Important Disclaimer!!!
Currently I don’t use additional entries. In my opinion, more often than not, the additional entries do more harm than good. You are just digging your losses deeper. But you may be able to use it better than me. Do extensive tests to see whether it really helps you or not.
Moving on to the main thing…
This snippet would only do additional entries on top of the initial entry from the bot. There would be no partial exit. The concept is very simple, it will trigger first additional entry when current_profit
reaches initial_safety_order_trigger
. After the first one, the next entries trigger would be determined by safety_order_step_scale
, and the amount of stake will be determined by safety_order_volume_scale
.
Things to note
- You don’t need to copy line 1 to 7 if your existing strategy has imported them. Otherwise, add the missing imports
- Change
strat_dca
to a new name that you want - Change
yourstrat
to the actual class name of the base strategy - Modify line 15-18 to suit your preference. You can use the spreadsheet (link below) to help you.
- Copy and paste line 11 downward under the base strategy
- The
current_profit
used will be calculated only based of last candle’s close price. This means it won’t follow the change of current tick price. This is done to make sure the backtest behavior can closely match dry/live performance. If you don’t want such behavior, you can delete line 38-40, but do know that this will make your strategy’s performance between backtest vs dry/live can have significant difference. - This snippet will only do one extra entry per candle, which mean after the bot do extra entry on current open candle, it won’t do any extra entry until the next candle no matter how deep the current candle goes down (or up for short trade). This is done to make sure the backtest behavior can closely match dry/live performance. If you don’t want such behavior, you can delete line 49 and adjust the indent of line 50-67, but do know that this will make your strategy’s performance between backtest vs dry/live can have significant difference.
import math
import logging
from datetime import datetime, timedelta, timezone
from freqtrade.persistence import Trade
import time
from typing import Dict, List, Optional
from freqtrade.exchange import timeframe_to_minutes
logger = logging.getLogger(__name__)
class strat_dca (yourstrat):
position_adjustment_enable = True
initial_safety_order_trigger = -0.02
max_entry_position_adjustment = 3
safety_order_step_scale = 2
safety_order_volume_scale = 1.8
max_so_multiplier = (1 + max_entry_position_adjustment)
if(max_entry_position_adjustment > 0):
if(safety_order_volume_scale > 1):
max_so_multiplier = (2 + (safety_order_volume_scale * (math.pow(safety_order_volume_scale,(max_entry_position_adjustment - 1)) - 1) / (safety_order_volume_scale - 1)))
elif(safety_order_volume_scale < 1):
max_so_multiplier = (2 + (safety_order_volume_scale * (1 - math.pow(safety_order_volume_scale,(max_entry_position_adjustment - 1))) / (1 - safety_order_volume_scale)))
# Let unlimited stakes leave funds open for DCA orders
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float, max_stake: float,
**kwargs) -> float:
return proposed_stake / self.max_so_multiplier
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, min_stake: float,
max_stake: float, **kwargs) -> Optional[float]:
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
current_candle = dataframe.iloc[-1].squeeze()
current_profit = trade.calc_profit_ratio(current_candle['close'])
if current_profit > self.initial_safety_order_trigger:
return None
filled_entries = trade.select_filled_orders(trade.entry_side)
count_of_entries = len(filled_entries)
if 1 <= count_of_entries <= self.max_entry_position_adjustment:
if (current_time - timedelta(minutes=timeframe_to_minutes(self.timeframe))) >= filled_entries[(count_of_entries - 1)].order_filled_date.replace(tzinfo=timezone.utc):
safety_order_trigger = (abs(self.initial_safety_order_trigger) * count_of_entries)
if (self.safety_order_step_scale > 1):
safety_order_trigger = abs(self.initial_safety_order_trigger) + (abs(self.initial_safety_order_trigger) * self.safety_order_step_scale * (math.pow(self.safety_order_step_scale,(count_of_entries - 1)) - 1) / (self.safety_order_step_scale - 1))
elif (self.safety_order_step_scale < 1):
safety_order_trigger = abs(self.initial_safety_order_trigger) + (abs(self.initial_safety_order_trigger) * self.safety_order_step_scale * (1 - math.pow(self.safety_order_step_scale,(count_of_entries - 1))) / (1 - self.safety_order_step_scale))
if current_profit <= (-1 * abs(safety_order_trigger)):
try:
# This returns first order stake size
stake_amount = filled_entries[0].stake_amount
# This then calculates current safety order size
stake_amount = stake_amount * math.pow(self.safety_order_volume_scale,(count_of_entries - 1))
amount = stake_amount / current_rate
logger.info(f"Initiating additional entry #{count_of_entries} for {trade.pair} with stake amount of {stake_amount} {trade.stake_currency} which equals {amount} {trade.base_currency}")
return stake_amount
except Exception as exception:
logger.info(f'Error occured while trying to get stake amount for {trade.pair}: {str(exception)}')
return None
return None
You can look at the spreadsheet here to see how it would work out. There are 2 sheets there, calc
and example
. You can only change the value of cells highlighted in blue color on calc
sheet.
Also you need to take note of the values in the green color column. Depending on how many additional entries you set, you need to prepare stake amount. So if you look at the example
sheet, let’s say I want to allow 5 additional entries, you will need to prepare 252.144 USDT per trading slot. So if you set max_open_trades
to 5 for example, you need to have balance around 1265 USDT available to the bot.
Another thing to note, since the example is created for a long-direction trade, when a loss before extra entry
is more than 100%, the cells will be highlighted in red, as you can see in example
sheet. So taking the example, that means 5 additional entries is the maximum limit you can have. You can set more than 5, but it won’t do anything (in long trade). If you are on short trade, then you can ignore the red warning.
How the code works
First additional entry will happen once the loss equal or greater than initial_safety_order_trigger
. The stake amount of it will be equal to the original entry’s stake amount. After the first one, the additional entries’ loss trigger and stake amount might be changed depending on your safety_order_step_scale
and safety_order_volume_scale
settings (use the spreadsheet to play around with the settings).
Summary
This is a very simple additional entries code. You can modify it to increase it performance, maybe by adding indicator checks. Go creative.
[…] Simple Additional Entries Code […]
[…] slight improvement from the simple code, what if you want to include check based of dataframe value, for example, additional entries only […]