Skip to content

Commit aef341e

Browse files
committed
feat(backtest): 实现国际化支持并改进回测配置
- 在批量回测中引入 i18n 模块,替换硬编码的中文文本 - 修改 BacktestConfig 类,添加市场选择、T+1覆盖和语言配置选项 - 将基准代码设置为可选,默认为空字符串使用市场默认基准 - 更新 CSV 导出模块,使用国际化消息替换打印文本 - 重构批量回测运行器,使用翻译函数处理进度和汇总信息
1 parent 7789b82 commit aef341e

5 files changed

Lines changed: 659 additions & 0 deletions

File tree

src/simtradelab/i18n.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
simtradelab i18n — 线程安全的翻译模块
4+
5+
使用方式:
6+
from simtradelab.i18n import set_locale, t
7+
set_locale("en")
8+
log.info(t("bt.start", strategy="my_strategy"))
9+
"""
10+
11+
import json
12+
import threading
13+
from pathlib import Path
14+
15+
_locales: dict[str, dict[str, str]] = {}
16+
_thread_local = threading.local()
17+
_DEFAULT_LOCALE = "zh"
18+
19+
20+
def _load_locales() -> None:
21+
locales_dir = Path(__file__).parent / "locales"
22+
for path in locales_dir.glob("*.json"):
23+
with open(path, "r", encoding="utf-8") as f:
24+
_locales[path.stem] = json.load(f)
25+
26+
27+
def set_locale(locale: str) -> None:
28+
_thread_local.locale = locale
29+
30+
31+
def get_locale() -> str:
32+
return getattr(_thread_local, "locale", _DEFAULT_LOCALE)
33+
34+
35+
def t(key: str, **params: object) -> str:
36+
locale = get_locale()
37+
translations = _locales.get(locale) or _locales.get(_DEFAULT_LOCALE, {})
38+
template = translations.get(key)
39+
if template is None:
40+
template = _locales.get(_DEFAULT_LOCALE, {}).get(key, key)
41+
return template.format(**params) if params else template
42+
43+
44+
_load_locales()

src/simtradelab/locales/de.json

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
{
2+
"bt.checking_strategy": "Strategie wird überprüft...",
3+
"bt.validation_failed": "\nStrategievalidierung fehlgeschlagen:",
4+
"bt.auto_fixed": "Python 3.5-Kompatibilitätsprobleme automatisch behoben",
5+
"bt.validation_passed": "Strategievalidierung bestanden\n",
6+
"bt.analyzing_deps": "Analyse der Strategiedatenabhängigkeiten...",
7+
"bt.data_cached": "Daten bereits im Speicher, kein Neuladen nötig\n",
8+
"bt.log_file": "Logdatei: {path}",
9+
"bt.strategy_loaded": "✓ Strategie geladen\n",
10+
"bt.initializing": "Strategie wird initialisiert...",
11+
"bt.starting_loop": "\nBacktest-Schleife wird gestartet...",
12+
"bt.start": "Backtest gestartet, Strategie: {strategy}",
13+
"bt.period": "Backtest-Zeitraum: {start} bis {end}",
14+
"bt.empty_dates": "Fehler: Handelstage-Sequenz ist leer! Datumsbereich prüfen: {start} - {end}",
15+
"bt.benchmark_range": "Benchmark-Datenbereich: {start} - {end}",
16+
"bt.trading_days": "Handelstage: {count}\n",
17+
"bt.interrupted": "Backtest unterbrochen",
18+
"bt.benchmark_index": "Benchmark (Index): {code}",
19+
"bt.benchmark_stock": "Benchmark (Aktie): {code}",
20+
"bt.benchmark_fallback": "Benchmark {code} nicht gefunden, verwende Standard: {fallback}",
21+
"bt.chart_saved": "Diagramm gespeichert: {path}",
22+
"bt.log_saved": "\nLog gespeichert: {path}",
23+
"bt.signal_received": "\n\nUnterbrechungssignal empfangen...",
24+
25+
"perf.complete": "✓ {name} abgeschlossen, Dauer: {time}",
26+
"perf.elapsed_ms": "{m}min {s}s",
27+
"perf.elapsed_s": "{s}s",
28+
"perf.elapsed_zero": "0s",
29+
"perf.batch_timing": " [PERF] {name}(Batch {count}) Dauer: {time}s",
30+
"perf.timing": " [PERF] {name} Dauer: {time}s",
31+
"perf.name.total": "Gesamt (inkl. Datenladung)",
32+
"perf.name.data_load": "Datenladung",
33+
"perf.name.backtest": "Backtest-Ausführung",
34+
"perf.name.chart": "Diagrammerstellung",
35+
"perf.name.adj_pre_create": "Vorkursbereinigung Cache-Erstellung",
36+
"perf.name.adj_pre_load": "Vorkursbereinigung Cache-Laden",
37+
"perf.name.adj_post_create": "Nachkursbereinigung Cache-Erstellung",
38+
"perf.name.adj_post_load": "Nachkursbereinigung Cache-Laden",
39+
40+
"report.header": "Backtest-Bericht {start}-{end} | Zeitraum: {days}T | Dauer: {time}",
41+
"report.returns": "Gesamtrendite: {total}% | Jährlich: {annual}% | Max. Drawdown: {drawdown}%",
42+
"report.ratios": "Sharpe: {sharpe} | Info-Ratio: {info} | Kapital: {start_val} → {end_val}",
43+
"report.ratios2": "Sortino: {sortino} | Calmar: {calmar}",
44+
"report.benchmark": "vs {name}: Überrendite {excess}% | Alpha {alpha}% | Beta {beta}",
45+
"report.win_stats": "Gewinntage: {wins}/{total}T ({rate}%) | G/V-Verhältnis: {plr} | Positionen: {avg}(max {max})",
46+
47+
"engine.start": "Strategieausführung gestartet: {strategy}",
48+
"engine.completed": "Strategieausführung abgeschlossen",
49+
"engine.failed": "Strategieausführung fehlgeschlagen: {error}",
50+
"engine.initialize": "Initialisierungsphase wird ausgeführt",
51+
"engine.cancelled": "Backtest abgebrochen",
52+
"engine.daily_task_failed": "run_daily-Aufgabe fehlgeschlagen: {error}",
53+
"engine.daily_task_time_failed": "run_daily-Aufgabe ({time}) fehlgeschlagen: {error}",
54+
"engine.phase_failed": "Lebenszyklusphase {phase} fehlgeschlagen: {error}",
55+
"engine.func_failed": "{func} Ausführung fehlgeschlagen: {error}",
56+
"engine.dividend_failed": "Dividenden-/Bezugsrechtsverarbeitung fehlgeschlagen: {error}",
57+
"engine.resetting": "Strategiezustand wird zurückgesetzt",
58+
59+
"order.price_no_data": "get_execution_price fehlgeschlagen | {stock} nicht in Datenquelle",
60+
"order.volume_zero": "Order storniert: Nullvolumen für {stock}",
61+
"order.price_abnormal": "get_execution_price fehlgeschlagen | {stock} abnormaler Preis: {price}",
62+
"order.price_error": "get_execution_price Fehler | {stock}: {error}",
63+
"order.buy_no_cash": "Kauf fehlgeschlagen | {stock} | Unzureichendes Guthaben (benötigt {cost}, verfügbar {cash})",
64+
"order.sell_no_position": "Verkauf fehlgeschlagen | {stock} | Keine Position",
65+
"order.sell_t1_limit": "Verkauf fehlgeschlagen | {stock} | T+1-Beschränkung, Tagesgleicher Verkauf nicht möglich",
66+
"order.t1_truncate": "T+1-Kürzung: {stock} Verkauf {amount} → {available} Stück",
67+
"order.sell_insufficient": "Verkauf fehlgeschlagen | {stock} | Unzureichende Position (gehalten {held}, versucht {amount})",
68+
"order.dividend_tax_pay": "Dividendensteuer | {stock} | Nachzahlung: {amount}",
69+
"order.dividend_tax_refund": "Dividendensteuer | {stock} | Erstattung: {amount}",
70+
"order.no_price": "Order fehlgeschlagen | {stock} | Kein Preis verfügbar",
71+
72+
"api.get_price_empty": "get_price leer | stocks={stocks}, frequency={frequency}, fq={fq}",
73+
"api.get_price_no_data": "get_price keine Daten | stock={stock}, frequency={frequency}, fq={fq}",
74+
"api.get_history_empty": "get_history leer | stocks={stocks}, count={count}, frequency={frequency}, fq={fq}",
75+
"api.order_no_price": "Order fehlgeschlagen {stock} | Kein Preis verfügbar",
76+
"api.buy_no_cash": "Kauf fehlgeschlagen | {stock} | Unzureichendes Guthaben",
77+
"api.buy_adjusted": "Unzureichendes Guthaben, {stock} Ordermenge auf {amount} angepasst",
78+
"api.star_min_200": "Order storniert: STAR-Markt Mindestorder 200 Stück",
79+
"api.order_buy": "Order #{order_id} | {stock} | Kauf {amount} Stück",
80+
"api.order_sell": "Order #{order_id} | {stock} | Verkauf {amount} Stück",
81+
"api.order_value_insufficient": "Order fehlgeschlagen | {stock} | Betrag unzureichend für {min_lot} Stück (zugewiesen {value}, Preis {price}, Guthaben {cash})",
82+
"api.sell_no_position": "Verkauf fehlgeschlagen | {stock} | Keine Position",
83+
"api.sell_value_insufficient": "Order fehlgeschlagen | {stock} | Verkaufsbetrag unzureichend für {min_lot} Stück (Betrag {value}, Preis {price})",
84+
"api.benchmark_index": "Benchmark gesetzt (Index): {benchmark}",
85+
"api.benchmark_stock": "Benchmark gesetzt (Aktie/Index): {benchmark}",
86+
"api.benchmark_not_found": "Benchmark {benchmark} nicht in Index- oder Aktiendaten gefunden, aktueller Benchmark beibehalten",
87+
"api.pos_not_list": "set_yesterday_position Parameter muss eine Liste sein",
88+
"api.set_position": "Ausgangsposition gesetzt: {stock}, Menge: {amount}, Kosten: {cost}",
89+
"api.prebuilding": "Datumsindex für {count} Aktien wird erstellt...",
90+
"api.prebuild_progress": " Erstellt {done}/{total}",
91+
"api.prebuild_done": " Fertig! Index für {count} Aktien erstellt",
92+
"api.read_failed": "Lesen von {table} fehlgeschlagen: stock={stock}, fields={fields}, error={error}",
93+
94+
"data.migrated": "Datenverzeichnis migriert: {old} → {new}",
95+
"data.using_cached": "Verwende zwischengespeicherte Daten (resident im Speicher)",
96+
"data.first_load": "Erstmaliges Laden der Daten",
97+
"data.data_path": "Datenpfad: {path}",
98+
"data.reading": "Daten werden gelesen...",
99+
"data.reading_meta": "Metadaten werden gelesen...",
100+
"data.price_loading": "\n[1] Aktienkurse ({count})...",
101+
"data.price_skip": "\n[1] Aktienkurse (übersprungen)",
102+
"data.minute_loading": "[1.1] Minutendaten ({count})...",
103+
"data.valuation_loading": "[2] Bewertungsdaten ({count})...",
104+
"data.valuation_skip": "[2] Bewertungsdaten (übersprungen)",
105+
"data.fundamentals_loading": "[3] Fundamentaldaten ({count}, verzögert)...",
106+
"data.fundamentals_skip": "[3] Fundamentaldaten (übersprungen)",
107+
"data.exrights_loading": "[4] Bezugsrechtsdaten ({count}, verzögert)...",
108+
"data.exrights_skip": "[4] Bezugsrechtsdaten (übersprungen)",
109+
"data.loaded_types": "\nGeladen: {types}",
110+
"data.benchmarks": "Verfügbare Benchmarks ({count}): {list} ...",
111+
"data.complete": "✓ Daten geladen\n",
112+
"data.supplement": "Fehlende Daten werden nachgeladen: {types}",
113+
"data.supplement_price": " Lade Aktienkurse ({count})...",
114+
"data.supplement_valuation": " Lade Bewertungsdaten ({count})...",
115+
"data.supplement_fundamentals": " Lade Fundamentaldaten ({count}, verzögert)...",
116+
"data.supplement_exrights": " Lade Bezugsrechtsdaten ({count}, verzögert)...",
117+
"data.supplement_minute": " Lade Minutendaten ({count})...",
118+
"data.not_started": "Datenserver nicht gestartet",
119+
"data.shutting_down": "Datenserver wird heruntergefahren...",
120+
"data.shutdown_done": "✓ Datenserver geschlossen, Speicher freigegeben\n",
121+
"data.will_reload": "Daten werden beim nächsten Lauf neu geladen\n",
122+
"data.status_stopped": "Datenserver-Status: Nicht gestartet",
123+
"data.status_running": "Datenserver-Status: Läuft",
124+
"data.status_stocks": " - Aktiendaten: {count}",
125+
"data.status_cached": " - Zwischengespeichert: {count}",
126+
"data.status_exrights": " - Bezugsrechte-Cache: {count}",
127+
"data.status_mode": " - Speichermodus: {mode}",
128+
"data.preload_mode": "Vorgeladen",
129+
"data.lazy_mode": "Verzögert",
130+
"data.parallel_loading": " Lade {count} Aktien mit {workers} Prozessen...",
131+
"data.parallel_done": " ✓ Laden abgeschlossen, {time}s",
132+
133+
"deps.failed": "Strategieanalyse fehlgeschlagen: {error}, lade alle Daten",
134+
"deps.result": "Strategiedaten-Abhängigkeiten: {items}",
135+
"deps.none": "Strategiedaten-Abhängigkeiten: Keine",
136+
"deps.price": "Kurse",
137+
"deps.valuation": "Bewertung",
138+
"deps.fundamentals": "Fundamentaldaten({tables})",
139+
"deps.exrights": "Bezugsrechte",
140+
141+
"export.done": "CSV exportiert:",
142+
"export.daily": " Tagesstatistik: {path}",
143+
"export.positions": " Positionshistorie: {path}",
144+
145+
"batch.period": "\n[{i}/{total}] Backtest-Zeitraum: {start} → {end}",
146+
"batch.summary": "Batch-Backtest Zusammenfassung",
147+
148+
"opt.bt_failed": "Backtest fehlgeschlagen: {error}",
149+
"opt.resume_found": "\nVorhandener Fortschritt gefunden: {count} abgeschlossene Versuche (inkl. Pruning)",
150+
"opt.resume_continue": "Optimierung wird fortgesetzt bis zu {count} Versuche...",
151+
"opt.resume_state": "Early-Stop wiederhergestellt: Bester={score}, Keine Verbesserung={count}/{patience}",
152+
"opt.all_done": "Alle {count} Versuche abgeschlossen, keine weitere Optimierung nötig",
153+
"opt.new_study": "\nNeue Optimierungsstudie erstellt: {name}",
154+
"opt.early_stop_pruned": "Early-Stop! {patience} Versuche ohne Verbesserung (inkl. Pruning)",
155+
"opt.best_score": "Bester Score: {score}",
156+
"opt.better_found": "\n✓ Bessere Lösung gefunden: {value} (+{improvement})",
157+
"opt.counter_reset": " Zähler zurückgesetzt: 0/{patience}",
158+
"opt.no_improvement": " Keine Verbesserung ({count}/{patience}): aktuell={current}, bester={best}",
159+
"opt.early_stop": "Early-Stop! {patience} Versuche ohne Verbesserung",
160+
"opt.starting": "\nOptimierung gestartet, bis zu {count} Versuche...",
161+
"opt.space_size": "Parameterraum: {size} Kombinationen\n",
162+
"opt.holdout_title": "\nHoldout-Validierung: {start} bis {end}",
163+
"opt.holdout_score": "Holdout-Score: {score}",
164+
"opt.stats_title": "\n[Optimierungsstatistik]",
165+
"opt.stats_total": " Versuche gesamt: {count}",
166+
"opt.stats_completed": " Abgeschlossen: {count}",
167+
"opt.stats_pruned": " Pruning: {count}",
168+
"opt.stats_failed": " Fehlgeschlagen: {count}",
169+
"opt.no_completed": "\nWarnung: Keine erfolgreich abgeschlossenen Versuche",
170+
"opt.best_trial_id": " Bester Versuch ID: {id}",
171+
"opt.best_params_title": "\n[Beste Parameter]",
172+
"opt.perf_title": "\n[Performance-Score]",
173+
"opt.combined_score": " Gesamtscore: {score}",
174+
"opt.train_score": " Trainings-Score: {score}",
175+
"opt.test_score": " Test-Score: {score}",
176+
"opt.test_std": " Test-Std: {value}",
177+
"opt.train_test_gap": " Train/Test-Diff: {value}",
178+
"opt.overfit_ratio": " Overfitting-Ratio: {value}",
179+
"opt.strategy_saved": "Optimierte Strategie gespeichert: {path}",
180+
"opt.holdout_test": "Holdout-Test - verwende {year} Out-of-Sample-Daten"
181+
}

0 commit comments

Comments
 (0)