alpacahq / Alpaca-API

The Alpaca API is a developer interface for trading operations and market data reception through the Alpaca platform.
https://alpaca.markets/
141 stars 13 forks source link

Please enable long<->short flipping or target weight orders #187

Open bionicles opened 2 years ago

bionicles commented 2 years ago

Is your feature request related to a problem? Please describe. To convert a long position to short, one must execute 2 trades. My algorithm outputs a target allocation, not a trade, so this forces me to do a bunch of complicated annoying calculations and write a lot of code to await resolution of multiple orders. It'd be easier if you guys did it once, versus all of your customers having to re-implement the stuff

This was working, but then I hit a major issue: alpaca's paper trading system bugged out and recorded equity going super high, then to zero, it seemed like a mess, so I quit using Alpaca for months and just made a custom paper trading thing. I want to give Alpaca another try.

Describe the solution you'd like I wish if I were allocated to a long or short position, I could simply execute a target weight order, and Alpaca would figure out how to allocate that % of my portfolio to the stock. Or, if you can't do target weight orders, then could you at least allow us to automatically flip from long to short or short to long?

Describe alternatives you've considered Well, I stopped using Alpaca months ago.

Additional context here's some free code for you. consider license MIT. Broker is just an abstract class with buy, sell, close, get_weight returns a float for the allocation. etc...

# ramda.py
def curry2(fun: Callable) -> any:
    @functools.wraps(fun)
    def f2(*args, **kwargs):
        if len(args) == 0:
            return f2
        if len(args) == 1:
            new_fun: Callable = partial(fun, args[0], **kwargs)
            return new_fun
        result: any = fun(*args, **kwargs)
        return result

    return f2

# trade.py
@R.curry2
def check_status(mystatus, id, api=None):
    if api is None:
        api = get_alpaca()
    order = api.get_order(id)._raw
    return True if order["status"] == mystatus else False

check_filled = check_status("filled")  # pylint: disable: no-value-for-parameter

@R.curry2
def wait_until(status, order_id, api=None, max_wait=env.MAX_WAIT):
    if api is None:
        api = get_alpaca()
    count = 0
    while not check_status(status, order_id):
        time.sleep(1)
        count += 1
        if count >= max_wait:
            api.cancel_order(order_id)
            log.critical("order didn't fill!")
            show_order(order_id)
            return False
    return True

wait_until_filled = wait_until("filled")

# rebalance.py
def rebalance(ticker, target, broker=None):
    if broker is None:
        broker = get_broker()
    # Portfolio (USD, SPY, ...)
    # Get number from -1, 1 as output from algorithm for weighting
    # 1: 100% Long, -1: 100% Short
    # Given current portfolio allocation and desired weighting rank (-1,1)
    # Fetch current allocation
    # current = (current_quantity * current_price) / portfolio_value
    current_price = get_price(ticker=ticker)
    current_weight = broker.get_weight(ticker=ticker, price=current_price)
    log.info(f"rebalance {ticker} from {current_weight:.02f} to {target:.02f}")
    # Execute the buy/sell order to reach allocation
    should_close = False
    if current_weight < target:  # buying
        misallocation = target - current_weight
        trade = broker.buy
        trade_str = "buy"
    elif current_weight > target:  # selling
        misallocation = current_weight - target
        trade = broker.sell
        trade_str = "sell"
    maybe_close_order = ()
    if R.sign(current_weight) != R.sign(target):  # "cross" the long/short boundary
        log.info(f"close the {ticker} position")
        close_order = broker.close(ticker)
        close_order_filled = broker.wait_until("filled", close_order["id"])
        if not close_order_filled:
            log.critical(f"couldn't close the {ticker} position. try again")
            return (close_order,)
        maybe_close_order = (close_order,)
        misallocation = target
    portfolio_value = broker.get_portfolio_value()
    desired_value_change = misallocation * portfolio_value
    qty = abs(desired_value_change / current_price)
    log.info(trade_str, qty, ticker)
    maybe_order = ()
    if qty > 1:
        order = trade(qty, ticker)
        wait_until("filled", order["id"])
        maybe_order = (order,)
    return tuple(maybe_close_order + maybe_order)
bionicles commented 2 years ago

image ^^^ equity clearly bugged in paper trades