100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
# 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')
|
||
|
||
# 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. 该币对已经占了多少(通过计算钱包中该币对的余额)
|
||
# 使用 wallets.get_free() 获取可用余额,get_used() 获取锁定余额
|
||
used_for_this_pair = self.wallets.get_used(pair)
|
||
|
||
# 5. 还剩多少可以投入
|
||
available = target_for_this_pair - used_for_this_pair
|
||
|
||
if available <= 0:
|
||
return 0
|
||
|
||
# 6. 简化逻辑:返回固定金额,让市场自然形成网格
|
||
# 如果想要25-30个订单,直接计算平均单笔
|
||
# 每个币对预算 / 目标订单数
|
||
per_order_stake = target_for_this_pair / TARGET_ORDERS_PER_PAIR
|
||
|
||
# 不超过可用资金,也不超过 proposed_stake
|
||
stake = min(per_order_stake, proposed_stake, available)
|
||
stake = max(stake, min_stake * 2) # 防止太小
|
||
|
||
return stake |