Simple Additional Entries Code

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.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *