This commit is contained in:
zhangkun9038@dingtalk.com 2025-06-23 16:36:21 +08:00
parent af12fe6b25
commit db95f4bb58
2 changed files with 240 additions and 196 deletions

View File

@ -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"
}

View File

@ -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 表示中性
综合 3m15m 1h 时间框架的趋势使用对数函数映射非线性增强两端趋势
融合价格趋势K 线形态StochRSI 和量价关系
参数
dataframe: 可选外部传入的 BTC/USDT 数据3m 时间框架默认为 None自动获取
metadata: 可选包含币种信息
返回值
int: 0-100 的趋势得分
判断市场趋势bull牛市bear熊市sideways震荡市
基于多时间框架的EMAK线形态和量价关系
"""
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'