]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
perf(qav3): fine tune SL/TP computation
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Fri, 21 Mar 2025 03:38:09 +0000 (04:38 +0100)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Fri, 21 Mar 2025 03:38:09 +0000 (04:38 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
quickadapter/docs/ labeling_window.txt
quickadapter/user_data/strategies/QuickAdapterV3.py

index be1df488574e7a61272d465a27cd5bbe7c1d5c4c..4dba5d28a87b4c107d931d7553d2060555a5ccc2 100644 (file)
@@ -15,27 +15,34 @@ Labeling window:
 Price movement expectation over the labeling window:
 ----------------------------------------------------
 
-trade candles = current candle date - trade candle date / timeframe (in the same time unit)
+trade duration candles = (current candle date - trade candle date) // timeframe (in the same time unit)
 
-                                    trade candle
-                              |
--------------------------------       -> trade natr * trade open rate = expected price movement in the future label window candles
-[   trade natr label window   ]
+                           trade candle
+                                |
+                                     current candle
+                                |
+---------------------------------
+[   current natr label window   ]
 
+trade duration candles = 0
+expected price movement in the future label window candles = current natr * current rate
 
    trade candle                 current candle
              |                             |
----------------------------------     -> current natr * current rate = expected price movement in the future label window candles
+---------------------------------
 [   current natr label window   ]
 
-   trade candle                                current candle
-             |                                      |
-------------------------------------------------------------------   -> current natr * current rate * trade candles // label window  = expected price movement in the future (trade candles // label window candles) candles
-[   current natr label window   ][   current natr label window   ]
-
+trade duration candles > 0
+expected price movement in the future label window candles = current natr * current rate
+expected price movement in the future trade candles window = current natr * current rate * trades duration candles / label window
 
+  trade 1 candle                                 trade 2 candle            current candle
+             |                                         |                          |
+------------------------------------------------------------------
+[   current natr label window   ][   current natr label window   ]
 
 
+expected price movement in the future trade candles approximation = current natr * current rate * trade duration candles / label window
 
 
 
index 01ed4f62473be6c5da704ce297f0f9d43a10db5c..e1d0050453be3ea3edcbc6961c365ebe3c4bd14c 100644 (file)
@@ -3,6 +3,7 @@ import logging
 from functools import reduce
 import datetime
 from pathlib import Path
+from statistics import fmean
 import talib.abstract as ta
 from pandas import DataFrame, Series, isna
 from technical import qtpylib
@@ -61,7 +62,7 @@ class QuickAdapterV3(IStrategy):
 
     @property
     def trailing_stoploss_natr_ratio(self) -> float:
-        return self.config.get("trailing_stoploss_natr_ratio", 0.05)
+        return self.config.get("trailing_stoploss_natr_ratio", 0.025)
 
     # Trailing stop:
     trailing_stop = False
@@ -403,7 +404,14 @@ class QuickAdapterV3(IStrategy):
             return None
         return entry_candle
 
-    def get_trade_candles(self, df: DataFrame, trade: Trade) -> int | None:
+    def get_trade_entry_natr(self, df: DataFrame, trade: Trade) -> float | None:
+        entry_candle = self.get_trade_entry_candle(df, trade)
+        if isna(entry_candle):
+            return None
+        entry_candle = entry_candle.squeeze()
+        return entry_candle["natr_ratio_labeling_window"]
+
+    def get_trade_duration_candles(self, df: DataFrame, trade: Trade) -> int | None:
         entry_candle = self.get_trade_entry_candle(df, trade)
         if isna(entry_candle):
             return None
@@ -415,47 +423,41 @@ class QuickAdapterV3(IStrategy):
         trade_duration_minutes = (
             current_candle_date - entry_candle_date
         ).total_seconds() / 60.0
-        return max(
-            int(trade_duration_minutes / timeframe_to_minutes(self.timeframe)), 1
-        )
+        return trade_duration_minutes // timeframe_to_minutes(self.timeframe)
 
-    def get_trade_entry_natr(self, df: DataFrame, trade: Trade) -> float | None:
-        entry_candle = self.get_trade_entry_candle(df, trade)
-        if isna(entry_candle):
+    def is_trade_duration_valid(self, df: DataFrame, trade: Trade) -> bool:
+        trade_duration_candles = self.get_trade_duration_candles(df, trade)
+        if isna(trade_duration_candles):
+            return False
+        if trade_duration_candles == 0:
+            return False
+        return True
+
+    def get_stoploss_distance(
+        self, df: DataFrame, trade: Trade, current_rate: float
+    ) -> float | None:
+        if self.is_trade_duration_valid(df, trade) is False:
             return None
-        entry_candle = entry_candle.squeeze()
-        return entry_candle["natr_ratio_labeling_window"]
+        current_natr = df["natr_ratio_labeling_window"].iloc[-1]
+        if isna(current_natr):
+            return None
+        return current_rate * current_natr * self.trailing_stoploss_natr_ratio
 
-    def get_trade_stoploss_distance(self, df: DataFrame, trade: Trade) -> float | None:
+    def get_take_profit_distance(self, df: DataFrame, trade: Trade) -> float | None:
+        if self.is_trade_duration_valid(df, trade) is False:
+            return None
         entry_natr = self.get_trade_entry_natr(df, trade)
         if isna(entry_natr):
             return None
-        return trade.open_rate * entry_natr * self.trailing_stoploss_natr_ratio
-
-    def get_current_stoploss_distance(
-        self, df: DataFrame, current_rate: float
-    ) -> float | None:
         current_natr = df["natr_ratio_labeling_window"].iloc[-1]
         if isna(current_natr):
             return None
-        return current_rate * current_natr * self.trailing_stoploss_natr_ratio
-
-    def get_stoploss_distance(
-        self, df: DataFrame, trade: Trade, current_rate: float
-    ) -> float | None:
-        label_window_frequency = self.get_trade_candles(df, trade) // (
-            self.get_label_period_candles(trade.pair) * 2
+        return (
+            trade.open_rate
+            * max(entry_natr, fmean([entry_natr, current_natr]))
+            * self.trailing_stoploss_natr_ratio
+            * self.reward_risk_ratio
         )
-        # trade_stoploss_distance = self.get_trade_stoploss_distance(df, trade)
-        current_stoploss_distance = self.get_current_stoploss_distance(
-            df, current_rate
-        ) / self.get_trade_candles(df, trade)
-        # if isna(trade_stoploss_distance) or isna(current_stoploss_distance):
-        #     return None
-        # return max(trade_stoploss_distance, current_stoploss_distance)
-        if isna(current_stoploss_distance):
-            return None
-        return current_stoploss_distance
 
     def custom_stoploss(
         self,
@@ -481,11 +483,6 @@ class QuickAdapterV3(IStrategy):
         if isna(stoploss_distance):
             return None
         if stoploss_distance == 0:
-            logger.warning(
-                f"Stoploss distance is 0 for trade {trade.id} on pair {pair}. "
-                f"Current rate: {current_rate}, trade open rate: {trade.open_rate}, "
-                f"trade entry tag: {trade.enter_tag}"
-            )
             return None
         sign = 1 if trade.is_short else -1
         return stoploss_from_absolute(
@@ -530,17 +527,10 @@ class QuickAdapterV3(IStrategy):
         ):
             return "maxima_detected_long"
 
-        take_profit_distance = (
-            self.get_stoploss_distance(df, trade, current_rate) * self.reward_risk_ratio
-        )
+        take_profit_distance = self.get_take_profit_distance(df, trade)
         if isna(take_profit_distance):
             return None
         if take_profit_distance == 0:
-            logger.warning(
-                f"Take profit distance is 0 for trade {trade.id} on pair {pair}. "
-                f"Current rate: {current_rate}, trade open rate: {trade.open_rate}, "
-                f"trade entry tag: {trade.enter_tag}"
-            )
             return None
         if trade.is_short:
             take_profit_price = trade.open_rate - take_profit_distance