myTestFreqAI/freqtrade/templates/freqaiprimer.py
2025-05-31 16:04:05 +00:00

286 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import logging
import numpy as np
import datetime
from functools import reduce
from freqtrade.persistence import Trade
import talib.abstract as ta
from pandas import DataFrame
from typing import Dict
from freqtrade.strategy import IStrategy
logger = logging.getLogger(__name__)
class FreqaiPrimer(IStrategy):
"""
策略说明:
- 只使用回归模型预测价值背离(&-price_value_divergence
- 动态调整阈值,基于 price_value_divergence 的历史数据
- 放宽过滤条件,增加信号触发频率
- 使用RSI和布林带作为辅助过滤条件
"""
# 策略参数
minimal_roi = {
"0": 0.02, # 固定止盈2%
"30": 0.01,
"60": 0
}
stoploss = -0.015 # 固定止损-1.5%
timeframe = "3m" # 3分钟K线
use_custom_stoploss = False # 不使用动态止损,改为固定止损
# 绘图配置
plot_config = {
"main_plot": {
"ema200": {"color": "blue"},
"bb_upperband": {"color": "gray"},
"bb_lowerband": {"color": "gray"},
"bb_middleband": {"color": "gray"}
},
"subplots": {
"Signals": {
"enter_long": {"color": "green"},
"exit_long": {"color": "red"}
},
"Price-Value Divergence": {
"&-price_value_divergence": {"color": "purple"}
},
"Volume Z-Score": {
"volume_z_score": {"color": "orange"}
},
"RSI": {
"rsi": {"color": "cyan"}
}
}
}
# FreqAI 配置:回归模型(预测价值背离)
freqai_info = {
"identifier": "divergence_model",
"model": "LightGBMRegressor",
"feature_parameters": {
"include_timeframes": ["3m", "15m", "1h"],
"label_period_candles": 12,
"include_shifted_candles": 3,
},
"data_split_parameters": {
"test_size": 0.2,
"shuffle": False,
},
"model_training_parameters": {
"n_estimators": 200,
"learning_rate": 0.05,
"num_leaves": 31,
"verbose": -1,
},
"fit_live_predictions_candles": 100,
}
def __init__(self, config: dict, *args, **kwargs):
super().__init__(config, *args, **kwargs)
logger.setLevel(logging.DEBUG)
logger.debug("✅ 策略已初始化,日志级别设置为 DEBUG")
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
"""
特征工程计算技术指标作为FreqAI的输入特征
"""
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
real = ta.TYPPRICE(dataframe)
upperband, middleband, lowerband = ta.BBANDS(real, timeperiod=period, nbdevup=2.0, nbdevdn=2.0)
dataframe["bb_lowerband-period"] = lowerband
dataframe["bb_upperband-period"] = upperband
dataframe["bb_middleband-period"] = middleband
dataframe["%-bb_width-period"] = (dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]) / dataframe["bb_middleband-period"]
dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period)
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
dataframe["%-relative_volume-period"] = dataframe["volume"] / dataframe["volume"].rolling(period).mean()
# 计算价值背离作为特征
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
dataframe["%-price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
columns_to_clean = [
"%-rsi-period", "%-mfi-period", "%-sma-period", "%-ema-period", "%-adx-period",
"bb_lowerband-period", "bb_middleband-period", "bb_upperband-period",
"%-bb_width-period", "%-relative_volume-period", "%-price_value_divergence"
]
for col in columns_to_clean:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0)
logger.debug(f"[{metadata['pair']}] 特征工程完成,列:{list(dataframe.columns)}")
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
"""
设置FreqAI的训练目标只预测价值背离
"""
if len(dataframe) < 200:
logger.warning(f"[{metadata['pair']}] 数据量不足({len(dataframe)}根K线需要至少200根K线进行训练")
return dataframe
# 计算200周期EMA作为公平价值
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
# 计算价值背离(回归目标)
dataframe["&-price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
# 成交量Z分数
dataframe["volume_mean_20"] = dataframe["volume"].rolling(20).mean()
dataframe["volume_std_20"] = dataframe["volume"].rolling(20).std()
dataframe["volume_z_score"] = (dataframe["volume"] - dataframe["volume_mean_20"]) / dataframe["volume_std_20"]
# 数据清理
dataframe["&-price_value_divergence"] = dataframe["&-price_value_divergence"].replace([np.inf, -np.inf], 0).ffill().fillna(0)
dataframe["volume_z_score"] = dataframe["volume_z_score"].replace([np.inf, -np.inf], 0).ffill().fillna(0)
logger.debug(f"[{metadata['pair']}] 目标列生成完成,列:{list(dataframe.columns)}")
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
计算所有指标包括FreqAI预测结果并基于历史数据动态调整阈值
"""
logger.info(f"[{metadata['pair']}] 当前可用列调用FreqAI前{list(dataframe.columns)}")
# 计算200周期EMA和历史价值背离
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
dataframe["price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
# 动态计算阈值:基于 price_value_divergence 的历史均值和标准差
if "price_value_divergence" in dataframe.columns:
divergence_mean = dataframe["price_value_divergence"].mean()
divergence_std = dataframe["price_value_divergence"].std()
k = 1.5 # 标准差倍数,可以调整
self.buy_threshold = max(divergence_mean - k * divergence_std, -0.05) # 设置下限,避免过低
self.sell_threshold = min(divergence_mean + k * divergence_std, 0.05) # 设置上限,避免过高
# 确保阈值至少有一定宽度
self.buy_threshold = min(self.buy_threshold, -0.005)
self.sell_threshold = max(self.sell_threshold, 0.005)
logger.info(f"[{metadata['pair']}] price_value_divergence 历史均值:{divergence_mean:.4f}")
logger.info(f"[{metadata['pair']}] price_value_divergence 历史标准差:{divergence_std:.4f}")
logger.info(f"[{metadata['pair']}] 动态买入阈值:{self.buy_threshold:.4f}")
logger.info(f"[{metadata['pair']}] 动态卖出阈值:{self.sell_threshold:.4f}")
else:
self.buy_threshold = -0.015 # 回退阈值
self.sell_threshold = 0.015
logger.warning(f"[{metadata['pair']}] 无法计算动态阈值,使用默认阈值 ±0.015")
# 调用FreqAI预测价值背离
if not hasattr(self, 'freqai') or self.freqai is None:
logger.error(f"[{metadata['pair']}] FreqAI 未初始化,请确保回测命令中启用了 --freqai")
# 回退到规则计算
dataframe["&-price_value_divergence"] = dataframe["price_value_divergence"]
else:
dataframe = self.freqai.start(dataframe, metadata, self)
# 检查预测结果
if "&-price_value_divergence" not in dataframe.columns:
logger.warning(f"[{metadata['pair']}] 回归模型未生成 &-price_value_divergence回退到规则计算")
dataframe["&-price_value_divergence"] = dataframe["price_value_divergence"]
# 计算其他指标
upperband, middleband, lowerband = ta.BBANDS(dataframe["close"], timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
dataframe["bb_upperband"] = upperband
dataframe["bb_middleband"] = middleband
dataframe["bb_lowerband"] = lowerband
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["volume_mean_20"] = dataframe["volume"].rolling(20).mean()
dataframe["volume_std_20"] = dataframe["volume"].rolling(20).std()
dataframe["volume_z_score"] = (dataframe["volume"] - dataframe["volume_mean_20"]) / dataframe["volume_std_20"]
# 数据清理
for col in ["ema200", "bb_upperband", "bb_middleband", "bb_lowerband", "rsi", "volume_z_score", "&-price_value_divergence", "price_value_divergence"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0)
logger.info(f"[{metadata['pair']}] 指标计算完成,列:{list(dataframe.columns)}")
return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
"""
入场逻辑:基于动态阈值和放宽的过滤条件
"""
conditions = []
if "&-price_value_divergence" in df.columns:
# 买入条件:价格低估且放量
buy_condition = (df["&-price_value_divergence"] < self.buy_threshold)
buy_condition &= (df["volume_z_score"] > 1.5) # 降低成交量要求
# RSI超卖过滤放宽条件
buy_condition &= (df["rsi"] < 40)
# 价格触及布林带下轨
buy_condition &= (df["close"] <= df["bb_lowerband"])
conditions.append(buy_condition)
else:
logger.warning("⚠️ &-price_value_divergence 列缺失,跳过该条件")
if len(conditions) > 0:
df.loc[reduce(lambda x, y: x & y, conditions), 'enter_long'] = 1
logger.info(f"[{metadata['pair']}] 入场信号触发,条件满足")
return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
"""
出场逻辑:基于动态阈值和放宽的过滤条件
"""
conditions = []
if "&-price_value_divergence" in df.columns:
# 卖出条件:价格高估
sell_condition = (df["&-price_value_divergence"] > self.sell_threshold)
sell_condition &= (df["volume_z_score"] > 1.5) # 降低成交量要求
# RSI超买过滤放宽条件
sell_condition |= (df["rsi"] > 60)
conditions.append(sell_condition)
else:
logger.warning("⚠️ &-price_value_divergence 列缺失,跳过该条件")
if len(conditions) > 0:
df.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1
logger.info(f"[{metadata['pair']}] 出场信号触发,条件满足")
return df
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_roi: Dict[float, float], max_profit: float):
"""
动态调整仓位持仓时间超过30分钟后强制平仓
"""
hold_time = (current_time - trade.open_date_utc).total_seconds() / 60
if hold_time > 30:
logger.info(f"[{trade.pair}] 持仓时间超过30分钟强制平仓")
return -1
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, **kwargs) -> bool:
"""
交易确认:确保没有快速重复交易
"""
recent_trades = Trade.get_trades(
pair=pair,
is_open=False,
close_date=current_time - datetime.timedelta(minutes=5)
).all()
if len(recent_trades) > 0:
logger.info(f"[{pair}] 5分钟内有近期交易跳过本次入场")
return False
return True
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, exit_reason: str,
current_time: datetime, **kwargs) -> bool:
"""
交易退出确认:记录退出原因
"""
logger.info(f"[{pair}] 退出交易,原因:{exit_reason}, 利润:{trade.calc_profit_ratio(rate):.2%}")
return True