#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 深入诊断 - 检查卖出逻辑和移动止损问题 """ import pandas as pd import numpy as np import sys sys.path.insert(0, '/root/.openclaw/workspace/quant') # 加载数据 df = pd.read_csv('/root/.openclaw/workspace/quant/cyb50_baostock.csv') df['date'] = pd.to_datetime(df['date']) df = df.set_index('date').sort_index() for col in ['open', 'high', 'low', 'close', 'volume']: df[col] = pd.to_numeric(df[col], errors='coerce') # 计算指标 df['ma10'] = df['close'].rolling(window=10).mean() df['ma30'] = df['close'].rolling(window=30).mean() df['high_20'] = df['high'].rolling(window=20).max() df['low_20'] = df['low'].rolling(window=20).min() df['ret_10'] = df['close'].pct_change(periods=10) # 买入条件 buy_cond = ( (df['close'] > df['ma10']) & (df['ma10'] > df['ma30']) & (df['close'] >= df['high_20'] * 0.995) & (df['ret_10'] > 0.02) ) # 卖出条件1: 跌破MA30 sell_cond1 = df['close'] < df['ma30'] # 卖出条件2: 创20日新低 sell_cond2 = df['close'] <= df['low_20'] * 1.005 print("="*60) print("🔍 卖出条件分析") print("="*60) print(f"\n卖出条件1 (跌破MA30): {sell_cond1.sum()} 天") print(f"卖出条件2 (创20日新低): {sell_cond2.sum()} 天") print(f"任一条件触发: {(sell_cond1 | sell_cond2).sum()} 天") # 检查卖出条件1和2的重叠 both_sell = sell_cond1 & sell_cond2 print(f"两个条件同时触发: {both_sell.sum()} 天") # 模拟交易,详细检查每次卖出原因 print("\n📈 详细交易分析:") df['signal'] = 0 df.loc[buy_cond, 'signal'] = 1 df.loc[sell_cond1 | sell_cond2, 'signal'] = -1 position = 0 entry_price = 0 peak_price = 0 capital = 1000000 trades = [] for i in range(30, len(df)): date = df.index[i] price = df['close'].iloc[i] signal = df['signal'].iloc[i] # 检查各种卖出原因 sell_ma30 = price < df['ma30'].iloc[i] sell_low20 = price <= df['low_20'].iloc[i] * 1.005 # 移动止损检查 stop_loss_triggered = False stop_loss_price = None if position > 0: if price > peak_price: peak_price = price stop_loss_price = peak_price * 0.90 if price < stop_loss_price: signal = -1 stop_loss_triggered = True # 买入 if signal == 1 and position == 0: position = 1 entry_price = price peak_price = price trades.append({ 'date': date, 'action': 'BUY', 'price': price, 'capital': capital }) # 卖出 elif signal == -1 and position == 1: pnl = (price / entry_price - 1) * capital capital += pnl hold_days = (date - trades[-1]['date']).days exit_reasons = [] if stop_loss_triggered: exit_reasons.append(f"移动止损({peak_price:.2f}→{stop_loss_price:.2f})") if sell_ma30: exit_reasons.append("跌破MA30") if sell_low20: exit_reasons.append("创20日新低") trades.append({ 'date': date, 'action': 'SELL', 'price': price, 'capital': capital, 'pnl': pnl, 'return_pct': (price / entry_price - 1) * 100, 'reasons': ' + '.join(exit_reasons), 'hold_days': hold_days }) position = 0 # 显示最近10次卖出的详细原因 print("\n最近10次卖出详情:") sell_trades = [t for t in trades if t['action'] == 'SELL'] for t in sell_trades[-10:]: print(f" {t['date'].strftime('%Y-%m-%d')}: {t['return_pct']:+.2f}% ({t['hold_days']}天)") print(f" 原因: {t['reasons']}") # 统计各卖出原因的占比 print("\n📊 卖出原因统计:") reasons_count = {} for t in sell_trades: for reason in t['reasons'].split(' + '): reasons_count[reason] = reasons_count.get(reason, 0) + 1 for reason, count in sorted(reasons_count.items(), key=lambda x: x[1], reverse=True): print(f" {reason}: {count}次 ({count/len(sell_trades)*100:.1f}%)") print("\n" + "="*60) # 检查移动止损的触发情况 print("\n📉 移动止损检查:") stop_loss_trades = [t for t in sell_trades if '移动止损' in t['reasons']] print(f" 移动止损触发次数: {len(stop_loss_trades)}") if stop_loss_trades: avg_return = np.mean([t['return_pct'] for t in stop_loss_trades]) print(f" 移动止损平均收益: {avg_return:.2f}%") print("\n 最近5次移动止损:") for t in stop_loss_trades[-5:]: print(f" {t['date'].strftime('%Y-%m-%d')}: {t['return_pct']:+.2f}%")