add logs
This commit is contained in:
parent
af12fe6b25
commit
db95f4bb58
@ -8,33 +8,33 @@
|
||||
"max_open_trades": 5
|
||||
},
|
||||
"buy": {
|
||||
"ADD_POSITION_THRESHOLD": -0.028,
|
||||
"BUY_THRESHOLD_MAX": -0.003,
|
||||
"BUY_THRESHOLD_MIN": -0.028,
|
||||
"COOLDOWN_PERIOD_MINUTES": 4,
|
||||
"ADD_POSITION_THRESHOLD": -0.01,
|
||||
"BUY_THRESHOLD_MAX": -0.017,
|
||||
"BUY_THRESHOLD_MIN": -0.044,
|
||||
"COOLDOWN_PERIOD_MINUTES": 10,
|
||||
"MAX_ENTRY_POSITION_ADJUSTMENT": 2
|
||||
},
|
||||
"sell": {
|
||||
"EXIT_POSITION_RATIO": 0.335,
|
||||
"SELL_THRESHOLD_MAX": 0.054,
|
||||
"SELL_THRESHOLD_MIN": 0.001,
|
||||
"TRAILING_STOP_DISTANCE": 0.005,
|
||||
"TRAILING_STOP_START": 0.048
|
||||
"EXIT_POSITION_RATIO": 0.377,
|
||||
"SELL_THRESHOLD_MAX": 0.049,
|
||||
"SELL_THRESHOLD_MIN": 0.002,
|
||||
"TRAILING_STOP_DISTANCE": 0.018,
|
||||
"TRAILING_STOP_START": 0.044
|
||||
},
|
||||
"protection": {},
|
||||
"roi": {
|
||||
"0": 0.156,
|
||||
"20": 0.057999999999999996,
|
||||
"50": 0.018,
|
||||
"113": 0
|
||||
"0": 0.20099999999999998,
|
||||
"9": 0.051,
|
||||
"16": 0.007,
|
||||
"69": 0
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.172,
|
||||
"trailing_stop_positive_offset": 0.23199999999999998,
|
||||
"trailing_only_offset_is_reached": false
|
||||
"trailing_stop_positive": 0.106,
|
||||
"trailing_stop_positive_offset": 0.126,
|
||||
"trailing_only_offset_is_reached": true
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2025-06-23 00:37:56.498155+00:00"
|
||||
"export_time": "2025-06-23 06:56:55.803622+00:00"
|
||||
}
|
||||
@ -104,6 +104,14 @@ class FreqaiPrimer(IStrategy):
|
||||
self.stats_logged = False
|
||||
self.fit_live_predictions_candles = self.freqai_info.get("fit_live_predictions_candles", 100)
|
||||
self.last_entry_time = {} # 记录每个币种的最后入场时间
|
||||
self.indicator_cache = {} # 缓存技术指标用于日志记录
|
||||
|
||||
# 设置交易专用日志
|
||||
trade_log_handler = logging.FileHandler("trade_log.jsonl")
|
||||
trade_log_handler.setFormatter(logging.Formatter("%(asctime)s - %(message)s"))
|
||||
self.trade_logger = logging.getLogger("trade_logger")
|
||||
self.trade_logger.setLevel(logging.INFO)
|
||||
self.trade_logger.addHandler(trade_log_handler)
|
||||
|
||||
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
|
||||
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
|
||||
@ -154,11 +162,6 @@ class FreqaiPrimer(IStrategy):
|
||||
|
||||
return dataframe
|
||||
|
||||
@staticmethod
|
||||
def linear_map(value, from_min, from_max, to_min, to_max):
|
||||
return (value - from_min) / (from_max - from_min) * (to_max - to_min) + to_min
|
||||
|
||||
# 在 populate_indicators 中修改市场趋势相关逻辑
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
pair = metadata.get('pair', 'Unknown')
|
||||
logger.info(f"[{pair}] 当前可用列(调用FreqAI前):{list(dataframe.columns)}")
|
||||
@ -195,10 +198,10 @@ class FreqaiPrimer(IStrategy):
|
||||
|
||||
# 添加调试日志
|
||||
logger.debug(f"[{pair}] 最新数据 - close:{dataframe['close'].iloc[-1]:.6f}, "
|
||||
f"rsi:{dataframe['rsi'].iloc[-1]:.2f}, "
|
||||
f"&-price_value_divergence:{dataframe['&-price_value_divergence'].iloc[-1]:.6f}, "
|
||||
f"volume_z_score:{dataframe['volume_z_score'].iloc[-1]:.2f}, "
|
||||
f"bb_lowerband:{dataframe['bb_lowerband'].iloc[-1]:.6f}")
|
||||
f"rsi:{dataframe['rsi'].iloc[-1]:.2f}, "
|
||||
f"&-price_value_divergence:{dataframe['&-price_value_divergence'].iloc[-1]:.6f}, "
|
||||
f"volume_z_score:{dataframe['volume_z_score'].iloc[-1]:.2f}, "
|
||||
f"bb_lowerband:{dataframe['bb_lowerband'].iloc[-1]:.6f}")
|
||||
|
||||
# 获取 labels_mean 和 labels_std
|
||||
labels_mean = None
|
||||
@ -239,10 +242,22 @@ class FreqaiPrimer(IStrategy):
|
||||
labels_std = 0.01
|
||||
logger.warning(f"[{pair}] labels_std 计算异常,使用默认值 0.01")
|
||||
|
||||
# 根据市场趋势得分动态调整买卖阈值
|
||||
market_trend_score = self.get_market_trend()
|
||||
k_buy = FreqaiPrimer.linear_map(market_trend_score, 0, 100, 1.2, 0.8)
|
||||
k_sell = FreqaiPrimer.linear_map(market_trend_score, 0, 100, 1.5, 1.0)
|
||||
# 根据市场趋势动态调整买卖阈值
|
||||
market_trend = self.get_market_trend()
|
||||
k_buy = 1.0
|
||||
k_sell = 1.2
|
||||
if market_trend == 'bull':
|
||||
k_buy = 0.8 # 放宽买入阈值
|
||||
k_sell = 1.0 # 收紧卖出阈值
|
||||
elif market_trend == 'bear':
|
||||
k_buy = 1.2 # 收紧买入阈值
|
||||
k_sell = 1.5 # 放宽卖出阈值
|
||||
else:
|
||||
k_buy = 1.0
|
||||
k_sell = 1.2
|
||||
|
||||
if labels_mean > 0.015:
|
||||
k_sell += 0.5
|
||||
|
||||
self.buy_threshold = labels_mean - k_buy * labels_std
|
||||
self.sell_threshold = labels_mean + k_sell * labels_std
|
||||
@ -253,7 +268,7 @@ class FreqaiPrimer(IStrategy):
|
||||
self.sell_threshold = min(self.sell_threshold, self.SELL_THRESHOLD_MAX.value)
|
||||
self.sell_threshold = max(self.sell_threshold, self.SELL_THRESHOLD_MIN.value)
|
||||
|
||||
logger.info(f"[{pair}] 市场趋势得分:{market_trend_score}, labels_mean:{labels_mean:.4f}, labels_std:{labels_std:.4f}")
|
||||
logger.info(f"[{pair}] 市场趋势:{market_trend}, labels_mean:{labels_mean:.4f}, labels_std:{labels_std:.4f}")
|
||||
logger.info(f"[{pair}] k_buy:{k_buy:.2f}, k_sell:{k_sell:.2f}")
|
||||
logger.info(f"[{pair}] 动态买入阈值:{self.buy_threshold:.4f}, 卖出阈值:{self.sell_threshold:.4f}")
|
||||
|
||||
@ -319,6 +334,13 @@ class FreqaiPrimer(IStrategy):
|
||||
combined_condition = reduce(lambda x, y: x & y, conditions)
|
||||
if combined_condition.any():
|
||||
dataframe.loc[combined_condition, 'enter_long'] = 1
|
||||
# 缓存指标用于交易日志
|
||||
self.indicator_cache[pair] = {
|
||||
"rsi": dataframe["rsi"].iloc[-1],
|
||||
"volume_z_score": dataframe["volume_z_score"].iloc[-1],
|
||||
"&-price_value_divergence": dataframe["&-price_value_divergence"].iloc[-1],
|
||||
"bb_lowerband": dataframe["bb_lowerband"].iloc[-1]
|
||||
}
|
||||
logger.debug(f"[{pair}] 入场信号触发,条件满足")
|
||||
else:
|
||||
logger.debug(f"[{pair}] 买入条件均不满足,未触发入场信号")
|
||||
@ -344,8 +366,19 @@ class FreqaiPrimer(IStrategy):
|
||||
logger.warning(f"[{pair}] ⚠️ &-price_value_divergence 列缺失,跳过该条件")
|
||||
|
||||
if len(conditions) > 0:
|
||||
dataframe.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1
|
||||
logger.debug(f"[{pair}] 出场信号触发,条件满足")
|
||||
combined_condition = reduce(lambda x, y: x & y, conditions)
|
||||
if combined_condition.any():
|
||||
dataframe.loc[combined_condition, 'exit_long'] = 1
|
||||
# 缓存指标用于交易日志
|
||||
self.indicator_cache[pair] = {
|
||||
"rsi": dataframe["rsi"].iloc[-1],
|
||||
"volume_z_score": dataframe["volume_z_score"].iloc[-1],
|
||||
"&-price_value_divergence": dataframe["&-price_value_divergence"].iloc[-1],
|
||||
"bb_upperband": dataframe["bb_upperband"].iloc[-1]
|
||||
}
|
||||
logger.debug(f"[{pair}] 出场信号触发,条件满足")
|
||||
else:
|
||||
logger.debug(f"[{pair}] 卖出条件均不满足,未触发出场信号")
|
||||
else:
|
||||
logger.debug(f"[{pair}] 无有效卖出条件")
|
||||
|
||||
@ -374,226 +407,238 @@ class FreqaiPrimer(IStrategy):
|
||||
current_entry_profit: float, current_exit_profit: float,
|
||||
**kwargs) -> float | None | tuple[float | None, str | None]:
|
||||
"""
|
||||
动态调整仓位:支持加仓、减仓、追踪止损和最大持仓时间限制
|
||||
动态调整仓位:支持加仓和分批减仓
|
||||
参数:
|
||||
- trade: 当前交易对象
|
||||
- current_time: 当前时间
|
||||
- current_rate: 当前价格
|
||||
- current_profit: 当前总盈利
|
||||
- min_stake: 最小下注金额
|
||||
- max_stake: 最大下注金额
|
||||
- current_entry_rate: 当前入场价格
|
||||
- current_exit_rate: 当前退出价格
|
||||
- current_entry_profit: 当前入场盈利
|
||||
- current_exit_profit: 当前退出盈利
|
||||
返回:
|
||||
- 调整金额(正数为加仓,负数为减仓)或 None
|
||||
trade: 当前交易对象
|
||||
current_time: 当前时间
|
||||
current_rate: 当前市场价格
|
||||
current_profit: 当前未实现利润百分比
|
||||
min_stake: 最小下单金额(可能为 None)
|
||||
max_stake: 最大可用下单金额
|
||||
current_entry_rate: 当前入场价格
|
||||
current_exit_rate: 当前退出价格
|
||||
current_entry_profit: 当前入场利润百分比
|
||||
current_exit_profit: 当前退出利润百分比
|
||||
返回值:
|
||||
- 正数(float):加仓金额(报价货币,例如 USDT)
|
||||
- 负数(float):减仓金额(例如 -30.0 表示卖出 30 USDT)
|
||||
- None:不调整仓位
|
||||
- tuple[float | None, str | None]:(调整金额, 调整原因)
|
||||
"""
|
||||
pair = trade.pair
|
||||
dataframe = self.dp.get_pair_dataframe(pair, self.timeframe)
|
||||
trend_score = self.get_market_trend(dataframe=dataframe, metadata={'pair': pair})
|
||||
hold_time = (current_time - trade.open_date_utc).total_seconds() / 60
|
||||
market_trend = self.get_market_trend()
|
||||
profit_ratio = (current_rate - trade.open_rate) / trade.open_rate
|
||||
|
||||
initial_stake_amount = trade.stake_amount / 3
|
||||
logger.debug(f"{pair} 首次入场金额: {initial_stake_amount:.2f}, 当前持仓金额: {trade.stake_amount:.2f}, "
|
||||
f"加仓次数: {trade.nr_of_successful_entries - 1}")
|
||||
|
||||
# 动态最大持仓时间
|
||||
# max_hold_time = self.MAX_HOLD_TIME_MINUTES.value
|
||||
# # 线性映射持仓时间因子
|
||||
# hold_factor = self.HOLD_TIME_REDUCTION.value + (self.HOLD_TIME_EXTENSION.value - self.HOLD_TIME_REDUCTION.value) * (trend_score / 100)
|
||||
# max_hold_time *= hold_factor
|
||||
# if profit_ratio > 0.02:
|
||||
# max_hold_time *= 1.2
|
||||
# elif profit_ratio < -0.02:
|
||||
# max_hold_time *= 0.8
|
||||
# max_hold_time = int(max_hold_time)
|
||||
|
||||
# 加仓逻辑
|
||||
# --- 加仓逻辑 ---
|
||||
max_entry_adjustments = self.MAX_ENTRY_POSITION_ADJUSTMENT.value
|
||||
if trade.nr_of_successful_entries <= max_entry_adjustments + 1:
|
||||
if trade.nr_of_successful_entries <= max_entry_adjustments + 1: # +1 包括初始入场
|
||||
add_position_threshold = self.ADD_POSITION_THRESHOLD.value
|
||||
# 线性映射加仓阈值,趋势值越高,加仓越严格
|
||||
add_threshold = 80 - 30 * (trend_score / 100) # 趋势值 100 -> 50, 0 -> 80
|
||||
if profit_ratio <= add_position_threshold and hold_time > 5 and trend_score <= add_threshold:
|
||||
logger.debug(f"{pair} 初始下注金额: {initial_stake_amount:.2f}, trend_score: {trend_score:.2f}, add_threshold: {add_threshold} ")
|
||||
|
||||
# 计算加仓金额
|
||||
add_count = trade.nr_of_successful_entries - 1
|
||||
multipliers = [2, 4, 8]
|
||||
if add_count < len(multipliers):
|
||||
multiplier = multipliers[add_count]
|
||||
add_amount = initial_stake_amount * multiplier
|
||||
logger.debug(f"{pair} 第 {add_count + 1} 次加仓,倍数={multiplier}, "
|
||||
f"金额 = {initial_stake_amount:.2f} * {multiplier} = {add_amount:.2f}")
|
||||
logger.debug(f"{pair} 加仓计算: 第 {add_count + 1} 次加仓,倍数={multiplier}, "
|
||||
f"金额 = {initial_stake_amount:.2f} * {multiplier} = {add_amount:.2f}")
|
||||
|
||||
if profit_ratio <= add_position_threshold and hold_time > 5:
|
||||
if market_trend in ['bear', 'sideways']: # 熊市或震荡市更倾向加仓
|
||||
add_amount = 0.5 * trade.stake_amount # 加仓 50% 的初始仓位
|
||||
if min_stake is not None and add_amount < min_stake:
|
||||
logger.warning(f"{pair} 加仓金额 {add_amount:.2f} 低于最小下注金额 {min_stake:.2f},取消加仓")
|
||||
return (None, f"Add amount {add_amount:.2f} below min_stake {min_stake:.2f}")
|
||||
logger.warning(f"[{pair}] 加仓金额 {add_amount:.2f} 低于最小下单金额 {min_stake},取消加仓")
|
||||
return (None, "Add amount below min_stake")
|
||||
if add_amount > max_stake:
|
||||
logger.warning(f"{pair} 加仓金额 {add_amount:.2f} 超出最大可用金额 {max_stake:.2f},调整为 {max_stake:.2f}")
|
||||
add_amount = max_stake
|
||||
logger.info(f"{pair} 价格下跌 {profit_ratio*100:.2f}%,触发第 {add_count + 1} 次加仓 {add_amount:.2f}")
|
||||
return (add_amount, f"Price dropped {profit_ratio*100:.2f}%, add {add_amount:.2f}")
|
||||
logger.warning(f"[{pair}] 加仓金额 {add_amount:.2f} 超出最大可用金额 {max_stake},取消加仓")
|
||||
return (None, "Add amount exceeds max_stake")
|
||||
logger.info(f"[{pair}] 价格下跌 {profit_ratio*100:.2f}%,触发加仓 {add_amount:.2f}")
|
||||
return (add_amount, f"Price dropped {profit_ratio*100:.2f}%")
|
||||
else:
|
||||
logger.debug(f"[{pair}] 价格下跌但市场趋势为 {market_trend},不加仓")
|
||||
return None
|
||||
|
||||
# 减仓逻辑
|
||||
# --- 减仓逻辑 ---
|
||||
exit_position_ratio = self.EXIT_POSITION_RATIO.value
|
||||
if profit_ratio >= 0.03:
|
||||
# 趋势值越高,减仓比例越低
|
||||
reduce_factor = 0.6 + 0.4 * (1 - trend_score / 100) # 牛市(100) -> 0.6, 熊市(0) -> 1.0
|
||||
reduce_amount = -exit_position_ratio * reduce_factor * trade.stake_amount
|
||||
logger.info(f"{pair} 趋势值 {trend_score:.2f},利润 {profit_ratio*100:.2f}%,减仓 {abs(reduce_amount):.2f}")
|
||||
return (reduce_amount, f"Profit {profit_ratio*100:.2f}%")
|
||||
elif profit_ratio >= 0.05:
|
||||
reduce_factor = 1.4 - 0.4 * (trend_score / 100) # 牛市(100) -> 1.0, 熊市(0) -> 1.4
|
||||
reduce_amount = -exit_position_ratio * reduce_factor * trade.stake_amount
|
||||
logger.info(f"{pair} 趋势值 {trend_score:.2f},利润 {profit_ratio*100:.2f}%,减仓 {abs(reduce_amount):.2f}")
|
||||
if profit_ratio >= 0.03: # 利润达到 3%
|
||||
if market_trend == 'bull':
|
||||
reduce_amount = -exit_position_ratio * 0.6 * trade.stake_amount # 牛市减仓较少
|
||||
logger.info(f"[{pair}] 牛市,利润 {profit_ratio*100:.2f}%,减仓 {abs(reduce_amount):.2f}")
|
||||
return (reduce_amount, f"Bull market, profit {profit_ratio*100:.2f}%")
|
||||
else:
|
||||
reduce_amount = -exit_position_ratio * trade.stake_amount # 其他市场减仓较多
|
||||
logger.info(f"[{pair}] 利润 {profit_ratio*100:.2f}%,减仓 {abs(reduce_amount):.2f}")
|
||||
return (reduce_amount, f"Profit {profit_ratio*100:.2f}%")
|
||||
elif profit_ratio >= 0.05: # 利润达到 5%
|
||||
reduce_amount = -exit_position_ratio * 1.4 * trade.stake_amount # 卖出更多
|
||||
logger.info(f"[{pair}] 利润 {profit_ratio*100:.2f}%,减仓 {abs(reduce_amount):.2f}")
|
||||
return (reduce_amount, f"Profit {profit_ratio*100:.2f}%")
|
||||
|
||||
# 追踪止损逻辑
|
||||
# --- 追踪止损逻辑 ---
|
||||
trailing_stop_start = self.TRAILING_STOP_START.value
|
||||
trailing_stop_distance = self.TRAILING_STOP_DISTANCE.value
|
||||
# 使用 Sigmoid 映射调整追踪止损参数
|
||||
sigmoid = 1 / (1 + np.exp(-0.1 * (trend_score - 50)))
|
||||
trailing_factor = 0.8 + (1.2 - 0.8) * sigmoid # 牛市(100) -> 1.2, 熊市(0) -> 0.8
|
||||
distance_factor = 0.7 + (1.5 - 0.7) * sigmoid # 牛市(100) -> 1.5, 熊市(0) -> 0.7
|
||||
trailing_stop_start *= trailing_factor
|
||||
trailing_stop_distance *= distance_factor
|
||||
if market_trend == 'bull':
|
||||
trailing_stop_distance *= 1.5 # 牛市放宽追踪止损
|
||||
trailing_stop_start *= 1.2
|
||||
elif market_trend == 'bear':
|
||||
trailing_stop_distance *= 0.7 # 熊市收紧追踪止损
|
||||
trailing_stop_start *= 0.8
|
||||
|
||||
if profit_ratio >= trailing_stop_start and not self.trailing_stop_enabled:
|
||||
self.trailing_stop_enabled = True
|
||||
trade.adjust_min_max_rates(current_rate, current_rate)
|
||||
logger.info(f"{pair} 价格上涨超过 {trailing_stop_start*100:.1f}%,启动追踪止损")
|
||||
trade.adjust_min_max_rates(current_rate, current_rate) # 初始化最高和最低价格
|
||||
logger.info(f"[{pair}] 价格上涨超过 {trailing_stop_start*100:.1f}%,启动 Trailing Stop")
|
||||
return None
|
||||
|
||||
if self.trailing_stop_enabled:
|
||||
max_rate = trade.max_rate or current_rate
|
||||
max_rate = trade.max_rate
|
||||
trailing_stop_price = max_rate * (1 - trailing_stop_distance)
|
||||
if current_rate < trailing_stop_price:
|
||||
logger.info(f"{pair} 价格回落至 {trailing_stop_price:.6f},触发全部卖出")
|
||||
logger.info(f"[{pair}] 价格回落至 Trailing Stop 点 {trailing_stop_price:.6f},触发全部卖出")
|
||||
return (-trade.stake_amount, f"Trailing stop at {trailing_stop_price:.6f}")
|
||||
trade.adjust_min_max_rates(current_rate, trade.min_rate)
|
||||
trade.adjust_min_max_rates(current_rate, trade.min_rate) # 更新最高价格
|
||||
return None
|
||||
|
||||
# 最大持仓时间限制
|
||||
# if hold_time > max_hold_time:
|
||||
# logger.info(f"{pair} 趋势值 {trend_score:.2f},持仓时间超过 {max_hold_time} 分钟,强制清仓")
|
||||
# return (-trade.stake_amount, f"Max hold time ({max_hold_time} min) exceeded")
|
||||
# --- 最大持仓时间限制 ---
|
||||
if hold_time > 30:
|
||||
logger.info(f"[{pair}] 持仓时间超过30分钟,强制清仓")
|
||||
return (-trade.stake_amount, "Max hold time exceeded")
|
||||
|
||||
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:
|
||||
# 调试日志:记录输入参数
|
||||
logger.debug(f"[{pair}] confirm_trade_entry called with rate={rate}, type(rate)={type(rate)}, "
|
||||
f"amount={amount}, order_type={order_type}, time_in_force={time_in_force}")
|
||||
|
||||
# 检查 rate 是否有效
|
||||
if not isinstance(rate, (float, int)) or rate is None:
|
||||
logger.error(f"[{pair}] Invalid rate value: {rate} (type: {type(rate)}). Skipping trade entry.")
|
||||
return False
|
||||
|
||||
market_trend_score = self.get_market_trend()
|
||||
cooldown_period_minutes = self.COOLDOWN_PERIOD_MINUTES.value if market_trend_score > 50 else self.COOLDOWN_PERIOD_MINUTES.value // 2
|
||||
"""
|
||||
确认交易进入时的逻辑,并记录详细日志
|
||||
"""
|
||||
market_trend = self.get_market_trend()
|
||||
cooldown_period_minutes = self.COOLDOWN_PERIOD_MINUTES.value if market_trend == 'bull' else self.COOLDOWN_PERIOD_MINUTES.value // 2
|
||||
|
||||
# 检查冷却期
|
||||
if pair in self.last_entry_time:
|
||||
last_time = self.last_entry_time[pair]
|
||||
if (current_time - last_time).total_seconds() < cooldown_period_minutes * 60:
|
||||
logger.info(f"[{pair}] 冷却期内({cooldown_period_minutes} 分钟),跳过本次入场")
|
||||
return False
|
||||
|
||||
# 记录进入交易的详细信息
|
||||
entry_log = {
|
||||
"pair": pair,
|
||||
"order_type": order_type,
|
||||
"amount": amount,
|
||||
"rate": rate,
|
||||
"total_value": amount * rate,
|
||||
"time_in_force": time_in_force,
|
||||
"timestamp": current_time.isoformat(),
|
||||
"market_trend": market_trend,
|
||||
"cooldown_period_minutes": cooldown_period_minutes,
|
||||
"buy_threshold": self.buy_threshold,
|
||||
"indicators": self.indicator_cache.get(pair, {
|
||||
"rsi": "N/A",
|
||||
"volume_z_score": "N/A",
|
||||
"&-price_value_divergence": "N/A",
|
||||
"bb_lowerband": "N/A"
|
||||
})
|
||||
}
|
||||
logger.info(f"[{pair}] 交易进入确认: {json.dumps(entry_log, indent=2)}")
|
||||
self.trade_logger.info(json.dumps(entry_log))
|
||||
|
||||
# 更新最后入场时间并重置追踪止损
|
||||
self.last_entry_time[pair] = current_time
|
||||
self.trailing_stop_enabled = False
|
||||
try:
|
||||
logger.info(f"[{pair}] 确认入场,价格:{float(rate):.6f}")
|
||||
except (ValueError, TypeError) as e:
|
||||
logger.error(f"[{pair}] Failed to format rate: {rate} (type: {type(rate)}), error: {e}")
|
||||
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:
|
||||
rate: float, time_in_force: str, exit_reason: str,
|
||||
current_time: datetime, **kwargs) -> bool:
|
||||
"""
|
||||
确认交易退出时的逻辑,并记录详细日志
|
||||
"""
|
||||
adjusted_rate = rate * (1 + 0.0025)
|
||||
logger.info(f"[{pair}] 退出交易,原因:{exit_reason}, 原始利润:{trade.calc_profit_ratio(rate):.2%},"
|
||||
f"调整后卖出价:{adjusted_rate:.6f}")
|
||||
profit_ratio = trade.calc_profit_ratio(rate)
|
||||
profit_amount = trade.calc_profit(rate)
|
||||
hold_time_minutes = (current_time - trade.open_date_utc).total_seconds() / 60
|
||||
|
||||
# 记录退出交易的详细信息
|
||||
exit_log = {
|
||||
"pair": pair,
|
||||
"order_type": order_type,
|
||||
"amount": amount,
|
||||
"rate": rate,
|
||||
"adjusted_rate": adjusted_rate,
|
||||
"total_value": amount * rate,
|
||||
"time_in_force": time_in_force,
|
||||
"timestamp": current_time.isoformat(),
|
||||
"exit_reason": exit_reason,
|
||||
"profit_ratio": profit_ratio,
|
||||
"profit_amount": profit_amount,
|
||||
"hold_time_minutes": hold_time_minutes,
|
||||
"market_trend": self.get_market_trend(),
|
||||
"sell_threshold": self.sell_threshold,
|
||||
"indicators": self.indicator_cache.get(pair, {
|
||||
"rsi": "N/A",
|
||||
"volume_z_score": "N/A",
|
||||
"&-price_value_divergence": "N/A",
|
||||
"bb_upperband": "N/A"
|
||||
}),
|
||||
"trade_details": {
|
||||
"open_rate": trade.open_rate,
|
||||
"open_date": trade.open_date_utc.isoformat(),
|
||||
"stake_amount": trade.stake_amount,
|
||||
"nr_of_successful_entries": trade.nr_of_successful_entries
|
||||
}
|
||||
}
|
||||
logger.info(f"[{pair}] 交易退出确认: {json.dumps(exit_log, indent=2)}")
|
||||
self.trade_logger.info(json.dumps(exit_log))
|
||||
|
||||
return True
|
||||
|
||||
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
|
||||
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||
adjusted_rate = proposed_rate * (1 - 0.005)
|
||||
logger.debug(f"[{pair}] 自定义买入价:{adjusted_rate:.6f}(原价:{proposed_rate:.6f})")
|
||||
return adjusted_rate
|
||||
|
||||
def custom_exit_price(self, pair: str, trade: Trade,
|
||||
current_time: datetime, proposed_rate: float,
|
||||
current_profit: float, exit_tag: str | None, **kwargs) -> float:
|
||||
current_time: datetime, proposed_rate: float,
|
||||
current_profit: float, exit_tag: str | None, **kwargs) -> float:
|
||||
adjusted_rate = proposed_rate * (1 + 0.0025)
|
||||
logger.debug(f"[{pair}] 自定义卖出价:{adjusted_rate:.6f}(原价:{proposed_rate:.6f})")
|
||||
return adjusted_rate
|
||||
|
||||
def get_market_trend(self, dataframe: DataFrame = None, metadata: dict = None) -> int:
|
||||
def get_market_trend(self, dataframe: DataFrame = None, metadata: dict = None) -> str:
|
||||
"""
|
||||
计算市场趋势得分,0 表示强熊,100 表示强牛,50 表示中性。
|
||||
综合 3m、15m 和 1h 时间框架的趋势,使用对数函数映射,非线性增强两端趋势。
|
||||
融合价格趋势、K 线形态、StochRSI 和量价关系。
|
||||
|
||||
参数:
|
||||
dataframe: 可选,外部传入的 BTC/USDT 数据(3m 时间框架),默认为 None(自动获取)。
|
||||
metadata: 可选,包含币种信息。
|
||||
|
||||
返回值:
|
||||
int: 0-100 的趋势得分。
|
||||
判断市场趋势:bull(牛市)、bear(熊市)、sideways(震荡市)
|
||||
基于多时间框架的EMA、K线形态和量价关系
|
||||
"""
|
||||
try:
|
||||
# 初始化得分和权重
|
||||
timeframes = ["3m", "15m", "1h"]
|
||||
weights = {"3m": 0.5, "15m": 0.3, "1h": 0.2} # 短期框架权重更高
|
||||
weights = {"3m": 0.5, "15m": 0.3, "1h": 0.2}
|
||||
trend_scores = {}
|
||||
|
||||
pair = metadata.get('pair', 'Unknown') if metadata else 'Unknown'
|
||||
logger.debug(f"[{pair}] 正在计算多时间框架市场趋势得分")
|
||||
logger.debug(f"[{pair}] 正在计算多时间框架市场趋势")
|
||||
|
||||
for tf in timeframes:
|
||||
# 获取对应时间框架的数据
|
||||
if tf == "3m" and dataframe is not None:
|
||||
btc_df = dataframe
|
||||
else:
|
||||
btc_df = self.dp.get_pair_dataframe("BTC/USDT", tf)
|
||||
|
||||
# 检查数据量
|
||||
min_candles = 200 if tf == "3m" else 100 if tf == "15m" else 50 # 不同时间框架所需最小数据量
|
||||
min_candles = 200 if tf == "3m" else 100 if tf == "15m" else 50
|
||||
if len(btc_df) < min_candles:
|
||||
logger.warning(f"BTC 数据不足({tf},{len(btc_df)} 根K线),使用默认得分:50")
|
||||
logger.warning(f"[{pair}] BTC 数据不足({tf},{len(btc_df)} 根K线),默认震荡市")
|
||||
trend_scores[tf] = 50
|
||||
continue
|
||||
|
||||
# --- 价格趋势 ---
|
||||
ema_short_period = 50 if tf == "3m" else 20 if tf == "15m" else 12 # 调整周期以适应时间框架
|
||||
ema_long_period = 200 if tf == "3m" else 80 if tf == "15m" else 50
|
||||
btc_df["ema_short"] = ta.EMA(btc_df, timeperiod=ema_short_period)
|
||||
btc_df["ema_long"] = ta.EMA(btc_df, timeperiod=ema_long_period)
|
||||
# 计算EMA
|
||||
btc_df["ema_short"] = ta.EMA(btc_df, timeperiod=50 if tf == "3m" else 20 if tf == "15m" else 12)
|
||||
btc_df["ema_long"] = ta.EMA(btc_df, timeperiod=200 if tf == "3m" else 80 if tf == "15m" else 50)
|
||||
btc_df["ema_short_slope"] = (btc_df["ema_short"] - btc_df["ema_short"].shift(10)) / btc_df["ema_short"].shift(10)
|
||||
|
||||
price_above_ema = btc_df["close"].iloc[-1] > btc_df["ema_long"].iloc[-1]
|
||||
ema_short_above_ema_long = btc_df["ema_short"].iloc[-1] > btc_df["ema_long"].iloc[-1]
|
||||
ema_short_slope = btc_df["ema_short_slope"].iloc[-1]
|
||||
|
||||
# 价格趋势得分
|
||||
price_score = 0
|
||||
if price_above_ema:
|
||||
if btc_df["close"].iloc[-1] > btc_df["ema_long"].iloc[-1]:
|
||||
price_score += 20
|
||||
if ema_short_above_ema_long:
|
||||
if btc_df["ema_short"].iloc[-1] > btc_df["ema_long"].iloc[-1]:
|
||||
price_score += 20
|
||||
if ema_short_slope > 0.005:
|
||||
if btc_df["ema_short_slope"].iloc[-1] > 0.005:
|
||||
price_score += 15
|
||||
elif ema_short_slope < -0.005:
|
||||
elif btc_df["ema_short_slope"].iloc[-1] < -0.005:
|
||||
price_score -= 15
|
||||
|
||||
# --- K 线形态 ---
|
||||
# K线形态得分
|
||||
btc_df["bullish_engulfing"] = (
|
||||
(btc_df["close"].shift(1) < btc_df["open"].shift(1)) &
|
||||
(btc_df["close"] > btc_df["open"]) &
|
||||
@ -606,7 +651,6 @@ class FreqaiPrimer(IStrategy):
|
||||
(btc_df["close"] < btc_df["open"].shift(1)) &
|
||||
(btc_df["open"] > btc_df["close"].shift(1))
|
||||
)
|
||||
|
||||
kline_score = 0
|
||||
if btc_df["bullish_engulfing"].iloc[-1]:
|
||||
kline_score += 15
|
||||
@ -616,60 +660,60 @@ class FreqaiPrimer(IStrategy):
|
||||
if volatility > 0.5:
|
||||
kline_score += 10 if price_score > 0 else -10
|
||||
|
||||
# --- StochRSI ---
|
||||
# StochRSI得分
|
||||
stochrsi = ta.STOCHRSI(btc_df, timeperiod=14, fastk_period=3, fastd_period=3)
|
||||
btc_df["stochrsi_k"] = stochrsi["fastk"]
|
||||
btc_df["stochrsi_d"] = stochrsi["fastd"]
|
||||
|
||||
stochrsi_score = 0
|
||||
stochrsi_k = btc_df["stochrsi_k"].iloc[-1]
|
||||
stochrsi_d = btc_df["stochrsi_d"].iloc[-1]
|
||||
if stochrsi_k > 80 and stochrsi_k < stochrsi_d:
|
||||
if btc_df["stochrsi_k"].iloc[-1] > 80 and btc_df["stochrsi_k"].iloc[-1] < btc_df["stochrsi_d"].iloc[-1]:
|
||||
stochrsi_score -= 15
|
||||
elif stochrsi_k < 20 and stochrsi_k > stochrsi_d:
|
||||
elif btc_df["stochrsi_k"].iloc[-1] < 20 and btc_df["stochrsi_k"].iloc[-1] > btc_df["stochrsi_d"].iloc[-1]:
|
||||
stochrsi_score += 15
|
||||
elif stochrsi_k > 50:
|
||||
elif btc_df["stochrsi_k"].iloc[-1] > 50:
|
||||
stochrsi_score += 5
|
||||
elif stochrsi_k < 50:
|
||||
elif btc_df["stochrsi_k"].iloc[-1] < 50:
|
||||
stochrsi_score -= 5
|
||||
|
||||
# --- 量价关系 ---
|
||||
# 量价关系得分
|
||||
btc_df["volume_mean_20"] = btc_df["volume"].rolling(20).mean()
|
||||
btc_df["volume_std_20"] = btc_df["volume"].rolling(20).std()
|
||||
btc_df["volume_z_score"] = (btc_df["volume"] - btc_df["volume_mean_20"]) / btc_df["volume_std_20"]
|
||||
btc_df["adx"] = ta.ADX(btc_df, timeperiod=14)
|
||||
|
||||
volume_score = 0
|
||||
if btc_df["volume_z_score"].iloc[-1] > 1.5:
|
||||
volume_score += 10 if price_score > 0 else -10
|
||||
if btc_df["adx"].iloc[-1] > 25:
|
||||
volume_score += 10 if price_score > 0 else -10
|
||||
|
||||
# --- 综合得分 ---
|
||||
# 综合得分
|
||||
raw_score = price_score + kline_score + stochrsi_score + volume_score
|
||||
raw_score = max(min(raw_score, 50), -50)
|
||||
|
||||
# 对数映射到 [0, 100]
|
||||
if raw_score >= 0:
|
||||
mapped_score = 50 + 50 * (np.log1p(raw_score / 50) / np.log1p(1))
|
||||
else:
|
||||
mapped_score = 50 * (np.log1p(-raw_score / 50) / np.log1p(1))
|
||||
|
||||
trend_scores[tf] = max(0, min(100, int(round(mapped_score))))
|
||||
logger.debug(f"[{pair}] {tf} 趋势得分:{trend_scores[tf]}, 原始得分:{raw_score}, "
|
||||
f"价格得分:{price_score}, K线得分:{kline_score}, "
|
||||
f"StochRSI得分:{stochrsi_score}, 量价得分:{volume_score}")
|
||||
logger.debug(f"[{pair}] {tf} 趋势得分:{trend_scores[tf]}, "
|
||||
f"原始得分:{raw_score}, 价格得分:{price_score}, "
|
||||
f"K线得分:{kline_score}, StochRSI得分:{stochrsi_score}, "
|
||||
f"量价得分:{volume_score}")
|
||||
|
||||
# 加权融合多时间框架得分
|
||||
# 加权融合
|
||||
final_score = sum(trend_scores[tf] * weights[tf] for tf in timeframes)
|
||||
final_score = int(round(final_score))
|
||||
final_score = max(0, min(100, final_score))
|
||||
|
||||
final_score = max(0, min(100, int(round(final_score))))
|
||||
logger.info(f"[{pair}] 最终趋势得分:{final_score}, "
|
||||
f"3m得分:{trend_scores.get('3m', 50)}, 15m得分:{trend_scores.get('15m', 50)}, "
|
||||
f"1h得分:{trend_scores.get('1h', 50)}")
|
||||
return final_score
|
||||
f"3m得分:{trend_scores.get('3m', 50)}, "
|
||||
f"15m得分:{trend_scores.get('15m', 50)}, "
|
||||
f"1h得分:{trend_scores.get('1h', 50)}")
|
||||
|
||||
# 映射到趋势类别
|
||||
if final_score >= 70:
|
||||
return 'bull'
|
||||
elif final_score <= 30:
|
||||
return 'bear'
|
||||
else:
|
||||
return 'sideways'
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[{pair}] 获取市场趋势失败:{e}", exc_info=True)
|
||||
return 50
|
||||
return 'sideways'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user