myTestFreqAI/user_data/strategies/freqai_primer_ml.py
zhangkun9038@dingtalk.com 5103976971 update ignore
2025-11-27 08:02:32 +08:00

223 lines
8.2 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 pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter
import pandas_ta as ta
logger = logging.getLogger(__name__)
class FreqaiPrimerML(IStrategy):
"""
基于 FreqAI ML 的交易策略 - 单目标回归模型
预测未来价格上升幅度,用于入场决策
"""
# FreqAI 配置 - 直接在类中定义
freqai_info = {
"identifier": "freqai_primer_ml",
"model": "LightGBMRegressor",
"feature_parameters": {
"include_timeframes": ["3m", "15m", "1h"],
"include_shifted_candles": 3,
"label_period_candles": 6,
},
"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,
"live_retrain_candles": 100,
}
# 策略参数
stoploss = -0.15
trailing_stop = True
trailing_stop_positive_offset = 0.005
timeframe = "3m"
can_short = False
# 可优化参数
bb_std = DecimalParameter(2.0, 5.0, decimals=1, default=3.5, optimize=True, space='buy')
rsi_length = IntParameter(10, 30, default=14, optimize=True, space='buy')
rsi_oversold = IntParameter(20, 50, default=30, optimize=True, space='buy')
def informative_pairs(self):
pairs = self.dp.current_whitelist()
return [(pair, '15m') for pair in pairs] + [(pair, '1h') for pair in pairs]
def feature_engineering_expand_all(self, dataframe: DataFrame, period, metadata, **kwargs) -> DataFrame:
"""
为 FreqAI 生成扩展特征
"""
# RSI 特征
dataframe[f"%-rsi-{period}"] = ta.rsi(dataframe["close"], length=period)
# 成交量特征
dataframe[f"%-volume_ratio-{period}"] = dataframe["volume"] / dataframe["volume"].rolling(period).mean()
# ADX 趋势强度
adx_result = ta.adx(dataframe["high"], dataframe["low"], dataframe["close"], length=period)
dataframe[f"%-adx-{period}"] = adx_result[f"ADX_{period}"]
# 价格位置特征
low = dataframe["low"].rolling(period).min()
high = dataframe["high"].rolling(period).max()
dataframe[f"%-price_position-{period}"] = (dataframe["close"] - low) / (high - low + 1e-8)
return dataframe
def feature_engineering_standard(self, dataframe: DataFrame, metadata, **kwargs) -> DataFrame:
"""
标准特征(不需要自动扩展)
"""
# 小时时间特征
dataframe["%-hour_of_day"] = (dataframe["date"].dt.hour + 1) / 25
# ATR 波动性
atr_14 = ta.atr(dataframe["high"], dataframe["low"], dataframe["close"], length=14)
dataframe["%-atr_14"] = atr_14 / dataframe["close"]
# EMA 比率
ema_5 = ta.ema(dataframe["close"], length=5)
ema_10 = ta.ema(dataframe["close"], length=10)
dataframe["%-ema_ratio"] = ema_5 / (ema_10 + 1e-8)
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata, **kwargs) -> DataFrame:
"""
为 FreqAI 生成回归目标:预测未来价格上升幅度
"""
pair = metadata.get('pair', 'Unknown')
# 获取标签周期
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 计算未来 label_period 根 K 线内的最大上升幅度
dataframe["&-price_rise"] = 0.0
for i in range(len(dataframe) - label_period):
future_high = dataframe["high"].iloc[i:i+label_period].max()
current_price = dataframe["close"].iloc[i]
# 计算上升幅度(百分比)
max_rise_pct = (future_high - current_price) / current_price if current_price != 0 else 0
dataframe.loc[i, "&-price_rise"] = max(0, max_rise_pct) # 只记录上升下跌记为0
# 平滑处理
dataframe["&-price_rise"] = dataframe["&-price_rise"].rolling(3).mean().fillna(0)
logger.debug(f"[{pair}] 目标标签统计: 均值={dataframe['&-price_rise'].mean():.6f}, "
f"最小={dataframe['&-price_rise'].min():.6f}, "
f"最大={dataframe['&-price_rise'].max():.6f}")
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
主指标计算函数 - FreqAI 在最开始调用
"""
pair = metadata.get('pair', 'Unknown')
# 🔧 第一步:调用 FreqAI 进行预测和训练
if hasattr(self, 'freqai') and self.freqai is not None:
dataframe = self.freqai.start(dataframe, metadata, self)
logger.debug(f"[{pair}] FreqAI 预测已生成,列: {list(dataframe.columns)}")
else:
logger.warning(f"[{pair}] FreqAI 未初始化")
# 计算基础指标
rsi_length = self.rsi_length.value
bb_std = self.bb_std.value
bb_length = 40
# 布林带
bb = ta.bbands(dataframe['close'], length=bb_length, std=bb_std)
dataframe['bb_lower'] = bb[f'BBL_{bb_length}_{bb_std}']
dataframe['bb_upper'] = bb[f'BBU_{bb_length}_{bb_std}']
dataframe['bb_middle'] = bb[f'BBM_{bb_length}_{bb_std}']
# RSI
dataframe['rsi'] = ta.rsi(dataframe['close'], length=rsi_length)
# EMA
dataframe['ema_50'] = ta.ema(dataframe['close'], length=50)
dataframe['ema_200'] = ta.ema(dataframe['close'], length=200)
# 成交量均线
dataframe['volume_ma'] = dataframe['volume'].rolling(20).mean()
# MACD
macd = ta.macd(dataframe['close'])
dataframe['macd'] = macd['MACD_12_26_9']
dataframe['macd_signal'] = macd['MACDs_12_26_9']
# 填充缺失值
dataframe = dataframe.fillna(0).bfill()
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
入场逻辑 - 使用 ML 预测
"""
conditions = []
# 条件1: 价格接近下轨
conditions.append(dataframe['close'] <= dataframe['bb_lower'] * 1.02)
# 条件2: RSI 超卖
conditions.append(dataframe['rsi'] < self.rsi_oversold.value)
# 条件3: MACD 底部
conditions.append(dataframe['macd'] < dataframe['macd_signal'])
# 条件4: EMA 多头排列
conditions.append(dataframe['ema_50'] > dataframe['ema_200'])
# 传统条件
traditional_signal = (conditions[0] & conditions[1] & conditions[2] & conditions[3])
# ML 增强:使用 FreqAI 预测的价格上升幅度
if '&-price_rise' in dataframe.columns:
ml_signal = dataframe['&-price_rise'] > 0.01 # 预测上升 > 1%
final_signal = traditional_signal & ml_signal
logger.debug(f"[{metadata['pair']}] 使用 ML 增强的入场信号")
else:
final_signal = traditional_signal
logger.debug(f"[{metadata['pair']}] 仅使用传统入场信号ML 未初始化)")
dataframe.loc[final_signal, 'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
出场逻辑
"""
conditions = []
# 条件1: 价格突破上轨
conditions.append(dataframe['close'] >= dataframe['bb_upper'] * 0.99)
# 条件2: RSI 超买
conditions.append(dataframe['rsi'] > 70)
# 条件3: MACD 下降
conditions.append(dataframe['macd'] > dataframe['macd_signal'])
# 至少满足 2 个条件
condition_count = conditions[0].astype(int) + conditions[1].astype(int) + conditions[2].astype(int)
final_signal = condition_count >= 2
dataframe.loc[final_signal, 'exit_long'] = 1
return dataframe