commit 5dfb8d7b1a15b878d9537d720376b38ac51d9b2c Author: lintaogood Date: Wed Mar 12 19:54:53 2025 +0800 提交基础内容 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..b737c50 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1282df1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bb93fdb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/土信号.iml b/.idea/土信号.iml new file mode 100644 index 0000000..74d515a --- /dev/null +++ b/.idea/土信号.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/土-优化01策略.txt b/土-优化01策略.txt new file mode 100644 index 0000000..c10e5cb --- /dev/null +++ b/土-优化01策略.txt @@ -0,0 +1,41 @@ +X_1:=LLV(LOW,10); +X_2:=HHV(HIGH,25); +X_5:=3.5; +X_6:=REF(EMA((CLOSE-X_1)/(X_2-X_1)*4,4),1); +X_7:=IF(EXIST(CROSS(X_6,X_5),5),0,1); +X_10:=(REF(CLOSE,1)-REF(CLOSE,2))/REF(CLOSE,2)*100; +X_11:=2*(REF(CLOSE+HIGH+LOW,1)+OPEN)*100; +X_12:=(X_11/EMA(X_11,4)-1)*100; +X_13:=X_7*X_12; +X_14:=DMA(EMA(OPEN,12),SUM(REF(VOL,1),5)/3/CAPITAL); +X_15:=X_7*(OPEN-X_14)/X_14*200; +X_16:=0-(HHV(EMA(OPEN,5),14)-OPEN)/OPEN*5000; +X_17:=REF(CLOSE+LOW+3*OPEN+HIGH,1)/6; +X_18:=20*X_17+19*REF(X_17,1)+18*REF(X_17,2)+17*REF(X_17,3)+16*REF(X_17,4)+15*REF(X_17,5)+14*REF(X_17,6)+13*REF(X_17,7)+12*REF(X_17,8)+11*REF(X_17,9); +X_19:=10*REF(X_17,10)+9*REF(X_17,11)+8*REF(X_17,12)+7*REF(X_17,13)+6*REF(X_17,14)+5*REF(X_17,15)+4*REF(X_17,16)+3*REF(X_17,17)+2*REF(X_17,18)+REF(X_17,20); +X_20:=(X_18+X_19)/210; +X_21:=MA(X_20,6); +X_22:=(X_20-X_21)*1.2; +X_23:=(OPEN*2+REF(HIGH+LOW,1))/4*10; +X_24:=EMA(X_23,6)-EMA(X_23,55); +X_25:=EMA(X_24,6); +X_26:=(X_24-X_25)*0.06; +X_27:=X_22+X_26; +X_28:=X_27>0; +X_29:=MA(OPEN,3); +X_30:=MA(OPEN,13); +X_31:=MA(OPEN,34); +X_32:=X_29>X_30 AND X_31>=REF(X_31,1); +X_33:=EMA(OPEN,12); +X_34:=EMA(OPEN,50); +X_35:=X_33-X_34>0; +X_36:=OPEN/REF(CLOSE,1)-1; +X_37:=IF(X_32,2,0)+IF(X_28,2,0)+IF(X_35,2,0)+X_36; +X_38:=(X_7*X_37/10+X_16/1000)*100+25; +X_39:=X_13+X_15+X_38; +X_40:=NOT(NAMELIKE(313) OR NAMELIKE(314) OR NAMELIKE(315) OR VOL=0); +X_41:=SLOPE(X_39,2); +X_42:=SLOPE(X_6,2); +X_43:=SUM(X_10,2)*X_7; + +土:IF(X_43>8 AND X_40,X_41*0.02-X_42*X_7,DRAWNULL)*X_7,COLORCYAN; \ No newline at end of file diff --git a/土-原始策略.txt b/土-原始策略.txt new file mode 100644 index 0000000..2314afa --- /dev/null +++ b/土-原始策略.txt @@ -0,0 +1,52 @@ +X_1:=LLV(LOW,10); +X_2:=HHV(HIGH,25); +X_4:=3.2; +X_5:=3.5; +X_6:=REF(EMA((CLOSE-X_1)/(X_2-X_1)*4,4),1); +X_7:=IF(EXIST(CROSS(X_6,X_5),5),0,1); +X_10:=(REF(CLOSE,1)-REF(CLOSE,2))/REF(CLOSE,2)*100; +X_11:=2*(REF(CLOSE+HIGH+LOW,1)+OPEN)*100; +X_12:=(X_11/EMA(X_11,4)-1)*100; +X_13:=X_7*X_12; +X_14:=DMA(EMA(OPEN,12),SUM(REF(VOL,1),5)/3/CAPITAL); +X_15:=X_7*(OPEN-X_14)/X_14*200; +X_16:=0-(HHV(EMA(OPEN,5),14)-OPEN)/OPEN*5000; +X_17:=REF(CLOSE+LOW+3*OPEN+HIGH,1)/6; +X_18:=20*X_17+19*REF(X_17,1)+18*REF(X_17,2)+17*REF(X_17,3)+16*REF(X_17,4)+15*REF(X_17,5)+14*REF(X_17,6)+13*REF(X_17,7)+12*REF(X_17,8)+11*REF(X_17,9); +X_19:=10*REF(X_17,10)+9*REF(X_17,11)+8*REF(X_17,12)+7*REF(X_17,13)+6*REF(X_17,14)+5*REF(X_17,15)+4*REF(X_17,16)+3*REF(X_17,17)+2*REF(X_17,18)+REF(X_17,20); +X_20:=(X_18+X_19)/210; +X_21:=MA(X_20,6); +X_22:=(X_20-X_21)*1.2; +X_23:=(OPEN*2+REF(HIGH+LOW,1))/4*10; +X_24:=EMA(X_23,6)-EMA(X_23,55); +X_25:=EMA(X_24,6); +X_26:=(X_24-X_25)*0.06; +X_27:=X_22+X_26; +X_28:=X_27>0; +X_29:=MA(OPEN,3); +X_30:=MA(OPEN,13); +X_31:=MA(OPEN,34); +X_32:=X_29>X_30 AND X_31>=REF(X_31,1); +X_33:=EMA(OPEN,12); +X_34:=EMA(OPEN,50); +X_35:=X_33-X_34>0; +X_36:=OPEN/REF(CLOSE,1)-1; +X_37:=IF(X_32,2,0)+IF(X_28,2,0)+IF(X_35,2,0)+X_36; +X_38:=(X_7*X_37/10+X_16/1000)*100+25; +X_39:=X_13+X_15+X_38; +量比:= VOL / REF(MA(VOL,5),1); +X_44:= REF(VOL,1)>0 AND 量比>1; +X_40:= NOT(NAMELIKE(313) OR NAMELIKE(314) OR NAMELIKE(315)) AND REF(VOL,1)>0 AND VOL>0; +X_41:=SLOPE(X_39,2); +X_42:=SLOPE(X_6,2); +X_43:=SUM(X_10,2)*X_7; + + + +ZB100:=REF(CLOSE,1)>REF(CLOSE,2)*1.09 AND REF(HIGH,1)=REF(CLOSE,1); +今开%:(OPEN-REF(CLOSE,1))/REF(CLOSE,1)*100; +土:IF(X_43>8 AND X_40,X_41*0.02-X_42*X_7,DRAWNULL)*X_7,COLORCYAN; +昨板数:BARSLASTCOUNT(ZB100),COLORRED; +细分行业:DRAWTEXT(1,0,HYBLOCK),COLOR00C0C0; + + diff --git a/土策略回测_基础01.py b/土策略回测_基础01.py new file mode 100644 index 0000000..cf80d2b --- /dev/null +++ b/土策略回测_基础01.py @@ -0,0 +1,158 @@ +import os +import pandas as pd +import numpy as np +from datetime import datetime, timedelta + +# 回测配置 +DATA_PATH = r'D:\gp_data\day' # 日线数据存放路径 +START_DATE = '20150101' # 回测开始日期 +END_DATE = '20231231' # 回测结束日期 +STOP_LOSS = 0.03 # 止损比例(3%) +COMMISSION = 0.0003 # 单边交易佣金(万分之三) + + +# 策略信号计算函数 +def calculate_signal(df): + """计算土信号""" + df = df.copy() + + # 计算基础参数 + df['X_1'] = df['low'].rolling(10, min_periods=1).min() # 10日最低价 + df['X_2'] = df['high'].rolling(25, min_periods=1).max() # 25日最高价 + df['X_5'] = 3.5 # 固定阈值 + + # 标准化波动率(X_6) + df['X_6'] = ((df['close'] - df['X_1']) / (df['X_2'] - df['X_1']) * 4) + df['X_6'] = df['X_6'].ewm(span=4, adjust=False).mean().shift(1) # 4日EMA并滞后1日 + + # 波动率过滤(X_7) + df['cross_flag'] = (df['X_6'] > df['X_5']).astype(int) # 当前是否突破 + df['cross_flag_shift'] = df['cross_flag'].shift(1) # 前一日状态 + df['cross_event'] = (df['cross_flag'] > df['cross_flag_shift']).astype(int) # 上穿事件 + df['X_7'] = df['cross_event'].rolling(5).max().replace(1, 0).shift(1) # 过去5日有上穿则为0 + + # 动量计算(X_43) + df['X_10'] = (df['close'].shift(1) - df['close'].shift(2)) / df['close'].shift(2) * 100 + df['X_43'] = df['X_10'].rolling(2).sum() * df['X_7'] + + # 复合指标(简化版X_39) + ema_open_12 = df['open'].ewm(span=12, adjust=False).mean() + df['X_15'] = df['X_7'] * (df['open'] - ema_open_12) / ema_open_12 * 200 + + # 斜率计算 + def calc_slope(series): + if len(series) < 2: return np.nan + return np.polyfit([0, 1], series.values, 1)[0] + + df['X_41'] = df['X_15'].rolling(2).apply(calc_slope, raw=False) + df['X_42'] = df['X_6'].rolling(2).apply(calc_slope, raw=False) + + # 生成最终信号 + df['signal'] = np.where( + (df['X_43'] > 8) & + (~df['ts_code'].str.startswith(('313', '314', '315'))) & # 排除ST + (df['vol'] > 0) & # 排除零成交量 + (df['X_7'] == 1), # 波动率过滤 + (df['X_41'] * 0.02 - df['X_42'] * df['X_7']) * df['X_7'], + np.nan + ) + return df + + +# 回测引擎核心逻辑 +def backtest_strategy(): + all_trades = [] + + # 遍历所有股票文件 + for filename in os.listdir(DATA_PATH): + if not filename.endswith('_daily_data.txt'): + continue + + # 读取数据 + code = filename.split('_')[0] + df = pd.read_csv( + os.path.join(DATA_PATH, filename), + sep='\t', + parse_dates=['trade_date'], + dtype={'ts_code': str} + ) + + # 过滤日期范围 + df = df[(df['trade_date'] >= pd.to_datetime(START_DATE)) & + (df['trade_date'] <= pd.to_datetime(END_DATE))] + if df.empty: + continue + + # 按日期排序并计算信号 + df = df.sort_values('trade_date') + df = calculate_signal(df) + + # 提取有效信号 + signals = df[df['signal'].notna()] + + # 逐笔交易处理 + for idx, row in signals.iterrows(): + buy_date = row['trade_date'] + buy_price = row['open'] + + # 获取次日数据 + next_day = df[df['trade_date'] > buy_date].head(1) + if next_day.empty: + continue # 无次日数据(如停牌) + + sell_date = next_day['trade_date'].values[0] + sell_high = next_day['high'].values[0] + sell_low = next_day['low'].values[0] + + # 计算止损价 + stop_price = buy_price * (1 - STOP_LOSS) + + # 确定卖出价格 + if sell_low < stop_price: + sell_price = stop_price # 触发止损 + else: + sell_price = sell_high # 按次日最高价卖出 + + # 计算收益率(扣除双边佣金) + ret = (sell_price / buy_price - 1) - 2 * COMMISSION + success = 1 if ret > 0 else 0 + + # 记录交易 + all_trades.append({ + 'code': code, + 'buy_date': buy_date.strftime('%Y%m%d'), + 'sell_date': sell_date.strftime('%Y%m%d'), + 'buy_price': buy_price, + 'sell_price': sell_price, + 'return': ret, + 'success': success, + 'hold_days': 1 + }) + + # 绩效分析 + if not all_trades: + print("未产生任何交易信号") + return + + results = pd.DataFrame(all_trades) + + # 关键指标计算 + total_trades = len(results) + win_rate = results['success'].mean() + avg_return = results['return'].mean() + annualized_return = avg_return * 240 # 假设年240个交易日 + + # 输出结果 + print(f"回测期间:{START_DATE} 至 {END_DATE}") + print(f"总交易次数:{total_trades}") + print(f"胜率:{win_rate:.2%}") + print(f"单次平均收益率:{avg_return:.2%}") + print(f"年化收益率(估算):{annualized_return:.2%}") + + # 保存详细结果 + results.to_csv('trading_records.csv', index=False) + print("详细交易记录已保存至 trading_records.csv") + + +if __name__ == '__main__': + backtest_strategy() \ No newline at end of file