myTestFreqAI/freqtrade/templates/freqprimer.py_origin
2025-05-21 14:36:23 +00:00

280 lines
14 KiB
Plaintext
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
from functools import reduce
import talib.abstract as ta
from pandas import DataFrame
from technical import qtpylib
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
logger = logging.getLogger(__name__)
class FreqaiPrimer(IStrategy):
minimal_roi = {
0: 0.135,
9: 0.052,
15: 0.007,
60: 0
}
stoploss = -0.263
trailing_stop = True
trailing_stop_positive = 0.324
trailing_stop_positive_offset = 0.411
trailing_only_offset_is_reached = False
max_open_trades = 4
process_only_new_candles = True
use_exit_signal = True
startup_candle_count: int = 40
can_short = False
buy_rsi = IntParameter(low=10, high=50, default=30, space="buy", optimize=False, load=True)
sell_rsi = IntParameter(low=50, high=90, default=70, space="sell", optimize=False, load=True)
roi_0 = DecimalParameter(low=0.01, high=0.2, default=0.135, space="roi", optimize=True, load=True)
roi_15 = DecimalParameter(low=0.005, high=0.1, default=0.052, space="roi", optimize=True, load=True)
roi_30 = DecimalParameter(low=0.001, high=0.05, default=0.007, space="roi", optimize=True, load=True)
stoploss_param = DecimalParameter(low=-0.35, high=-0.1, default=-0.263, space="stoploss", optimize=True, load=True)
trailing_stop_positive_param = DecimalParameter(low=0.1, high=0.5, default=0.324, space="trailing", optimize=True, load=True)
trailing_stop_positive_offset_param = DecimalParameter(low=0.2, high=0.6, default=0.411, space="trailing", optimize=True, load=True)
freqai_info = {
"model": "LightGBMRegressor",
"feature_parameters": {
"include_timeframes": ["5m", "15m", "1h"],
"include_corr_pairlist": [],
"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,
},
}
plot_config = {
"main_plot": {},
"subplots": {
"&-buy_rsi": {"&-buy_rsi": {"color": "green"}},
"&-sell_rsi": {"&-sell_rsi": {"color": "red"}},
"&-stoploss": {"&-stoploss": {"color": "purple"}},
"&-roi_0": {"&-roi_0": {"color": "orange"}},
"do_predict": {"do_predict": {"color": "brown"}},
},
}
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period)
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2)
dataframe["bb_lowerband-period"] = bollinger["lower"]
dataframe["bb_middleband-period"] = bollinger["mid"]
dataframe["bb_upperband-period"] = bollinger["upper"]
dataframe["%-bb_width-period"] = (
dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]
) / dataframe["bb_middleband-period"]
dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"]
dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
dataframe["%-relative_volume-period"] = (
dataframe["volume"] / dataframe["volume"].rolling(period).mean()
)
dataframe = dataframe.replace([np.inf, -np.inf], 0)
dataframe = dataframe.ffill()
dataframe = dataframe.fillna(0)
return dataframe
def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
dataframe["%-pct-change"] = dataframe["close"].pct_change()
dataframe["%-raw_volume"] = dataframe["volume"]
dataframe["%-raw_price"] = dataframe["close"]
dataframe = dataframe.replace([np.inf, -np.inf], 0)
dataframe = dataframe.ffill()
dataframe = dataframe.fillna(0)
return dataframe
def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
if len(dataframe["close"]) < 20:
logger.warning(f"数据不足 {len(dataframe)} 根 K 线,%-volatility 可能不完整")
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20, min_periods=1).std()
dataframe["%-volatility"] = dataframe["%-volatility"].replace([np.inf, -np.inf], 0)
dataframe["%-volatility"] = dataframe["%-volatility"].ffill()
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}")
if "close" not in dataframe.columns:
logger.error("数据框缺少必要的 'close' 列")
raise ValueError("数据框缺少必要的 'close' 列")
try:
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
if "%-volatility" not in dataframe.columns:
logger.warning("缺少 %-volatility 列,强制重新生成")
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20, min_periods=1).std()
dataframe["%-volatility"] = dataframe["%-volatility"].replace([np.inf, -np.inf], 0)
dataframe["%-volatility"] = dataframe["%-volatility"].ffill()
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
# 移除 shift(-label_period),改为使用当前及过去的数据
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["&-buy_rsi"] = dataframe["&-buy_rsi"].rolling(window=label_period).mean().ffill().bfill()
for col in ["&-buy_rsi", "%-volatility"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
dataframe[col] = dataframe[col].ffill()
dataframe[col] = dataframe[col].fillna(0)
if dataframe[col].isna().any():
logger.warning(f"目标列 {col} 仍包含 NaN数据预览\n{dataframe[col].tail(10)}")
except Exception as e:
logger.error(f"创建 FreqAI 目标失败:{str(e)}")
raise
logger.info(f"目标列预览:\n{dataframe[['&-buy_rsi']].head().to_string()}")
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
logger.info(f"处理交易对:{metadata['pair']}")
logger.debug(f"输入特征列:{list(dataframe.columns)}")
dataframe = self.freqai.start(dataframe, metadata, self)
logger.debug(f"FreqAI 输出特征列:{list(dataframe.columns)}")
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe["bb_lowerband"] = bollinger["lower"]
dataframe["bb_middleband"] = bollinger["mid"]
dataframe["bb_upperband"] = bollinger["upper"]
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 使用滚动窗口而非未来函数来生成 up_or_down 列
dataframe["up_or_down"] = np.where(
dataframe["close"].rolling(window=label_period).mean() > dataframe["close"], 1, 0
)
if "&-buy_rsi" in dataframe.columns:
if "%-volatility" not in dataframe.columns:
logger.warning("缺少 %-volatility 列,强制重新生成")
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20, min_periods=1).std()
dataframe["%-volatility"] = dataframe["%-volatility"].replace([np.inf, -np.inf], 0)
dataframe["%-volatility"] = dataframe["%-volatility"].ffill()
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30
dataframe["&-stoploss"] = self.stoploss - (dataframe["%-volatility"] * 5).clip(-0.05, 0.05)
dataframe["&-roi_0"] = (dataframe["close"].rolling(window=label_period).mean() / dataframe["close"] - 1).clip(0, 0.2)
for col in ["&-buy_rsi", "&-sell_rsi", "&-stoploss", "&-roi_0"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
dataframe[col] = dataframe[col].ffill()
dataframe[col] = dataframe[col].fillna(0)
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5).mean().clip(10, 50)
dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5).mean().clip(50, 90)
dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.35, -0.1)
dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2)
for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred"]:
if dataframe[col].isna().any():
logger.warning(f"列 {col} 包含 NaN填充为默认值")
dataframe[col] = dataframe[col].ffill()
dataframe[col] = dataframe[col].fillna(dataframe[col].mean())
dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3)
dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.75).clip(0.02, 0.4)
self.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1])
self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1])
self.stoploss = float(self.stoploss_param.value)
self.minimal_roi = {
0: float(self.roi_0.value),
15: float(self.roi_15.value),
30: float(self.roi_30.value),
60: 0
}
self.trailing_stop_positive = float(self.trailing_stop_positive_param.value)
self.trailing_stop_positive_offset = float(self.trailing_stop_positive_offset_param.value)
logger.info(f"动态参数buy_rsi={self.buy_rsi.value}, sell_rsi={self.sell_rsi.value}, "
f"stoploss={self.stoploss}, trailing_stop_positive={self.trailing_stop_positive}")
else:
logger.warning(f"&-buy_rsi 列缺失,跳过 FreqAI 预测逻辑,检查 freqai.start 输出")
dataframe = dataframe.replace([np.inf, -np.inf], 0)
dataframe = dataframe.ffill()
dataframe = dataframe.fillna(0)
logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}")
logger.info(f"do_predict 值统计:\n{dataframe['do_predict'].value_counts().to_string()}")
logger.debug(f"最终特征列:{list(dataframe.columns)}")
return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
enter_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]),
df["tema"] > df["tema"].shift(1),
df["volume"] > 0,
df["do_predict"] == 1,
df["up_or_down"] == 1
]
if enter_long_conditions:
df.loc[
reduce(lambda x, y: x & y, enter_long_conditions),
["enter_long", "enter_tag"]
] = (1, "long")
return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
exit_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]),
(df["close"] < df["close"].shift(1) * 0.97),
df["volume"] > 0,
df["do_predict"] == 1,
df["up_or_down"] == 0
]
if exit_long_conditions:
df.loc[
reduce(lambda x, y: x & y, exit_long_conditions),
"exit_long"
] = 1
return df
def confirm_trade_entry(
self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time, entry_tag, side: str, **kwargs
) -> bool:
try:
df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if df is None or df.empty:
logger.warning(f"无法获取 {pair} 的分析数据,拒绝交易")
return False
last_candle = df.iloc[-1].squeeze()
if "close" not in last_candle or np.isnan(last_candle["close"]):
logger.warning(f"{pair} 的最新 K 线缺少有效 close 价格,拒绝交易")
return False
if side == "long":
max_rate = last_candle["close"] * (1 + 0.0025) # 0.25% 滑点阈值
if rate > max_rate:
logger.debug(f"拒绝 {pair} 的买入,价格 {rate} 超过最大允许价格 {max_rate}")
return False
elif side == "short":
logger.warning(f"{pair} 尝试做空,但策略不支持做空 (can_short={self.can_short})")
return False
logger.debug(f"确认 {pair} 的交易side={side}, rate={rate}, close={last_candle['close']}")
return True
except Exception as e:
logger.error(f"确认 {pair} 交易时出错:{str(e)}")
return False