💬 Forum

MainStrategy

🏆 League #339 / 1939

SandPearlStone/trading-bot/freqtrade_userdata/strategies/MainStrategy.py · first seen 2026-07-16 · repo updated 2026-03-21

Basics mode: futures timeframe: 1h interface version: 3
Settings stoploss: -0.03 has minimal roi custom stoploss
Indicators ADX ATR Bollinger_Bands EMA MACD MFI ROC RSI Stochastic talib
15 related strategies ( identical code, similar name)

Each tile is a different kind of check — from an instant code lint to full sandboxed backtests and forward tests on recent data. Not sure what a check actually proves? See the FAQ →

Source

Download Raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
"""
MainStrategy — FreqTrade strategy with FreqAI + P1-3 confluence + Kelly sizing.

Uses:
- FreqAI LightGBMRegressor for ML predictions
- Custom P1-3 confluence scoring for signal gating
- Kelly criterion for position sizing
- ATR-based SL/TP
"""

import sys
sys.path.insert(0, '/home/sandro/.openclaw/workspace/trading')

from freqtrade.strategy import IStrategy, merge_informative_pair
from freqtrade.persistence import Trade
import talib
import numpy as np
import pandas as pd
from pandas import DataFrame


class MainStrategy(IStrategy):
    """FreqTrade strategy wrapping P1-3 + ML system."""
    
    # Strategy settings
    INTERFACE_VERSION = 3
    timeframe = '1h'
    informative_timeframes = ['4h', '15m']
    
    # Can short
    can_short = True
    
    # Stoploss
    stoploss = -0.03  # 3% max (ATR-based SL is tighter)
    trailing_stop = False
    
    # Take profit (handled by custom exit)
    minimal_roi = {"0": 0.05}  # 5% max, but ATR-based TP is tighter
    
    # Position sizing
    position_adjustment_enable = False
    
    # FreqAI
    freqai_info = {
        "enabled": True,
        "purge_old_models": 4,
        "train_period_days": 90,
        "backtest_period_days": 7,
        "identifier": "sandro_v1",
        "feature_parameters": {
            "include_timeframes": ["1h"],
            "include_corr_pairlist": ["BTC/USDT"],
            "label_period_candles": 10,
            "include_shifted_candles": 5,
            "indicator_periods_candles": [7, 14, 21],
            "DI_threshold": 0.9,
            "weight_factor": 0.9,
        },
        "data_split_parameters": {
            "test_size": 0.25,
            "random_state": 42,
        },
        "model_training_parameters": {
            "n_estimators": 200,
            "learning_rate": 0.05,
            "max_depth": 7,
        },
    }
    
    # === FEATURE ENGINEERING ===
    
    def feature_engineering_expand_all(self, dataframe: DataFrame, period, metadata, **kwargs) -> DataFrame:
        """Features that expand across periods, timeframes, and correlated pairs."""
        dataframe["%-rsi-period"] = talib.RSI(dataframe["close"], timeperiod=period)
        dataframe["%-mfi-period"] = talib.MFI(dataframe["high"], dataframe["low"], dataframe["close"], dataframe["volume"], timeperiod=period)
        dataframe["%-adx-period"] = talib.ADX(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=period)
        dataframe["%-roc-period"] = talib.ROC(dataframe["close"], timeperiod=period)
        
        bollinger = talib.BBANDS(dataframe["close"], timeperiod=period)
        dataframe["%-bb_width-period"] = (bollinger[0] - bollinger[2]) / bollinger[1]
        dataframe["%-bb_pctb-period"] = (dataframe["close"] - bollinger[2]) / (bollinger[0] - bollinger[2])
        
        dataframe["%-relative_volume-period"] = dataframe["volume"] / dataframe["volume"].rolling(period).mean()
        
        return dataframe
    
    def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata, **kwargs) -> DataFrame:
        """Features that expand across timeframes and correlated pairs (not periods)."""
        dataframe["%-pct-change"] = dataframe["close"].pct_change()
        dataframe["%-raw_volume"] = dataframe["volume"]
        
        # EMA ratios
        ema8 = talib.EMA(dataframe["close"], timeperiod=8)
        ema21 = talib.EMA(dataframe["close"], timeperiod=21)
        ema55 = talib.EMA(dataframe["close"], timeperiod=55)
        dataframe["%-ema_8_21_ratio"] = ema8 / ema21
        dataframe["%-ema_21_55_ratio"] = ema21 / ema55
        
        # ATR as percentage
        dataframe["%-atr_pct"] = talib.ATR(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=14) / dataframe["close"] * 100
        
        # MACD
        macd, signal, hist = talib.MACD(dataframe["close"])
        dataframe["%-macd_hist"] = hist
        
        # Stochastic
        dataframe["%-stoch_k"], dataframe["%-stoch_d"] = talib.STOCH(dataframe["high"], dataframe["low"], dataframe["close"])
        
        return dataframe
    
    def feature_engineering_standard(self, dataframe: DataFrame, metadata, **kwargs) -> DataFrame:
        """Non-expanded features (temporal, custom)."""
        # Temporal
        dataframe["%-hour_of_day"] = (dataframe["date"].dt.hour + 1) / 25
        dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7
        
        return dataframe
    
    def set_freqai_targets(self, dataframe: DataFrame, metadata, **kwargs) -> DataFrame:
        """Set prediction target: forward return 10 bars ahead minus fees."""
        dataframe["&-target"] = (
            dataframe["close"]
            .shift(-self.freqai_info["feature_parameters"]["label_period_candles"])
            / dataframe["close"]
            - 1
            - 0.001  # subtract fees
        )
        return dataframe
    
    # === ENTRY/EXIT LOGIC ===
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Calculate ATR for SL/TP and confluence score."""
        dataframe["atr"] = talib.ATR(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=14)
        
        # P1-3 Confluence approximation (pre-feature-engineering)
        rsi = talib.RSI(dataframe["close"], timeperiod=14)
        adx = talib.ADX(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=14)
        ema8 = talib.EMA(dataframe["close"], timeperiod=8)
        ema21 = talib.EMA(dataframe["close"], timeperiod=21)
        vol_ratio = dataframe["volume"] / dataframe["volume"].rolling(20).mean()
        bb = talib.BBANDS(dataframe["close"], timeperiod=20)
        bb_pctb = (dataframe["close"] - bb[2]) / (bb[0] - bb[2])
        
        score = pd.Series(0, index=dataframe.index)
        score += ((rsi < 30) | (rsi > 70)).astype(int) * 20
        score += (((rsi >= 30) & (rsi < 40)) | ((rsi > 60) & (rsi <= 70))).astype(int) * 10
        score += (adx > 25).astype(int) * 15
        score += (((bb_pctb < 0.1) | (bb_pctb > 0.9))).astype(int) * 15
        score += (abs(ema8 / ema21 - 1) > 0.002).astype(int) * 15
        score += (vol_ratio > 1.5).astype(int) * 15
        
        dataframe["%-confluence_score"] = score.clip(0, 100)
        
        # FreqAI predictions (populated by FreqAI framework)
        # dataframe["&-target"] will contain the predicted return
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Entry signals: P1-3 confluence gate + basic volume/trend filter."""
        
        # Additional trend filter: EMA alignment
        ema8 = talib.EMA(dataframe["close"], timeperiod=8)
        ema21 = talib.EMA(dataframe["close"], timeperiod=21)
        ema_bullish = ema8 > ema21
        ema_bearish = ema8 < ema21
        
        # Long entries: confluence + bullish EMA trend + volume
        dataframe.loc[
            (
                (dataframe["%-confluence_score"] >= 40) &  # P1-3 confluence gate
                ema_bullish &                               # EMA trend filter
                (dataframe["volume"] > 0)
            ),
            ["enter_long", "enter_tag"]
        ] = (1, "p13_long")
        
        # Short entries: confluence + bearish EMA trend + volume
        dataframe.loc[
            (
                (dataframe["%-confluence_score"] >= 40) &  # P1-3 confluence gate
                ema_bearish &                               # EMA trend filter
                (dataframe["volume"] > 0)
            ),
            ["enter_short", "enter_tag"]
        ] = (1, "p13_short")
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Exit handled by stoploss and ROI."""
        dataframe["exit_long"] = 0
        dataframe["exit_short"] = 0
        return dataframe
    
    def custom_stoploss(self, pair: str, trade: Trade, current_time, current_rate, current_profit, after_fill, **kwargs) -> float:
        """ATR-based dynamic stoploss: 1.5 × ATR."""
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if len(dataframe) > 0:
            last = dataframe.iloc[-1]
            atr = last.get("atr", 0)
            if atr > 0:
                sl_pct = (1.5 * atr) / current_rate
                return -sl_pct
        return self.stoploss
    
    def custom_stake_amount(self, pair: str, current_time, current_rate, proposed_stake, min_stake, max_stake, leverage, entry_tag, side, **kwargs) -> float:
        """Kelly criterion position sizing (f*/4 conservative)."""
        # Simple Kelly: use backtest WR 52% and R:R 1.67
        win_rate = 0.52
        rr_ratio = 2.5 / 1.5  # TP/SL = 1.67
        
        kelly_f = (win_rate * rr_ratio - (1 - win_rate)) / rr_ratio
        conservative_f = kelly_f / 4  # f*/4
        
        if conservative_f <= 0:
            return min_stake
        
        # Adjust by regime (reduce in low-confidence setups)
        stake = proposed_stake * min(conservative_f * 4, 1.0)  # scale
        return max(min(stake, max_stake), min_stake)