Files
AlphaMaster/main.py
T

217 lines
8.1 KiB
Python
Raw Normal View History

2026-07-02 00:51:15 +08:00
"""
2026-07-03 12:35:29 +08:00
main.py — 多因子训练入口(分组训练)
2026-07-02 00:51:15 +08:00
使用方法:
2026-07-03 12:35:29 +08:00
python main.py # 按 SYMBOL_GROUPS 分组训练(默认推荐)
2026-07-03 14:55:57 +08:00
python main.py --offline # 仅使用本地缓存,不连接 MT5
2026-07-03 12:35:29 +08:00
python main.py --single XAUUSD # 只训练单个品种
python main.py --cross-section # 所有品种一起训练(截面)
python main.py --group risk # 只训练 risk 组
2026-07-02 00:51:15 +08:00
2026-07-03 12:35:29 +08:00
分组说明(Config.SYMBOL_GROUPS):
forex 组(EURUSD, USDJPY):外汇,美元方向因子,2品种×11000=22000样本
risk 组(XAUUSD, US100.cash, US500.cash):风险资产,3品种×11000=33000样本
2026-07-02 00:51:15 +08:00
"""
2026-07-03 12:35:29 +08:00
import sys, pathlib, json
2026-07-03 01:15:41 +08:00
2026-07-03 14:55:57 +08:00
# 无控制台环境(如 Start-Process -WindowStyle Hidden)下,sys.stdout 可能为 None,
# 导致 tqdm.write 报错。此时重定向到日志文件。
if sys.stdout is None or sys.stderr is None:
_log_path = r"D:\素材\自动挖因子\training_stdout.log"
_log_fp = open(_log_path, "a", encoding="utf-8")
if sys.stdout is None:
sys.stdout = _log_fp
if sys.stderr is None:
sys.stderr = _log_fp
2026-07-02 00:51:15 +08:00
from config import Config
from data_pipeline.data_manager import MT5DataManager
from data_pipeline.fetcher import MT5DataFetcher
2026-07-03 01:15:41 +08:00
from data_pipeline.single_symbol_manager import SingleSymbolDataManager
2026-07-02 00:51:15 +08:00
from model_core.engine import AlphaEngine
2026-07-03 01:15:41 +08:00
from model_core.config import ModelConfig
2026-07-03 12:35:29 +08:00
from model_core.vocab import VOCAB_VERSION
2026-07-03 01:15:41 +08:00
2026-07-03 12:35:29 +08:00
class GroupDataManager:
"""品种分组数据视图,兼容 AlphaEngine 接口(N = 组内品种数)。"""
def __init__(self, multi_manager, symbols: list[str]):
self._multi = multi_manager
self._symbols = [s for s in symbols if s in multi_manager.symbols]
self._idxs = [multi_manager.symbols.index(s) for s in self._symbols]
@property
def symbols(self): return list(self._symbols)
@property
def raw_dict(self):
full = self._multi.raw_dict
return {k: v[self._idxs] for k, v in full.items()}
@property
def feat_tensor(self):
from model_core.features import MT5FeatureEngineer
return MT5FeatureEngineer.compute_features(self.raw_dict)
@property
def target_ret(self):
return self._multi.target_ret[self._idxs]
@property
def bar_time(self):
return self._multi.bar_time[self._idxs]
def save_group_strategy(engine: AlphaEngine, group_name: str, symbols: list[str]):
"""保存分组策略:group 总文件 + 各品种 best_*.json。"""
pathlib.Path("strategies").mkdir(exist_ok=True)
# 分组总文件
gp = pathlib.Path("strategies") / f"best_group_{group_name}.json"
gp.write_text(json.dumps({
"vocab_version": VOCAB_VERSION,
"group": group_name,
"symbols": symbols,
"formula": engine.best_formula,
"best_score": engine.best_score,
}, indent=2))
# 各品种文件(runner 按品种名加载)
for sym in symbols:
sp = pathlib.Path("strategies") / f"best_{sym}.json"
sp.write_text(json.dumps({
"vocab_version": VOCAB_VERSION,
"symbol": sym,
"group": group_name,
"formula": engine.best_formula,
"best_score": engine.best_score,
"source": f"group_{group_name}",
}, indent=2))
print(f" 已保存: best_group_{group_name}.json + {len(symbols)} 个品种文件")
def train_group(fetcher, group_name: str, symbols: list[str], offline: bool):
"""训练一个品种组,使用组内独立的 DataManager(不与其他组取时间交集)。
2026-07-05 09:48:30 +08:00
自动检测是否有可续训的 checkpoint:若存在则从断点继续,否则从 step 0 开始。
"""
2026-07-03 12:35:29 +08:00
print(f"\n{'─'*60}")
print(f" [{group_name}] 组: {symbols}")
print(f"{'─'*60}")
# 临时覆盖 SYMBOLS 只加载本组品种
original_symbols = Config.SYMBOLS[:]
Config.SYMBOLS = [s for s in symbols if s in original_symbols or True]
try:
group_mgr = MT5DataManager(fetcher)
group_mgr.load()
actual_symbols = group_mgr.symbols
T = group_mgr.raw_dict["open"].shape[1]
print(f" 独立加载: {actual_symbols} T={T} bars")
except Exception as e:
print(f" [错误] 数据加载失败: {e}")
Config.SYMBOLS = original_symbols
return None
finally:
Config.SYMBOLS = original_symbols # 恢复原始配置
if not actual_symbols:
2026-07-03 12:35:29 +08:00
print(f" [跳过] 无有效品种")
return None
engine = AlphaEngine(data_manager=group_mgr, target_symbol=group_name)
2026-07-05 09:48:30 +08:00
# ── 自动续训:检测最新 checkpoint ────────────────────────────────
import glob as _glob
ckpt_pattern = str(pathlib.Path("checkpoints") / f"ckpt_{group_name}_step_*.pt")
ckpt_files = sorted(_glob.glob(ckpt_pattern))
start_step = 0
if ckpt_files:
latest_ckpt = ckpt_files[-1]
try:
start_step = engine.load_checkpoint(latest_ckpt)
print(f" [续训] 从 {latest_ckpt} 恢复,start_step={start_step}")
except Exception as e:
print(f" [警告] checkpoint 加载失败({e}),从头开始训练")
start_step = 0
else:
print(f" [新训] 未找到 checkpoint,从 step 0 开始")
if start_step >= ModelConfig.TRAIN_STEPS:
print(f" [完成] {group_name} 已完成全部 {ModelConfig.TRAIN_STEPS} 步,跳过训练")
save_group_strategy(engine, group_name, actual_symbols)
return engine
engine.train(start_step=start_step)
save_group_strategy(engine, group_name, actual_symbols)
2026-07-03 12:35:29 +08:00
return engine
def main():
cross = "--cross-section" in sys.argv
single = "--single" in sys.argv
2026-07-03 14:55:57 +08:00
offline = "--offline" in sys.argv
2026-07-03 12:35:29 +08:00
grp_only = None
if "--group" in sys.argv:
idx = sys.argv.index("--group")
grp_only = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else None
single_sym = [s for s in sys.argv[1:] if not s.startswith("--")] or None
mode = "截面" if cross else ("单品种" if single else "分组")
2026-07-03 01:15:41 +08:00
print(f"{'='*60}")
2026-07-03 14:55:57 +08:00
print(f" AlphaGPT 训练 [{mode}模式]" + (" [离线缓存]" if offline else ""))
2026-07-03 01:15:41 +08:00
print(f" TRAIN_STEPS={ModelConfig.TRAIN_STEPS} "
f"MAX_FORMULA_LEN={ModelConfig.MAX_FORMULA_LEN} "
f"BATCH_SIZE={ModelConfig.BATCH_SIZE}")
2026-07-03 12:35:29 +08:00
print(f" SYMBOLS={Config.SYMBOLS}")
2026-07-03 01:15:41 +08:00
print(f"{'='*60}")
2026-07-03 14:55:57 +08:00
with MT5DataFetcher(offline=offline) as fetcher:
2026-07-03 12:35:29 +08:00
if single and single_sym:
# 单品种模式:用全局 multi_mgr
multi_mgr = MT5DataManager(fetcher)
multi_mgr.load()
2026-07-03 12:35:29 +08:00
sym = single_sym[0]
s_mgr = SingleSymbolDataManager(multi_mgr, sym)
engine = AlphaEngine(data_manager=s_mgr, target_symbol=sym)
2026-07-03 01:15:41 +08:00
engine.train()
2026-07-03 12:35:29 +08:00
elif cross:
multi_mgr = MT5DataManager(fetcher)
multi_mgr.load()
2026-07-03 12:35:29 +08:00
engine = AlphaEngine(data_manager=multi_mgr, target_symbol=None)
engine.train()
save_group_strategy(engine, "cross_section", multi_mgr.symbols)
2026-07-03 12:01:30 +08:00
else:
# 分组训练:每组独立加载,不共享 DataManager
2026-07-03 12:35:29 +08:00
groups = getattr(Config, "SYMBOL_GROUPS", {
"forex": ["EURUSD", "USDJPY"],
"risk": ["XAUUSD", "US100.cash", "US500.cash"],
})
if grp_only:
groups = {grp_only: groups[grp_only]} if grp_only in groups else groups
2026-07-03 12:01:30 +08:00
results = {}
2026-07-03 12:35:29 +08:00
for gname, gsyms in groups.items():
eng = train_group(fetcher, gname, gsyms, offline)
2026-07-03 12:35:29 +08:00
if eng:
results[gname] = {
"score": eng.best_score,
"formula": eng._decode_formula(eng.best_formula),
}
2026-07-03 12:01:30 +08:00
print(f"\n{'='*60}")
2026-07-03 12:35:29 +08:00
print(f" 分组训练完成")
2026-07-03 12:01:30 +08:00
print(f"{'='*60}")
2026-07-03 12:35:29 +08:00
for gname, r in results.items():
print(f" [{gname}]: score={r['score']:.4f}")
print(f" {r['formula']}")
2026-07-03 12:01:30 +08:00
print()
2026-07-03 01:15:41 +08:00
2026-07-02 00:51:15 +08:00
if __name__ == "__main__":
2026-07-03 12:35:29 +08:00
main()