策略 revert b4b647dc4 之后又进行优化, 纯freaai填充所有参数

This commit is contained in:
zhangkun9038@dingtalk.com 2025-05-22 15:39:48 +00:00
parent abbada318d
commit a117548d02
2 changed files with 104 additions and 370 deletions

2
.gitignore vendored
View File

@ -125,5 +125,5 @@ data/
!result/
tools/.env
result/
output.log
result/

View File

@ -4,47 +4,38 @@ from functools import reduce
import talib.abstract as ta
from pandas import DataFrame
from technical import qtpylib
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from pandas.core.dtypes.common import is_scalar
import pandas as pd
from freqtrade.strategy import IStrategy
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
"""
策略说明
- 所有交易信号由 FreqAI 动态预测
- 不使用 Hyperopt 优化任何参数
- 使用 FreqAI 提供的趋势和波动率信号进行交易决策
"""
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)
tema_period = IntParameter(low=5, high=20, default=9, space="buy", optimize=True, load=True)
plot_config = {
"main_plot": {},
"subplots": {
"Signals": {
"enter_long": {"color": "green"},
"exit_long": {"color": "red"}
},
"FreqAI Predictions": {
"&-buy_signal": {"color": "blue"},
"&-sell_signal": {"color": "orange"},
"&-volatility_forecast": {"color": "purple"}
}
}
}
freqai_info = {
"model": "LightGBMRegressorMultiTarget",
"model": "LightGBMClassifier",
"feature_parameters": {
"include_timeframes": ["5m", "15m", "1h"],
"include_corr_pairlist": [],
"label_period_candles": 12,
"label_period_candles": 24,
"include_shifted_candles": 3,
},
"data_split_parameters": {
@ -52,357 +43,100 @@ class FreqaiPrimer(IStrategy):
"shuffle": False,
},
"model_training_parameters": {
"n_estimators": 500,
"learning_rate": 0.01,
"num_leaves": 64,
"max_depth": 8,
"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 __init__(self, config: dict):
super().__init__(config)
self.cycle_trade_pair = None # 跟踪当前周期的下单币对
self.last_cycle_time = None # 记录上一个周期时间
def bot_loop_start(self, current_time, **kwargs) -> None:
"""
Reset cycle_trade_pair at the start of each new cycle.
"""
try:
# 检查是否进入新周期(基于 timeframe如 5m
if self.last_cycle_time is None or current_time >= self.last_cycle_time + pd.Timedelta(self.timeframe):
self.cycle_trade_pair = None
self.last_cycle_time = current_time
logger.debug(f"New cycle started at {current_time}, reset cycle_trade_pair")
except Exception as e:
logger.error(f"Error in bot_loop_start: {str(e)}")
def clean_dataframe(self, dataframe: DataFrame, columns: list = None) -> DataFrame:
try:
if columns:
valid_columns = [col for col in columns if col in dataframe.columns]
if not valid_columns:
logger.warning(f"None of the specified columns {columns} exist in DataFrame. Skipping cleaning.")
return dataframe
for col in valid_columns:
logger.debug(f"Cleaning column {col}: NaN count before = {dataframe[col].isna().sum()}")
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0)
logger.debug(f"NaN count after = {dataframe[col].isna().sum()}")
else:
logger.debug(f"Cleaning entire DataFrame: NaN count before = {dataframe.isna().sum().sum()}")
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
logger.debug(f"NaN count after = {dataframe.isna().sum().sum()}")
return dataframe
except Exception as e:
logger.error(f"Data cleaning failed: {str(e)}")
return dataframe # 返回原始 DataFrame避免策略崩溃
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
try:
dataframe = self.clean_dataframe(dataframe, columns=["close", "volume", "open", "high", "low"])
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 = self.clean_dataframe(dataframe, columns=["%-rsi-period", "%-mfi-period", "%-sma-period",
"%-ema-period", "%-adx-period", "bb_lowerband-period",
"bb_middleband-period", "bb_upperband-period",
"%-bb_width-period", "%-close-bb_lower-period",
"%-roc-period", "%-relative_volume-period"])
return dataframe
except Exception as e:
logger.error(f"Error in feature_engineering_expand_all for {metadata['pair']}: {str(e)}")
return dataframe
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
try:
dataframe = self.clean_dataframe(dataframe, columns=["close", "volume", "open", "high", "low"])
dataframe["%-pct-change"] = dataframe["close"].pct_change()
dataframe["%-raw_volume"] = dataframe["volume"]
dataframe["%-raw_price"] = dataframe["close"]
dataframe = self.clean_dataframe(dataframe, columns=["%-pct-change", "%-raw_volume", "%-raw_price"])
return dataframe
except Exception as e:
logger.error(f"Error in feature_engineering_expand_basic for {metadata['pair']}: {str(e)}")
return dataframe
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"]
def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
try:
dataframe = self.clean_dataframe(dataframe, columns=["close", "volume", "open", "high", "low"])
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).ffill().fillna(0)
dataframe = self.clean_dataframe(dataframe, columns=["%-day_of_week", "%-hour_of_day", "%-volatility"])
return dataframe
except Exception as e:
logger.error(f"Error in feature_engineering_standard for {metadata['pair']}: {str(e)}")
return dataframe
dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
try:
logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}")
if "close" not in dataframe.columns:
logger.error("数据框缺少必要的 'close'")
raise ValueError("数据框缺少必要的 'close'")
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).ffill().fillna(0)
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14).rolling(window=label_period).mean().ffill().bfill()
for col in ["&-buy_rsi", "%-volatility"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0)
if dataframe[col].isna().any():
logger.warning(f"目标列 {col} 仍包含 NaN数据预览\n{dataframe[col].tail(10)}")
logger.info(f"目标列预览:\n{dataframe[['&-buy_rsi']].head().to_string()}")
return dataframe
except Exception as e:
logger.error(f"创建 FreqAI 目标失败:{str(e)}")
return dataframe
"""
使用历史窗口预测未来趋势和波动率作为辅助信号
避免使用任何未来数据 shift(-N)
"""
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 1. 趋势强度:使用过去 N 根 K 线的收益率判断当前趋势
dataframe["&-trend_strength"] = dataframe["close"].pct_change(label_period)
# 2. 波动率预测:使用过去 N 根 K 线的价格变动绝对值
dataframe["&-volatility_forecast"] = dataframe["close"].pct_change(label_period).abs()
# 3. 动态 ROI 目标:基于趋势强度缩放
dataframe["&-roi_target"] = np.where(
dataframe["&-trend_strength"] > 0,
dataframe["&-trend_strength"] * 1.5,
0.01 # 最小 ROI
)
# 4. 动态止损目标:基于波动率缩放
dataframe["&-stoploss_target"] = -dataframe["&-volatility_forecast"] * 1.2
# 5. 市场状态识别:判断当前是震荡、趋势、超买还是超卖
if "bb_upperband-period" in dataframe.columns and "bb_lowerband-period" in dataframe.columns:
dataframe["&-market_condition"] = np.select([
(dataframe["close"] > dataframe["bb_upperband-period"]),
(dataframe["close"] < dataframe["bb_lowerband-period"]),
(dataframe["&-trend_strength"] > 0.03),
], [
"overbought",
"oversold",
"strong_up",
], default="sideways")
# 买入信号(必须存在)
dataframe["&-buy_signal"] = np.where(dataframe["&-trend_strength"] > 0.01, 1, 0)
# 卖出信号(必须存在)
dataframe["&-sell_signal"] = np.where(dataframe["&-trend_strength"] < -0.01, 1, 0)
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
try:
logger.debug(f"Pre-cleaning input DataFrame for {metadata['pair']}: columns={list(dataframe.columns)}")
dataframe = self.clean_dataframe(dataframe, columns=["close", "volume", "open", "high", "low"])
logger.info(f"Processing pair: {metadata['pair']}")
logger.debug(f"Input columns before FreqAI: {list(dataframe.columns)}, shape={dataframe.shape}")
# FreqAI 预测
dataframe = self.freqai.start(dataframe, metadata, self)
logger.debug(f"Columns after FreqAI: {list(dataframe.columns)}, shape={dataframe.shape}")
# 技术指标
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=self.tema_period.value)
dataframe["atr"] = ta.ATR(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=14)
# 标签生成
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
dataframe["up_or_down"] = np.where(
dataframe["close"].rolling(window=label_period).mean() > dataframe["close"], 1, 0
)
# FreqAI 预测后处理
if "&-buy_rsi" in dataframe.columns:
if "%-volatility" not in dataframe.columns:
logger.warning("Missing %-volatility column, regenerating")
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20, min_periods=1).std()
dataframe["%-volatility"] = dataframe["%-volatility"].replace([np.inf, -np.inf], 0).ffill().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)
dataframe = self.clean_dataframe(dataframe, columns=["&-buy_rsi", "&-sell_rsi", "&-stoploss", "&-roi_0", "%-volatility"])
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"Column {col} contains NaN values, filling with mean")
dataframe[col] = dataframe[col].ffill().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"Dynamic parameters: 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 column missing, skipping FreqAI prediction logic. Check model training or prediction files.")
dataframe["buy_rsi_pred"] = self.buy_rsi.default
dataframe["sell_rsi_pred"] = self.sell_rsi.default
dataframe["stoploss_pred"] = self.stoploss_param.default
dataframe["roi_0_pred"] = self.roi_0.default
dataframe["trailing_stop_positive"] = self.trailing_stop_positive_param.default
dataframe["trailing_stop_positive_offset"] = self.trailing_stop_positive_offset_param.default
# 清理生成的列
generated_columns = [
"rsi", "bb_lowerband", "bb_middleband", "bb_upperband", "tema", "atr",
"up_or_down", "buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred",
"trailing_stop_positive", "trailing_stop_positive_offset"
]
dataframe = self.clean_dataframe(dataframe, columns=generated_columns)
logger.info(f"up_or_down value counts:\n{dataframe['up_or_down'].value_counts().to_string()}")
logger.info(f"do_predict value counts:\n{dataframe['do_predict'].value_counts().to_string()}")
logger.debug(f"Final columns: {list(dataframe.columns)}, shape={dataframe.shape}")
return dataframe
except Exception as e:
logger.error(f"Error processing indicators for {metadata['pair']}: {str(e)}")
return dataframe # 返回未修改的 DataFrame避免中断
logger.info(f"[{metadata['pair']}] 当前可用列: {list(dataframe.columns)}")
dataframe = self.freqai.start(dataframe, metadata, self)
# FreqAI 提供的预测列
if "&-trend" in dataframe.columns:
dataframe["in_uptrend"] = dataframe["&-trend"] > 0.5
return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
try:
# 验证输入列
required_columns = ["rsi", "tema", "volume", "buy_rsi_pred", "do_predict", "bb_middleband", "close", "&-buy_rsi"]
missing_cols = [col for col in required_columns if col not in df.columns]
if missing_cols:
logger.error(f"Missing columns for {metadata['pair']}: {missing_cols}")
return df
conditions = []
# 处理 NaN
if df[required_columns].isna().any().any():
logger.warning(f"NaN values in {metadata['pair']}: {df[required_columns].isna().any()}")
df[required_columns] = df[required_columns].ffill().fillna(0)
if "&-buy_signal" in df.columns:
conditions.append(df["&-buy_signal"] > 0.5)
else:
logger.warning("⚠️ &-buy_signal 列缺失,跳过该条件")
# 计算 entry_score
df["entry_score"] = (
(50 - df["rsi"]) * 0.5 + # RSI 超卖权重
(df["volume"] / df["volume"].rolling(20, min_periods=1).mean()) * 0.3 + # 成交量活跃
df["&-buy_rsi"] * 0.2 # FreqAI 预测
)
# 确保 entry_score 是标量
if not df["entry_score"].apply(is_scalar).all():
logger.error(f"Non-scalar values in entry_score for {metadata['pair']}: {df['entry_score'].head()}")
df["entry_score"] = df["entry_score"].apply(lambda x: x[0] if isinstance(x, (list, np.ndarray)) else x).astype(float)
# 买入条件(优化为 5 个,触发率 0.35%-0.42%
enter_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]), # RSI 上穿 FreqAI 预测
df["tema"] > df["tema"].shift(1), # TEMA 上行(趋势确认)
df["volume"] > df["volume"].rolling(20, min_periods=1).mean() * 1.1, # 成交量略高于均量
df["do_predict"] == 1, # FreqAI 预测有效
df["rsi"] < 45 # RSI 放宽到 < 45
]
# 合并条件
if enter_long_conditions:
condition = reduce(lambda x, y: x & y, enter_long_conditions)
df.loc[condition, "enter_long"] = 1
df.loc[condition, "enter_tag"] = "long"
df.loc[condition, "entry_score"] = df.loc[condition, "entry_score"]
df["enter_long"] = df["enter_long"].fillna(0).astype(int)
df["enter_tag"] = df["enter_tag"].fillna("")
df["entry_score"] = df["entry_score"].fillna(0.0).astype(float)
else:
logger.warning(f"No valid entry conditions for {metadata['pair']}")
df["enter_long"] = 0
df["enter_tag"] = ""
df["entry_score"] = 0.0
logger.debug(f"Entry signals for {metadata['pair']}: {df['enter_long'].sum()} signals")
return df
except Exception as e:
logger.error(f"Error in populate_entry_trend for {metadata['pair']}: {str(e)}")
if len(conditions) == 0:
return df
df.loc[reduce(lambda x, y: x & y, conditions), 'enter_long'] = 1
return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
try:
exit_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]),
(df["close"] < df["close"].shift(1) * 0.97) | (df["rsi"] > 80),
df["volume"] > 0,
(df["do_predict"] == 1) | (df["do_predict"] == 0),
(df["up_or_down"] == 0) | (df["rsi"] > 70)
]
if exit_long_conditions:
df.loc[
reduce(lambda x, y: x & y, exit_long_conditions),
"exit_long"
] = 1
return df
except Exception as e:
logger.error(f"Error in populate_exit_trend for {metadata['pair']}: {str(e)}")
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":
# 检查当前周期是否已有下单
if self.cycle_trade_pair is not None:
logger.debug(f"周期内已有下单 {self.cycle_trade_pair},拒绝 {pair} 的买入")
return False
# 收集所有交易对的信号和分数
signals = []
for p in self.config["exchange"]["pair_whitelist"]:
p_df, _ = self.dp.get_analyzed_dataframe(p, self.timeframe)
if p_df is not None and not p_df.empty and "enter_long" in p_df.columns:
if p_df["enter_long"].iloc[-1] == 1:
score = p_df["entry_score"].iloc[-1] if "entry_score" in p_df.columns else -float('inf')
signals.append((p, score))
# 如果有信号,选择得分最高的币对
if signals:
signals = sorted(signals, key=lambda x: x[1], reverse=True)
top_pair, top_score = signals[0]
if pair != top_pair:
logger.debug(f"{pair} 不是最高得分币对 {top_pair} (score={top_score:.2f}),拒绝买入")
return False
else:
logger.debug(f"无有效信号,拒绝 {pair} 的买入")
return False
# 滑点检查
max_rate = last_candle["close"] * (1 + 0.0025) # 0.25% 滑点阈值
if rate > max_rate:
logger.debug(f"拒绝 {pair} 的买入,价格 {rate} 超过最大允许价格 {max_rate}")
return False
# 记录下单币对
self.cycle_trade_pair = pair
logger.info(f"确认 {pair} 的买入,周期内最优信号 (score={last_candle['entry_score']:.2f})")
return True
elif side == "short":
logger.warning(f"{pair} 尝试做空,但策略不支持做空 (can_short={self.can_short})")
return False
except Exception as e:
logger.error(f"确认 {pair} 交易时出错:{str(e)}")
return False
conditions = [
df["&-sell_signal"] > 0.5,
]
df.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1
return df