kieran-mackle / AutoTrader

A Python-based development platform for automated trading systems - from backtesting to optimisation to livetrading.
https://kieran-mackle.github.io/AutoTrader/
GNU General Public License v3.0
945 stars 217 forks source link

Insufficient margin to fill order when stop loss is set #38

Closed SeifallahSnoussi closed 2 years ago

SeifallahSnoussi commented 2 years ago

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

Configuration:

Backtest configuration:

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/(HCFprice)**

Thanks

kieran-mackle commented 2 years ago

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:

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

SeifallahSnoussi commented 2 years ago

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]

kieran-mackle commented 2 years ago

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.

SeifallahSnoussi commented 2 years ago

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

kieran-mackle commented 2 years ago

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.