diff --git a/config_examples/smartbbgrid.json b/config_examples/smartbbgrid.json new file mode 100644 index 00000000..94552122 --- /dev/null +++ b/config_examples/smartbbgrid.json @@ -0,0 +1,60 @@ +{ + "strategy": "SmartBBGrid", + "$schema": "https://schema.freqtrade.io/schema.json", + "redis": { + "url": "redis://192.168.1.215:6379/0" + }, + "trading_mode": "spot", + "margin_mode": "isolated", + "max_open_trades": 55, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 1, + "process_only_new_candles": false, + "dry_run": true, + "timeframe": "4h", + "dry_run_wallet": 3000, + "cancel_open_orders_on_exit": true, + "max_entry_position_adjustment": -1, + "position_adjustment_enable": true, + "amount_reserve_percent": 0.05, + "unfilledtimeout": { + "entry": 5, + "exit": 15 + }, + "startup_candle_count": 200, + "exchange": { + "name": "okx", + "key": "314e75b0-1113-47e8-ad01-1fca7e3c0496", + "secret": "9C8B170390F46EA6FB87592AD46F5A34", + "password": "nekFoylf:Om0", + "ccxt_config": { + "enableRateLimit": true, + "rateLimit": 500 + }, + "pair_whitelist": ["BTC/USDT", "ETH/USDT", "SOL/USDT"] + }, + "entry_pricing": { + "price_side": "same", + "use_order_book": true, + "order_book_top": 1 + }, + "exit_pricing": { + "price_side": "other", + "use_order_book": true, + "order_book_top": 1 + }, + "pairlists": [ + { + "method": "StaticPairList" + } + ], + "bot_name": "SmartBBGrid-2025", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 5, + "heartbeat_interval": 20, + "loglevel": "INFO" + } +} diff --git a/freqtrade/templates/smartbbgrid.json b/freqtrade/templates/smartbbgrid.json new file mode 100644 index 00000000..4ea0b7a1 --- /dev/null +++ b/freqtrade/templates/smartbbgrid.json @@ -0,0 +1,3 @@ +{ + "strategy_name": "SmartBBGrid" +} \ No newline at end of file diff --git a/freqtrade/templates/smartbbgrid.py b/freqtrade/templates/smartbbgrid.py new file mode 100644 index 00000000..3554c89a --- /dev/null +++ b/freqtrade/templates/smartbbgrid.py @@ -0,0 +1,102 @@ +# user_data/strategies/SmartBBGrid.py +from freqtrade.strategy import IStrategy +from pandas import DataFrame +import talib.abstract as ta + +class SmartBBGrid(IStrategy): + INTERFACE_VERSION = 3 + timeframe = '4h' + can_short = False + + minimal_roi = {"0": 100} # 不自动ROI + stoploss = -0.99 + startup_candle_count = 200 + use_exit_signal = True + exit_profit_only = False + ignore_roi_if_entry_signal = True + + # ---------- 指标 ---------- + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # 布林带(20期 2.3倍) + bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.3, nbdevdn=2.3) + dataframe['bb_lower'] = bb['lowerband'] + dataframe['bb_upper'] = bb['upperband'] + dataframe['bb_mid'] = bb['middleband'] + dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_mid'] + + # ADX + dataframe['adx'] = ta.ADX(dataframe, timeperiod=14) + return dataframe + + # ---------- 买入 ---------- + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe['enter_long'] = False + + # 震荡或强牛市都允许在下轨补仓 + oscillating = (dataframe['adx'] < 24) & (dataframe['bb_width'] > 0.04) + strong_bull = dataframe['adx'] > 30 + + dataframe.loc[ + ((oscillating | strong_bull) & + (dataframe['close'] <= dataframe['bb_lower'] * 1.006) & + (dataframe['volume'] > 0)), + 'enter_long'] = True + return dataframe + + # ---------- 卖出 ---------- + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe['exit_long'] = False + + # 只有震荡市才卖,牛市死拿 + dataframe.loc[ + (dataframe['adx'] < 24) & ( + (dataframe['close'] >= dataframe['bb_upper'] * 0.994) | # 碰到上轨卖 + (dataframe['close'] >= dataframe['bb_mid'] * 1.13) # 赚13%全清 + ), + 'exit_long'] = True + return dataframe + + # ---------- 每币25-30个订单 ---------- + + def custom_stake_amount(self, pair: str, current_time, current_rate, + proposed_stake, min_stake, max_stake, + entry_tag, **kwargs) -> float: + + # 目标:每个币对保持 25-30 个订单 + TARGET_ORDERS_PER_PAIR = 27 # 中位数 + + # 1. 拿到当前钱包总余额 + total_balance = self.wallets.get_total_stake_amount() + + # 2. 定义每个币对的资金占比 + weights = { + 'BTC/USDT': 0.30, + 'ETH/USDT': 0.30, + 'SOL/USDT': 0.40, + } + + # 3. 该币对应该占的总金额 + target_for_this_pair = total_balance * weights.get(pair, 0.33) + + # 4. 该币对已经占了多少 + used_for_this_pair = self.wallets.get_used_for_pair(pair) + + # 5. 还剩多少可以投入 + available = target_for_this_pair - used_for_this_pair + + if available <= 0: + return 0 + + # 6. 根据目标订单数,计算每笔应该投多少 + # 假设还需要投入的订单数 = 目标订单数 - 当前订单数 + current_trades_for_pair = len([t for t in self.trades if t.pair == pair]) + remaining_orders = max(1, TARGET_ORDERS_PER_PAIR - current_trades_for_pair) + + # 平均分配剩余资金给剩余订单 + per_order_stake = available / remaining_orders + + # 取最小值(不超过可用资金) + stake = min(per_order_stake, proposed_stake) + stake = max(stake, min_stake * 2) # 防止太小 + + return stake \ No newline at end of file