Files
AlphaMaster/main.py
T
2026-07-05 09:48:30 +08:00

217 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
main.py — 多因子训练入口(分组训练)
使用方法:
python main.py # 按 SYMBOL_GROUPS 分组训练(默认推荐)
python main.py --offline # 仅使用本地缓存,不连接 MT5
python main.py --single XAUUSD # 只训练单个品种
python main.py --cross-section # 所有品种一起训练(截面)
python main.py --group risk # 只训练 risk 组
分组说明(Config.SYMBOL_GROUPS):
forex 组(EURUSD, USDJPY):外汇,美元方向因子,2品种×11000=22000样本
risk 组(XAUUSD, US100.cash, US500.cash):风险资产,3品种×11000=33000样本
"""
import sys, pathlib, json
# 无控制台环境(如 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
from config import Config
from data_pipeline.data_manager import MT5DataManager
from data_pipeline.fetcher import MT5DataFetcher
from data_pipeline.single_symbol_manager import SingleSymbolDataManager
from model_core.engine import AlphaEngine
from model_core.config import ModelConfig
from model_core.vocab import VOCAB_VERSION
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(不与其他组取时间交集)。
自动检测是否有可续训的 checkpoint:若存在则从断点继续,否则从 step 0 开始。
"""
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:
print(f" [跳过] 无有效品种")
return None
engine = AlphaEngine(data_manager=group_mgr, target_symbol=group_name)
# ── 自动续训:检测最新 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)
return engine
def main():
cross = "--cross-section" in sys.argv
single = "--single" in sys.argv
offline = "--offline" in sys.argv
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 "分组")
print(f"{'='*60}")
print(f" AlphaGPT 训练 [{mode}模式]" + (" [离线缓存]" if offline else ""))
print(f" TRAIN_STEPS={ModelConfig.TRAIN_STEPS} "
f"MAX_FORMULA_LEN={ModelConfig.MAX_FORMULA_LEN} "
f"BATCH_SIZE={ModelConfig.BATCH_SIZE}")
print(f" SYMBOLS={Config.SYMBOLS}")
print(f"{'='*60}")
with MT5DataFetcher(offline=offline) as fetcher:
if single and single_sym:
# 单品种模式:用全局 multi_mgr
multi_mgr = MT5DataManager(fetcher)
multi_mgr.load()
sym = single_sym[0]
s_mgr = SingleSymbolDataManager(multi_mgr, sym)
engine = AlphaEngine(data_manager=s_mgr, target_symbol=sym)
engine.train()
elif cross:
multi_mgr = MT5DataManager(fetcher)
multi_mgr.load()
engine = AlphaEngine(data_manager=multi_mgr, target_symbol=None)
engine.train()
save_group_strategy(engine, "cross_section", multi_mgr.symbols)
else:
# 分组训练:每组独立加载,不共享 DataManager
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
results = {}
for gname, gsyms in groups.items():
eng = train_group(fetcher, gname, gsyms, offline)
if eng:
results[gname] = {
"score": eng.best_score,
"formula": eng._decode_formula(eng.best_formula),
}
print(f"\n{'='*60}")
print(f" 分组训练完成")
print(f"{'='*60}")
for gname, r in results.items():
print(f" [{gname}]: score={r['score']:.4f}")
print(f" {r['formula']}")
print()
if __name__ == "__main__":
main()