kieran-mackle / AutoTrader

A Python-based development platform for automated trading systems - from backtesting to optimisation to livetrading.

Home Page:https://kieran-mackle.github.io/AutoTrader/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Insufficient margin to fill order when stop loss is set

SeifallahSnoussi opened this issue · comments

Describe the bug
I've an issue when I set a stop loss in my strategy:
Situation :

  • stop loss is equal to 6% ( issue still valid in case of different values)
  • result : 0 trade executed --> reason : "Insufficient margin to fill order."

Configuration:

  • RISK_PC: 100
  • SIZING: 'risk'

Backtest configuration:

  • initial_balance=32000,
  • leverage = 1,
  • spread=0.03,
  • commission=0.005

Strategy Exit signals:

If signal == 1:
     stop = self.data.Close[i] * (1 - 0.06)  # self.data.Close[i] is the close price
If signal == -1:
     stop = self.data.Close[i] * (1 + 0.06)

When I've performed some code investigation, I figured out that ;
margin_required is always greater than self.margin_available ( in file broker.py inside _fill_order function )
Example :
margin_required=377116.75920040935 , self.margin_available=32000

This is due to how order.size is calculated in file broker_utils.py inside get_size function :
It seems that when a stop loss is set, the number of units to buy is calculated as follows:

price_distance = abs(price - stop_price)
quote_risk = amount_risked / HCF
units = quote_risk / price_distance

where HCF = 1

I don't understand why the position size is calculated in that way. why we divide it by the price_distance and not just price ?
In my point of view it must always be like this : units = amount_risked/(HCF*price)

Thanks

The SIZING: 'risk' method is a built in helper function to automatically size your positions when you provide a stop loss with an order. The idea is that if your stop loss gets hit, your account will only lose as much is defined by the RISK_PC parameter. In this way, your position sizes will be scaled based on where your stop loss sits, so that every trade will lose a fixed percentage of the account balance if the stop loss is hit.

Note that if your stop loss is particularly close (eg. 6% in this case), and your account leverage is only 1, the position size which results from the above calculation will exceed your margin requirements (ie. your balance, in the case of 1x leverage), as you have seen. That is, the size which is calculated would result in a 100% draw-down if price were to move 6% away from your entry.

This is just one approach to position sizing, it may not be optimal but it can be useful for a quick implementation. Adding other sizing methods is on my agenda.

You have a few options:

  • Reduce RISK_PC to a lower value, so that you have smaller position sizes within your margin requirements;
  • Increase your leverage, so that you have lower margin requirements;
  • Manually provide size when creating orders - this way you can use the formula you have suggested. You can fetch your current account balance using the broker methods get_NAV() or get_balance(). Note that you will need to include the INCLUDE_BROKER: True flag in your strategy parameters, so that your strategy is instantiated with the broker instance (see here).

Hopefully that makes sense - please let me know if you need any more clarification.

Thank you for this clarification.

I would prefer the 3rd option because I'm going to configure a variable stop loss ( it is value will depend on the market volatility and Instrument name)

I've tested options 1 and 3, and they work well. However, I got different results:

Fewer trades and lower gain with option 3 compared to option 1, Maybe this is due to the way how I calculate position size
position_size = self.broker.get_balance()/self.data.Close[i]

It's hard to say why you would have fewer trades without seeing your strategy, but there are a few things you can do to investigate what's going on. My guess would be that your account is still facing margin constraints, and so the broker isn't filling some orders when this is the case.

  • Inspect the trade history, accessible in the Backtest Results class: at.backtest_results.trade_history. You will be able to see each trade taken and the sizes of the trades, and compare them between each option.
  • Increase the broker verbosity option in autotrader.configure.
  • Compare the number of cancelled orders between each option in the backtest printout, and look more specifically at at.backtest_results.cancelled_orders to see the sizes of the orders which were cancelled. They are perhaps still too large in option 1, in which case reducing the RISK_PC should help.

In fact, I had more canceled trades with option 3,  so in order to correct it, I applied floor method when I calculate position size:

position_size  = floor(self.broker.get_NAV()/self.data.Close[i])

There is another reason why I used the floor method, it is because, in almost commodities and stocks, position size is generally a decimal number with precision from 0 to 4 digits at most. --> I must adapt the precision of position size for each Instrument.

Example of position size I've seen in trades history output before I've applied floor method: 1219.88442587388 ( Instrument = XAG_USD)
Almost brokers don't let traders open a position with that size value.

Fees should be round numbers too with less precision, Example of value I've seen: fees = 3.368840046

I think you can close this ticket because the main issue for which I opened it, has been resolved

Thanks

That's a very good point, and in fact precision checking is something I have been meaning to implement (as you can see in the check_precision method of Order). Hopefully I can solidify this implementation in a future release.