From 04d4f15dff8184a553365b845d8e08ea5853d35b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Jul 2022 10:32:16 +0200 Subject: [PATCH] Add example calculation --- docs/strategy-callbacks.md | 27 +++++++++++++++++++++++---- freqtrade/rpc/telegram.py | 1 + tests/test_persistence.py | 25 +++++++++++++++++++------ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 717cc043d..18de3513b 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -629,7 +629,7 @@ class AwesomeStrategy(IStrategy): The `position_adjustment_enable` strategy property enables the usage of `adjust_trade_position()` callback in the strategy. For performance reasons, it's disabled by default and freqtrade will show a warning message on startup if enabled. -`adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging) or to increase or decrease winning positions. +`adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging) or to increase or decrease positions. `max_entry_position_adjustment` property is used to limit the number of additional buys per trade (on top of the first buy) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment buys. @@ -652,12 +652,12 @@ Position adjustments will always be applied in the direction of the trade, so a !!! Warning Stoploss is still calculated from the initial opening price, not averaged price. + Regular stoploss rules still apply (cannot move down). -!!! Warning "/stopbuy" While `/stopbuy` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades. !!! Warning "Backtesting" - During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so performance will be affected. + During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected. ``` python from freqtrade.persistence import Trade @@ -678,7 +678,7 @@ class DigDeeperStrategy(IStrategy): max_dca_multiplier = 5.5 # This is called when placing the initial order (opening trade) -def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: Optional[float], max_stake: float, leverage: float, entry_tag: Optional[str], side: str, **kwargs) -> float: @@ -757,6 +757,25 @@ def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: f ``` +### Position adjust calculations + +* Entry rates are calculated using weighted averages. +* Exits will not influence the average entry rate. +* Partial exit relative profit is relative to the average entry price at this point. +* Final exit relative profit is calculated based on the total invested capital. (See example below) + +??? example "Calculation example" + *This example assumes 0 fees for simplicity, and a long position on an imaginary coin.* + + * Buy 100@8\$ + * Buy 100@9\$ -> Avg price: 8.5\$ + * Sell 100@10\$ -> Avg price: 8.5\$, realized profit 150\$, 17.65% + * Buy 150@11\$ -> Avg price: 10\$, realized profit 150\$, 17.65% + * Sell 100@12\$ -> Avg price: 10\$, total realized profit 350\$, 20% + * Sell 150@14\$ -> Avg price: 10\$, total realized profit 950\$, 40% + + The total profit for this trade was 950$ on a 3350$ investment (`100@8$ + 100@9$ + 150@11$`). As such - the final relative profit is 28.35% (`950 / 3350`). + ## Adjust Entry Price The `adjust_entry_price()` callback may be used by strategy developer to refresh/replace limit orders upon arrival of new candles. diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c9f6a10d9..66192fb16 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -485,6 +485,7 @@ class Telegram(RPCHandler): sumA += amount * filled_orders[y]["safe_price"] sumB += amount prev_avg_price = sumA / sumB + # TODO: This calculation ignores fees. price_to_1st_entry = ((cur_entry_average - first_avg) / first_avg) minus_on_entry = 0 if prev_avg_price: diff --git a/tests/test_persistence.py b/tests/test_persistence.py index f9c3c8e34..42fcc7413 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -2835,7 +2835,7 @@ def test_order_to_ccxt(limit_buy_order_open): (('buy', 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)), (('sell', 50, 12), (150.0, 12.5, 1875.0, -25.0, -25.0, -0.04)), (('sell', 100, 20), (50.0, 12.5, 625.0, 725.0, 750.0, 0.60)), - (('sell', 50, 5), (50.0, 12.5, 625.0, 725.0, -375.0, -0.60)), + (('sell', 50, 5), (50.0, 12.5, 625.0, 350.0, -375.0, -0.60)), ], 'end_profit': 350.0, 'end_profit_ratio': 0.14, @@ -2847,7 +2847,7 @@ def test_order_to_ccxt(limit_buy_order_open): (('buy', 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)), (('sell', 50, 12), (150.0, 12.5, 1875.0, -28.0625, -28.0625, -0.044788)), (('sell', 100, 20), (50.0, 12.5, 625.0, 713.8125, 741.875, 0.59201995)), - (('sell', 50, 5), (50.0, 12.5, 625.0, 713.8125, -377.1875, -0.60199501)), + (('sell', 50, 5), (50.0, 12.5, 625.0, 336.625, -377.1875, -0.60199501)), ], 'end_profit': 336.625, 'end_profit_ratio': 0.1343142, @@ -2860,7 +2860,7 @@ def test_order_to_ccxt(limit_buy_order_open): (('sell', 100, 11), (100.0, 5.0, 500.0, 596.0, 596.0, 1.189027)), (('buy', 150, 15), (250.0, 11.0, 2750.0, 596.0, 596.0, 1.189027)), (('sell', 100, 19), (150.0, 11.0, 1650.0, 1388.5, 792.5, 0.7186579)), - (('sell', 150, 23), (150.0, 11.0, 1650.0, 1388.5, 1787.25, 1.08048062)), + (('sell', 150, 23), (150.0, 11.0, 1650.0, 3175.75, 1787.25, 1.08048062)), ], 'end_profit': 3175.75, 'end_profit_ratio': 0.9747170, @@ -2874,11 +2874,24 @@ def test_order_to_ccxt(limit_buy_order_open): (('sell', 100, 11), (100.0, 5.0, 500.0, 600.0, 600.0, 1.2)), (('buy', 150, 15), (250.0, 11.0, 2750.0, 600.0, 600.0, 1.2)), (('sell', 100, 19), (150.0, 11.0, 1650.0, 1400.0, 800.0, 0.72727273)), - (('sell', 150, 23), (150.0, 11.0, 1650.0, 1400.0, 1800.0, 1.09090909)), + (('sell', 150, 23), (150.0, 11.0, 1650.0, 3200.0, 1800.0, 1.09090909)), ], 'end_profit': 3200.0, - 'fee': 0.0, 'end_profit_ratio': 0.98461538, + 'fee': 0.0, + }, + { + 'orders': [ + (('buy', 100, 8), (100.0, 8.0, 800.0, 0.0, None, None)), + (('buy', 100, 9), (200.0, 8.5, 1700.0, 0.0, None, None)), + (('sell', 100, 10), (100.0, 8.5, 850.0, 150.0, 150.0, 0.17647059)), + (('buy', 150, 11), (250.0, 10, 2500.0, 150.0, 150.0, 0.17647059)), + (('sell', 100, 12), (150.0, 10.0, 1500.0, 350.0, 350.0, 0.2)), + (('sell', 150, 14), (150.0, 10.0, 1500.0, 950.0, 950.0, 0.40)), + ], + 'end_profit': 950.0, + 'end_profit_ratio': 0.283582, + 'fee': 0.0, }, ]) def test_recalc_trade_from_orders_dca(data) -> None: @@ -2940,7 +2953,7 @@ def test_recalc_trade_from_orders_dca(data) -> None: assert trade.open_rate == result[1] assert trade.stake_amount == result[2] # TODO: enable the below. - # assert pytest.approx(trade.realized_profit) == result[3] + assert pytest.approx(trade.realized_profit) == result[3] # assert pytest.approx(trade.close_profit_abs) == result[4] assert pytest.approx(trade.close_profit) == result[5]