Order Margin Call on order_target_percent
-
Hi all. I’ve been struggling with this problem for a bit now. The orders keep on failing on margin just a few trades in when a commission scheme is added.
In attempt to backtest more accurately, I added a commission scheme on the strategy working off of https://teddykoker.com/2019/04/backtesting-a-cross-sectional-mean-reversion-strategy-in-python/:
Things I have tried:
- Fractioned the targets after weight calculation:
target=weights[i]/n
Reason: instead of taking ~50% and ~50% of the portfolio size for each of the 2 currency pairs in the universe, I scale it way down to not use 100% of cash. cerebro.broker.set_shortcash(False)
. Reason: Shorting can count as cash added to an account.cerebro.broker.set_checksubmit(False)
Reason: https://community.backtrader.com/topic/1611/having-margin-problem-when-reversing-the-position-using-order_target_percent/2. Similar problem, but for this strategy, doesn’t even do a partial order. Just straight to order failed because of margin call.cheat_on_open=True
. Weights are calculated at close, so it’ll make sense to try and execute order on open to match.
None of these solutions have worked for me, still margin called after the first few trades. Perhaps some of the attempts at solving this problem is not completely understanding config settings. It is the configuration that makes the most sense for me at this time.
Strategy:
class St(bt.Strategy): def log(self, arg): print("{} {}".format(self.datetime.datetime(), arg)) def __init__(self): if self.p.backtest: self.datastatus = 1 else: self.datastatus = 0 def notify_data(self, data, status, *args, **kwargs): print("*" * 5, "DATA NOTIF:", data._getstatusname(status), *args) if status == data.LIVE: self.datastatus = 1 def notify_order(self, order): """Run on every next iteration. Checks order status and logs accordingly""" if order.status in [order.Submitted, order.Accepted]: return elif order.status == order.Completed: if order.isbuy(): self.log( "BUY EXECUTED price: {}, value: {}, commission: {}, broker: {}, cash: {}".format( order.executed.price, order.executed.value, order.executed.comm, self.broker.get_value(), self.broker.get_cash(), ) ) self.buyprice = order.executed.price self.buycomm = order.executed.comm else: # sell self.log( "SELL EXECUTED price: {}, value: {}, commission: {}, broker: {}, cash: {}".format( order.executed.price, order.executed.value, order.executed.comm, self.broker.get_value(), self.broker.get_cash(), ) ) elif order.status in [order.Margin, order.Rejected, order.Canceled]: self.log( "ORDER FAILED with status: {}, BROKER VAL: {}, CASH AVAILABLE: {}".format( order.getstatusname(), self.broker.get_value(), self.broker.get_cash() ) ) # change order variable back to None to indicate no pending order self.order = None def notify_trade(self, trade): """Run on every next iteration. Logs data on every trade when closed.""" if trade.isclosed: self.log("CLOSE Gross P/L: {}, Net P/L: {}".format(trade.pnl, trade.pnlcomm)) def next(self): # only look at data that existed yesterday available = list(filter(lambda d: len(d), self.datas)) rets = np.zeros(len(available)) for i, d in enumerate(available): # calculate individual daily returns rets[i] = (d.close[0] - d.close[-1]) / d.close[-1] # calculate weights using formula market_ret = np.mean(rets) weights = -(rets - market_ret) weights = weights / np.sum(np.abs(weights)) for i, d in enumerate(available): dt, dn = self.datetime.datetime(), d._name if self.datastatus: self.order_target_percent(d, target=weights[i] / 4) self.log( "ORDER CREATED: {:.2f} on {} with broker val: {} cash: {} weights: {} ".format( d.close[0], dn, self.broker.get_value(), self.broker.get_cash(), weights[i] / 4 ) )
Commission Scheme:
class forexSpreadCommisionScheme(bt.CommInfoBase): """ https://backtest-rookies.com/2017/07/13/code-snippet-forex-commission-scheme/ """ params = ( ("spread", 2.0), ("JPY_pair", False), ("stocklike", False), ("acc_counter_currency", True), ("margin", 0.03), # highest margin requirements for these /USD pairs. ("leverage", 20.0), # 20x cash ("commtype", bt.CommInfoBase.COMM_FIXED), ) def _getcommission(self, size, price, pseudoexec): if self.p.JPY_pair == True: multiplier = 0.01 else: multiplier = 0.0001 if self.p.acc_counter_currency == True: comm = abs((self.p.spread * (size * multiplier) / 2)) else: comm = abs((self.p.spread * ((size / price) * multiplier) / 2)) return comm
Configuration:
cerebro = bt.Cerebro(quicknotify=True, cheat_on_open=True) for ticker in ['NZD_USD', 'AUD_USD']: # get datafeed here... cerebro.adddata(data) cerebro.broker.setcash(10_000) cerebro.addstrategy(St) cerebro.addsizer(bt.sizers.FixedSize, stake=1) # Add the new commission scheme comminfo = forexSpreadCommisionScheme(spread=2.0, acc_counter_currency=True) cerebro.broker.addcommissioninfo(comminfo) # Set slippage for realistic scenarios. Increase chance of order filling cerebro.broker.set_slippage_fixed(0.0005, slip_open=False, slip_match=False, slip_out=False) cerebro.broker.set_shortcash(False) # Adds cash when shorting, instead of subtracting cerebro.broker.set_checksubmit(False) # Check margin/cash before accepting an order into the system results = cerebro.run(tradehistory=True) pnl = cerebro.broker.get_value() - args.cash print("Profit or Loss: {:.2f}".format(pnl))
Logs:
2010-12-30 22:00:00 ORDER CREATED: 0.77 on NZD_USD with broker val: 10000.0 cash: 10000.0 weights: 0.12500000000000003 2010-12-30 22:00:00 ORDER CREATED: 1.02 on AUD_USD with broker val: 10000.0 cash: 10000.0 weights: -0.125 2010-12-30 22:00:00 BUY EXECUTED price: 0.77988, value: 24999.6, commission: 83.33200000000001, broker: 23011.875139999738, cash: 14091.601199999866 2010-12-30 22:00:00 SELL EXECUTED price: 1.02324, value: 24999.6, commission: 83.33200000000001, broker: 23011.875139999738, cash: 14091.601199999866 2010-12-31 22:00:00 ORDER CREATED: 0.78 on NZD_USD with broker val: 23011.875139999738 cash: 14091.601199999866 weights: 0.125 2010-12-31 22:00:00 ORDER CREATED: 1.02 on AUD_USD with broker val: 23011.875139999738 cash: 14091.601199999866 weights: -0.125 2010-12-31 22:00:00 CLOSE Gross P/L: -3041.618000000035, Net P/L: -3208.282000000035 2010-12-31 22:00:00 ORDER FAILED with status: Margin, BROKER VAL: 12298.29655999978, CASH AVAILABLE: 8325.02679999987 2010-12-31 22:00:00 ORDER FAILED with status: Margin, BROKER VAL: 12298.29655999978, CASH AVAILABLE: 8325.02679999987 2011-01-01 22:00:00 ORDER CREATED: 0.77 on NZD_USD with broker val: 12298.29655999978 cash: 8325.02679999987 weights: 0.125 2011-01-01 22:00:00 ORDER CREATED: 1.02 on AUD_USD with broker val: 12298.29655999978 cash: 8325.02679999987 weights: -0.125 2011-01-01 22:00:00 BUY EXECUTED price: 0.77328, value: 30745.199999999997, commission: 102.48400000000001, broker: 18258.873459999806, cash: 9794.544799999883 2011-01-01 22:00:00 ORDER FAILED with status: Margin, BROKER VAL: 18258.873459999806, CASH AVAILABLE: 9794.544799999883 2011-01-02 22:00:00 ORDER CREATED: 0.77 on NZD_USD with broker val: 18258.873459999806 cash: 9794.544799999883 weights: -0.12500000000000003 2011-01-02 22:00:00 ORDER CREATED: 1.02 on AUD_USD with broker val: 18258.873459999806 cash: 9794.544799999883 weights: 0.12499999999999997 2011-01-02 22:00:00 CLOSE Gross P/L: -6866.427999999926, Net P/L: -7071.395999999926 2011-01-02 22:00:00 ORDER FAILED with status: Margin, BROKER VAL: 32688.961160000075, CASH AVAILABLE: 15336.738800000057 2011-01-02 22:00:00 ORDER FAILED with status: Margin, BROKER VAL: 32688.961160000075, CASH AVAILABLE: 15336.738800000057 2011-01-03 22:00:00 ORDER CREATED: 0.76 on NZD_USD with broker val: 32688.961160000075 cash: 15336.738800000057 weights: -0.12499999999999978 2011-01-03 22:00:00 ORDER CREATED: 1.00 on AUD_USD with broker val: 32688.961160000075 cash: 15336.738800000057 weights: 0.12500000000000022 2011-01-03 22:00:00 SELL EXECUTED price: 0.75709, value: 81722.4, commission: 272.408, broker: 47940.29879999999, cash: 18939.106000000014 2011-01-03 22:00:00 ORDER FAILED with status: Margin, BROKER VAL: 47940.29879999999, CASH AVAILABLE: 18939.106000000014 2011-01-04 22:00:00 ORDER CREATED: 0.76 on NZD_USD with broker val: 47940.29879999999 cash: 18939.106000000014 weights: 0.12500000000000003 2011-01-04 22:00:00 ORDER CREATED: 1.00 on AUD_USD with broker val: 47940.29879999999 cash: 18939.106000000014 weights: -0.12499999999999997 .... .... .... 2019-07-25 21:00:00 ORDER CREATED: 0.66 on NZD_USD with broker val: -94102645413.3025 cash: -94102645413.3025 weights: -0.12499999999999993 2019-07-25 21:00:00 ORDER CREATED: 0.69 on AUD_USD with broker val: -94102645413.3025 cash: -94102645413.3025 weights: 0.12500000000000008 2019-07-25 21:00:00 ORDER FAILED with status: Margin, BROKER VAL: -94102645413.3025, CASH AVAILABLE: -94102645413.3025 2019-07-25 21:00:00 ORDER FAILED with status: Margin, BROKER VAL: -94102645413.3025, CASH AVAILABLE: -94102645413.3025 2019-07-28 21:00:00 ORDER CREATED: 0.66 on NZD_USD with broker val: -94102645413.3025 cash: -94102645413.3025 weights: 0.125 2019-07-28 21:00:00 ORDER CREATED: 0.69 on AUD_USD with broker val: -94102645413.3025 cash: -94102645413.3025 weights: -0.125 2019-07-28 21:00:00 ORDER FAILED with status: Margin, BROKER VAL: -94102645413.3025, CASH AVAILABLE: -94102645413.3025 2019-07-28 21:00:00 ORDER FAILED with status: Margin, BROKER VAL: -94102645413.3025, CASH AVAILABLE: -94102645413.3025 2019-07-29 21:00:00 ORDER CREATED: 0.66 on NZD_USD with broker val: -94102645413.3025 cash: -94102645413.3025 weights: -0.125 2019-07-29 21:00:00 ORDER CREATED: 0.69 on AUD_USD with broker val: -94102645413.3025 cash: -94102645413.3025 weights: 0.125 Strategy run finished with Run ID: 19 Profit or Loss: -94102655413.30
Is there something I am overlooking/misunderstanding? Only in year 2011 of the logs are a handful of successfully executed orders and closed positions. Any insights as to what may be happening is appreciated and let me know if more information is required. Thank you!
- Fractioned the targets after weight calculation:
-
Bump. Any ideas?