From 87659e00559446f307f3ecac645c55688e9ef82e Mon Sep 17 00:00:00 2001 From: 27942 <2794236280@qq.com> Date: Mon, 20 Oct 2025 13:39:49 +0800 Subject: [PATCH] dededdew --- models/database.db | Bin 23343104 -> 23396352 bytes weex/优化开仓方向版本.py | 398 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 398 insertions(+) create mode 100644 weex/优化开仓方向版本.py diff --git a/models/database.db b/models/database.db index ceb5482a2bbe14dcb49055928b2bf65a4f7a0647..ea562c1c59581ae1f3c1734e11723e9ac23e1539 100644 GIT binary patch delta 31022 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 delta 1742 zcmW;McU;tU9LI4ExFx5eA_{tV;y{5z6c-Lq!F8yhI9ST5-O*B_rIppC+2tsqpLUgD ziZ-Te_N~q7n-l zTzS!5&HKGg#WDG2XUN*#RJg0C-4Uwvw%CoeUR#RUY4(`cnVZZf%%?3GmNLs`%K^(t ztHYXW^;(0Gx(;(F)(FUSHRKDTic zyM5_KoiEMs!0F2lM`}E_h^b@ydRzEdNzl5-TWQK^%{g3ca@oSAW&5m~Oo5;&U_vxv z5DPObu)+pAx*-k@bVm>LL_B(-H~Js}eUXTM=#L~MV*m!ii4+Wi3xko0AsC8b7>+cI zz(}Mc1DS9m3!{*Y(HMgqjKw&N#{^77E+%0zreG@aBBnro%yHXZdsP(25^=;AmD=O2 z;l?AK8(mCn;l`GOr-HTR(=Z(~P=G?r#4OCl9L$9WMVN>ASb$=bU?CQv6pOJ0OR)@P zC`SbZbk^V;8xs*-MAf% z*n>N8C+@=CxCc$R7x&?QJb(wW7Z2fKG-Dqg!K2uZ0|?_WwBT_>@C2U3K|F=0aR|?# z6^HRGp2PEa0Wabu9Klf>!^?ODui`bljyG@|C-5fT!rOQUZFm>&;eC975AhK`#wR$5 zc6^G@@HxJ~m-q@_;~RX7@9;f-z>oL|KcfS`;8*;H-_eOb@F%+P7f#`C{DXgS8vkvp zEjR6e60O82v5Hx-C|1R$*p+TdoZ?WrD?OB+O1#ob>8= 0: + raise ValueError("止损点数必须小于0") + + +@dataclass +class TradeRecord: + """交易记录类""" + entry_time: datetime.datetime + exit_time: datetime.datetime + signal_type: str + direction: str + entry_price: float + exit_price: float + profit_loss: float + profit_amount: float + total_fee: float + net_profit: float + + +@dataclass +class SignalStats: + """信号统计类""" + signal_name: str + count: int = 0 + wins: int = 0 + total_profit: float = 0.0 + + @property + def win_rate(self) -> float: + """胜率计算""" + return (self.wins / self.count * 100) if self.count > 0 else 0.0 + + @property + def avg_profit(self) -> float: + """平均盈利""" + return self.total_profit / self.count if self.count > 0 else 0.0 + + +# =============================================================== +# 📊 数据获取模块 +# =============================================================== + +def get_data_by_date(model, date_str): + """按天获取指定表的数据""" + try: + target_date = datetime.datetime.strptime(date_str, '%Y-%m-%d') + except ValueError: + logger.error("日期格式不正确,请使用 YYYY-MM-DD 格式。") + return [] + + start_ts = int(target_date.timestamp() * 1000) + end_ts = int((target_date + datetime.timedelta(days=1)).timestamp() * 1000) - 1 + + query = (model + .select() + .where(model.id.between(start_ts, end_ts)) + .order_by(model.id.asc())) + + return [ + {'id': i.id, 'open': i.open, 'high': i.high, 'low': i.low, 'close': i.close} + for i in query + ] + + +def get_future_data_1min(start_ts, end_ts): + """获取指定时间范围内的 1 分钟数据""" + query = (Weex1 + .select() + .where(Weex1.id.between(start_ts, end_ts)) + .order_by(Weex1.id.asc())) + return [{'id': i.id, 'open': i.open, 'high': i.high, 'low': i.low, 'close': i.close} for i in query] + + +# =============================================================== +# 📈 信号判定模块 +# =============================================================== + +def is_bullish(c): return float(c['open']) < float(c['close']) + + +def is_bearish(c): return float(c['open']) > float(c['close']) + + +def check_signal(prev, curr): + """判断是否出现包住形态""" + p_open, p_close = float(prev['open']), float(prev['close']) + c_open, c_close = float(curr['open']), float(curr['close']) + + # 前跌后涨包住 -> 做多 + if is_bullish(curr) and is_bearish(prev) and c_open <= p_close and c_close >= p_open: + return "long", "bear_bull_engulf" + + # 前涨后跌包住 -> 做空 + if is_bearish(curr) and is_bullish(prev) and c_open >= p_close and c_close <= p_open: + return "short", "bull_bear_engulf" + + return None, None + + +# =============================================================== +# 💹 回测模拟模块(使用 1 分钟数据) +# =============================================================== + +# ---------- 替换后的 simulate_trade ---------- +def simulate_trade(direction, entry_price, entry_time, next_15min_time, tp=8, sl=-1): + """ + 返回 (exit_price, profit_diff, exit_time, exit_reason) + exit_reason: 'tp' (触及止盈并以tp价平仓), + 'sl' (触及止损并以sl价平仓), + 'open_tp' (开盘跳空并以开盘价止盈), + 'open_sl' (开盘跳空并以开盘价止损), + 'timeout' (到分析窗口末尾,用最后一根收盘价平仓) + 注意:sl 参数为负数(你的设定),但在计算止损价时已处理方向 + """ + future_candles = get_future_data_1min(entry_time, next_15min_time) + if not future_candles: + return None, 0.0, None, None + + # 计算目标价位(数值) + tp_price = entry_price + tp if direction == "long" else entry_price - tp + sl_price = entry_price + sl if direction == "long" else entry_price - sl # sl 为负数,long 情况为 entry + (负数) -> 小于 entry + + for candle in future_candles: + open_p = float(candle['open']) + high = float(candle['high']) + low = float(candle['low']) + ts = candle['id'] + + if direction == "long": + # 开盘跳空(以开盘价直接触及止盈/止损) + if open_p >= tp_price: + return open_p, open_p - entry_price, ts, 'open_tp' + if open_p <= sl_price: + return open_p, open_p - entry_price, ts, 'open_sl' + # 盘中触及 + if high >= tp_price: + return tp_price, tp, ts, 'tp' + if low <= sl_price: + return sl_price, sl, ts, 'sl' + else: # short + if open_p <= tp_price: + return open_p, entry_price - open_p, ts, 'open_tp' + if open_p >= sl_price: + return open_p, entry_price - open_p, ts, 'open_sl' + if low <= tp_price: + return tp_price, tp, ts, 'tp' + if high >= sl_price: + return sl_price, sl, ts, 'sl' + + # 未触发止盈止损,用最后一根收盘价平仓(视为 timeout) + final = future_candles[-1] + final_price = float(final['close']) + diff = (final_price - entry_price) if direction == "long" else (entry_price - final_price) + return final_price, diff, final['id'], 'timeout' + + +# ---------- 替换后的 backtest_single_position ---------- +def backtest_single_position(dates, tp, sl): + """ + 单笔持仓回测(增强版),加入连续3次止损触发反向开仓(转向单)逻辑 + - 只有当 exit_reason 属于 'sl' 或 'open_sl' 时才算“真实止损” + - 当连续真实止损计数达到 3 时,**下一笔**信号反向开仓(且该转向单不计入连续止损统计) + """ + all_data = [] + for date_str in dates: + all_data.extend(get_data_by_date(Weex15, date_str)) + all_data.sort(key=lambda x: x['id']) + + stats = { + "bear_bull_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "涨包跌"}, + "bull_bear_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "跌包涨"}, + } + + trades = [] + current_position = None # 当前持仓信息 dict or None + + consec_sl_count = 0 # 连续真实止损计数(只有 exit_reason 为 'sl' 或 'open_sl' 时 +1) + reverse_next_signal = False # 是否需要将下一笔信号取反(由连续3次止损触发) + ignore_next_result = False # 标记下一笔成交的结果是否要被忽略(用于转向单:转向单不计入连续止损计数,也不影响计数) + + for idx in range(1, len(all_data) - 1): + prev, curr = all_data[idx - 1], all_data[idx] + entry_candle = all_data[idx + 1] + + # 原始信号判定 + direction, signal = check_signal(prev, curr) + if not direction: + continue + + # 如果需要反向下一笔信号(由之前三次止损触发),则翻转 direction 并标记为转向单(只对这一次有效) + is_reversal_trade = False + if reverse_next_signal: + direction = 'long' if direction == 'short' else 'short' + is_reversal_trade = True + reverse_next_signal = False + ignore_next_result = True # 这笔成交的平仓结果不影响 consec_sl_count + + # 下一个 15 分钟K线的时间范围(用 idx+50 作为 15min 窗口近似) + next_15min_time = all_data[idx + 50]['id'] if idx + 50 < len(all_data) else all_data[-1]['id'] + entry_price = float(entry_candle['open']) + + # 如果已有持仓 + if current_position: + # 同向信号 -> 跳过(维持现有持仓) + if current_position['direction'] == direction: + continue + # 反向信号 -> 先按当前位置止盈止损平仓,再根据规则决定是否开新仓 + else: + exit_price, diff, exit_time, exit_reason = simulate_trade( + current_position['direction'], + current_position['entry_price'], + current_position['entry_time'], + entry_candle['id'], + tp=tp, + sl=sl + ) + if exit_price is not None: + trades.append({ + "entry_time": datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000), + "exit_time": datetime.datetime.fromtimestamp(exit_time / 1000), + "signal": current_position['signal'], + "direction": "做多" if current_position['direction'] == "long" else "做空", + "entry": current_position['entry_price'], + "exit": exit_price, + "diff": diff, + "exit_reason": exit_reason, + "is_reversal_trade": current_position.get('is_reversal_trade', False) + }) + # 更新统计(只有非转向单会计入统计?按你原逻辑计入) + stats_key = 'bear_bull_engulf' if current_position['signal'] == '涨包跌' else 'bull_bear_engulf' + stats[stats_key]['count'] += 1 + stats[stats_key]['total_profit'] += diff + if diff > 0: + stats[stats_key]['wins'] += 1 + + # 根据 exit_reason 更新 consec_sl_count(但如果当时该仓为被标记为 ignore_result,则不变) + if not current_position.get('ignore_result', False): + if exit_reason in ('sl', 'open_sl'): + consec_sl_count += 1 + else: + consec_sl_count = 0 + + # 如果累计达到 3 次真实止损 -> 标记下一笔反向 + if consec_sl_count >= 3: + reverse_next_signal = True + consec_sl_count = 0 # 触发后清零(下一笔为转向单) + else: + # 如果这笔被标记为 ignore(通常是前面是转向单),则不影响计数 + pass + + current_position = None # 清空持仓 + + # 开新仓(注意:如果这笔是转向单,我们之前已经取反了 direction,并设置了 is_reversal_trade 和 ignore_next_result) + current_position = { + "direction": direction, + "signal": stats[signal]['name'], + "entry_price": entry_price, + "entry_time": entry_candle['id'], + "is_reversal_trade": is_reversal_trade, + "ignore_result": ignore_next_result # 如果为 True,则本仓平仓结果不计入 consec_sl_count + } + + # 如果我们标记了 ignore_next_result(说明这是转向单),只在刚开仓后清除标记,确保仅忽略这笔的**平仓**结果 + if ignore_next_result: + # 清掉,只有这笔仓的平仓结果需要被忽略(记录在 current_position 中), + # 后续在处理平仓时会读取 current_position['ignore_result'] 并决定是否影响计数 + ignore_next_result = False + + # 最后一笔持仓如果未平仓,用最后收盘价平掉 + if current_position: + exit_price, diff, exit_time, exit_reason = simulate_trade( + current_position['direction'], + current_position['entry_price'], + current_position['entry_time'], + all_data[-1]['id'], + tp=tp, + sl=sl + ) + if exit_price is not None: + trades.append({ + "entry_time": datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000), + "exit_time": datetime.datetime.fromtimestamp(exit_time / 1000), + "signal": current_position['signal'], + "direction": "做多" if current_position['direction'] == "long" else "做空", + "entry": current_position['entry_price'], + "exit": exit_price, + "diff": diff, + "exit_reason": exit_reason, + "is_reversal_trade": current_position.get('is_reversal_trade', False) + }) + stats_key = 'bear_bull_engulf' if current_position['signal'] == '涨包跌' else 'bull_bear_engulf' + stats[stats_key]['count'] += 1 + stats[stats_key]['total_profit'] += diff + if diff > 0: + stats[stats_key]['wins'] += 1 + + # 最后一笔是否计入连续止损计数 + if not current_position.get('ignore_result', False): + if exit_reason in ('sl', 'open_sl'): + consec_sl_count += 1 + else: + consec_sl_count = 0 + if consec_sl_count >= 3: + # 可选择记录或告警,这里仅重置计数 + consec_sl_count = 0 + + return trades, stats + + + +# =============================================================== +# 🚀 启动主流程 +# =============================================================== + +if __name__ == '__main__': + dates = [f"2025-9-{i}" for i in range(1, 31)] + + trades, stats = backtest_single_position(dates, tp=50, sl=-10) + + logger.info("===== 每笔交易详情 =====") + for t in trades: + logger.info( + f"{t['entry_time']} {t['direction']}({t['signal']}) " + f"入场={t['entry']:.2f} 出场={t['exit']:.2f} 出场时间={t['exit_time']} " + f"差价={t['diff']:.2f}" + ) + + total_profit = sum(t['diff'] / t['entry'] * 10000 for t in trades) + total_fee = sum(5 + 10000 / t['entry'] * t['exit'] * 0.0005 for t in trades) + + print(f"\n一共交易笔数:{len(trades)}") + print(f"一共盈利:{total_profit:.2f}") + print(f"一共手续费:{total_fee:.2f}") + print(f"净利润:{total_profit - total_fee:.2f}") + print("\n===== 信号统计 =====") + + # =============================================================================================================================== + + # for i in range(1, 16): + # for i1 in range(1, 51): + # trades, stats = backtest_single_position(dates, tp=i1, sl=-i) + # + # total_profit = sum(t['diff'] / t['entry'] * 10000 for t in trades) + # total_fee = sum(5 + 10000 / t['entry'] * t['exit'] * 0.0005 for t in trades) + # + # if total_profit > total_fee * 0.1: + # print("\n===== 信号统计 =====") + # print(f"止盈:{i1}, 止损:{i}") + # print(f"\n一共交易笔数:{len(trades)}") + # print(f"一共盈利:{total_profit:.2f}") + # print(f"一共手续费:{total_fee:.2f}") + # print(f"净利润:{total_profit - total_fee * 0.1}") + +# 需要优化,目前有两种情况,第一种,同向,不如说上一单开单是涨,上一单还没有结束,当前信号来了,就不开单,等上一单到了上一单的止损位或者止盈位在平仓 +# 第二种,方向,上一单是涨,上一单还没有结束,当前信号来了,是跌,然后就按照现在这个信号要开仓的位置,平掉上一单,然后开一单方向的, +# 一笔中可能有好几次信号,都按照上面的规则去判断,要保证同一时间,只会有一笔持仓, +# 打印每笔的交易详细,如果一笔中同向,输入为一条交易记录