Skip to content

Commit 60ff9a6

Browse files
committed
kernel: TradingKernel v1 — single control plane
干净tick loop: data → signal → risk → execution → recon → monitor 安全模式: LIVE / SAFE / HALT 三态转换 失败自治: crash → 自动safe mode 控制: run() / stop() / report() 所有模块通过kernel串联, 不再是孤岛.
1 parent 41287aa commit 60ff9a6

1 file changed

Lines changed: 107 additions & 245 deletions

File tree

kernel/kernel.py

Lines changed: 107 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""TradingKernel — alpha-v1.0 系统控制平面.
22
3-
唯一主循环. 单一事实来源. 失败自治.
3+
Single control plane. 唯一主循环. 失败自治.
44
"""
55

66
from __future__ import annotations
@@ -9,280 +9,142 @@
99
import time
1010
import traceback
1111
from dataclasses import dataclass, field
12-
from datetime import datetime, timedelta
13-
from enum import Enum
14-
from typing import Any, Callable
12+
from datetime import datetime
13+
from typing import Any
1514

16-
import numpy as np
17-
import pandas as pd
18-
19-
logger = logging.getLogger(__name__)
20-
21-
# ── 系统状态 ──
22-
23-
class SystemMode(Enum):
24-
NORMAL = "normal"
25-
SAFE = "safe" # 降级运行 (只监控, 不交易)
26-
HALT = "halt" # 停止交易, 等待恢复
27-
RECOVERY = "recovery" # 正在恢复
28-
29-
class SystemStatus(Enum):
30-
INIT = "init"
31-
RUNNING = "running"
32-
ERROR = "error"
33-
STOPPED = "stopped"
15+
logger = logging.getLogger("kernel")
3416

3517

3618
@dataclass
37-
class SystemState:
38-
"""系统单一事实来源."""
39-
40-
# 时间
41-
market_time: datetime | None = None
42-
system_time: datetime | None = None
43-
broker_time: datetime | None = None
44-
45-
# 资金
46-
cash: float = 10_000_000
19+
class KernelState:
20+
"""系统运行时状态."""
21+
running: bool = True
22+
mode: str = "LIVE" # LIVE / SAFE / HALT
23+
last_tick: float = 0.0
24+
cycle_count: int = 0
25+
errors: list[str] = field(default_factory=list)
26+
alerts: list[str] = field(default_factory=list)
4727
equity: float = 10_000_000
4828
equity_peak: float = 10_000_000
49-
daily_pnl: float = 0.0
50-
51-
# 持仓
29+
cash: float = 10_000_000
5230
n_positions: int = 0
53-
gross_exposure: float = 0.0
54-
55-
# 状态
56-
mode: SystemMode = SystemMode.NORMAL
57-
status: SystemStatus = SystemStatus.INIT
58-
last_cycle: str = ""
59-
cycle_count: int = 0
60-
errors: list[dict] = field(default_factory=list)
61-
alerts: list[dict] = field(default_factory=list)
62-
63-
# 调仓
64-
last_rebalance: str = ""
65-
next_rebalance: str = ""
66-
days_to_rebalance: int = 0
67-
68-
def update_equity(self, total: float):
69-
self.equity = total
70-
if total > self.equity_peak:
71-
self.equity_peak = total
7231

7332
@property
7433
def drawdown(self) -> float:
7534
if self.equity_peak <= 0:
7635
return 0.0
7736
return (self.equity_peak - self.equity) / self.equity_peak
7837

79-
def summary(self) -> str:
80-
return (
81-
f"State | mode={self.mode.value} status={self.status.value} "
82-
f"equity={self.equity:>.0f} dd={self.drawdown:.2%} "
83-
f"cycle={self.cycle_count} errors={len(self.errors)}"
84-
)
85-
86-
87-
# ── 时钟 ──
88-
89-
@dataclass
90-
class SystemClock:
91-
"""系统时间一致性管理器."""
92-
93-
market_offset: float = 0.0 # market_time - system_time
94-
broker_offset: float = 0.0 # broker_time - system_time
95-
96-
def sync(self, market_time: datetime | None = None, broker_time: datetime | None = None):
97-
now = datetime.now()
98-
if market_time:
99-
self.market_offset = (market_time - now).total_seconds()
100-
if broker_time:
101-
self.broker_offset = (broker_time - now).total_seconds()
102-
103-
@property
104-
def market_now(self) -> datetime:
105-
return datetime.now() + timedelta(seconds=self.market_offset)
106-
107-
@property
108-
def broker_now(self) -> datetime:
109-
return datetime.now() + timedelta(seconds=self.broker_offset)
110-
111-
112-
# ── 内核 ──
11338

11439
class TradingKernel:
115-
"""系统控制平面. 唯一主循环. 决策中枢."""
116-
117-
def __init__(self, initial_capital: float = 10_000_000):
118-
self.state = SystemState(cash=initial_capital, equity=initial_capital)
119-
self.clock = SystemClock()
120-
self.components: dict[str, Any] = {}
121-
self._running = False
122-
self._cycle_interval = 60 # seconds
123-
self._last_cycle_time: float = 0.0
124-
125-
# ── 组件注册 ──
126-
127-
def register(self, name: str, component: Any):
128-
"""注册系统组件."""
129-
self.components[name] = component
130-
logger.info("Component registered: %s", name)
40+
"""系统控制平面. 唯一主循环. 全系统串联."""
41+
42+
def __init__(self, data_engine=None, signal_engine=None, risk_engine=None,
43+
execution_engine=None, monitor=None, reconciliation=None):
44+
self.data = data_engine
45+
self.signal = signal_engine
46+
self.risk = risk_engine
47+
self.exec = execution_engine
48+
self.monitor = monitor
49+
self.recon = reconciliation
50+
self.state = KernelState()
13151

13252
# ── 主循环 ──
13353

134-
def cycle(self) -> dict[str, Any]:
135-
"""一次完整运行周期."""
136-
result: dict[str, Any] = {"status": "ok", "alerts": []}
137-
138-
try:
139-
self.state.system_time = datetime.now()
140-
self.state.cycle_count += 1
141-
self.state.last_cycle = self.state.system_time.strftime("%Y-%m-%d %H:%M:%S")
142-
143-
# 1. 数据
144-
data = self._get_data()
145-
if data is None:
146-
self._transition(SystemMode.SAFE, "data_unavailable")
147-
result["alerts"].append("data_unavailable")
148-
return result
149-
150-
# 2. 策略
151-
engine = self.components.get("engine")
152-
if engine:
153-
engine.run_once(data.get("last_date"))
154-
self.state.n_positions = len(engine.state.positions)
155-
self.state.cash = engine.state.cash
156-
self.state.update_equity(engine.state.current_equity)
157-
158-
# 3. 风控
159-
safety = self.components.get("safety")
160-
if safety:
161-
checks = safety.check_all(
162-
equity=self.state.equity,
163-
peak_equity=self.state.equity_peak,
164-
cash=self.state.cash,
165-
daily_pnl=self.state.daily_pnl,
166-
positions={},
167-
)
168-
failed = [c for c in checks if not c.passed]
169-
if failed:
170-
result["alerts"].extend(f"{c.name}: {c.message}" for c in failed)
171-
if safety.kill_switch_triggered:
172-
self._transition(SystemMode.HALT, "kill_switch")
173-
result["status"] = "killed"
174-
return result
175-
176-
# 4. 对账
177-
recon = self.components.get("reconciliation")
178-
if recon:
179-
pass # 需要 broker 接入后启用
180-
181-
# 5. 状态检查
182-
dd = self.state.drawdown
183-
if dd > 0.25:
184-
self._transition(SystemMode.SAFE, f"drawdown_warning:{dd:.2%}")
185-
result["alerts"].append(f"drawdown={dd:.2%}")
186-
187-
self.state.status = SystemStatus.RUNNING
188-
self.state.mode = SystemMode.NORMAL
189-
190-
except Exception as e:
191-
logger.error("Cycle failed: %s\n%s", e, traceback.format_exc())
192-
self.state.errors.append({
193-
"time": str(datetime.now()),
194-
"error": str(e),
195-
"trace": traceback.format_exc(),
196-
})
197-
self._transition(SystemMode.RECOVERY, str(e)[:100])
198-
result["status"] = "error"
199-
200-
return result
201-
202-
def run_forever(self, interval: int = 3600):
54+
def run(self):
20355
"""永不结束的主循环."""
204-
self._running = True
205-
self._cycle_interval = interval
206-
self.state.status = SystemStatus.RUNNING
207-
208-
logger.info("Kernel started. Interval: %ds", interval)
209-
210-
while self._running:
56+
logger.info("TradingKernel started")
57+
while self.state.running:
21158
try:
212-
self.cycle()
213-
time.sleep(self._cycle_interval)
214-
except KeyboardInterrupt:
215-
logger.info("Kernel stopped by user")
216-
self.stop()
217-
break
218-
except Exception:
219-
logger.error("Kernel crashed: %s", traceback.format_exc())
220-
self._recover()
59+
self._tick()
60+
except Exception as e:
61+
logger.exception("Kernel crash: %s", e)
62+
self.state.errors.append(str(e))
63+
self._enter_safe_mode()
64+
65+
def _tick(self):
66+
"""单次运行周期."""
67+
now = time.time()
68+
self.state.last_tick = now
69+
self.state.cycle_count += 1
70+
71+
# 1. Data
72+
market_data = self.data.get_latest() if self.data else None
73+
74+
# 2. Signal
75+
signal = None
76+
if self.signal and market_data is not None:
77+
signal = self.signal.generate(market_data)
78+
79+
# 3. Risk
80+
risk_ok = True
81+
if self.risk and signal is not None:
82+
risk_ok = self.risk.check(signal, market_data)
83+
if not risk_ok:
84+
logger.warning("Risk block — skipping execution")
85+
if self.monitor:
86+
self.monitor.log_event("RISK_BLOCK")
87+
return
88+
89+
# 4. Execution
90+
fills = None
91+
if self.exec and signal is not None:
92+
orders = self.exec.generate_orders(signal)
93+
fills = self.exec.execute(orders)
94+
95+
# 5. Reconciliation
96+
if self.recon and fills:
97+
self.recon.update(fills)
98+
99+
# 6. Monitoring
100+
if self.monitor:
101+
self.monitor.update(market_data=market_data, signal=signal,
102+
fills=fills, state=self.state)
103+
104+
# 7. Safety
105+
self._post_tick_safety()
106+
107+
# ── 安全 ──
108+
109+
def _post_tick_safety(self):
110+
if self.risk and self.risk.drawdown_exceeded():
111+
self._enter_safe_mode()
112+
if self.risk and self.risk.daily_loss_limit_hit():
113+
self._enter_halt()
114+
115+
def _enter_safe_mode(self):
116+
logger.critical("SAFE MODE")
117+
self.state.mode = "SAFE"
118+
self.state.alerts.append("safe_mode")
119+
if self.exec:
120+
self.exec.cancel_all()
121+
122+
def _enter_halt(self):
123+
logger.critical("HALT MODE")
124+
self.state.mode = "HALT"
125+
self.state.running = False
126+
self.state.alerts.append("halt")
127+
if self.exec:
128+
self.exec.cancel_all()
221129

222130
def stop(self):
223-
"""停止系统."""
224-
self._running = False
225-
self.state.status = SystemStatus.STOPPED
226-
logger.info("Kernel stopped. Cycles: %d, Errors: %d",
131+
"""手动停止."""
132+
logger.info("Kernel stopping. Cycles: %d, Errors: %d",
227133
self.state.cycle_count, len(self.state.errors))
228-
229-
# ── 内部 ──
230-
231-
def _get_data(self) -> dict | None:
232-
"""获取最新数据."""
233-
data_rel = self.components.get("data")
234-
if data_rel:
235-
try:
236-
result = data_rel.connect()
237-
if result is not None:
238-
return {"last_date": datetime.now()}
239-
except Exception as e:
240-
logger.warning("Data unavailable: %s", e)
241-
return None
242-
243-
# 从 engine 获取数据
244-
engine = self.components.get("engine")
245-
if engine and engine._returns is not None:
246-
return {"last_date": engine._returns.index[-1]}
247-
return None
248-
249-
def _transition(self, mode: SystemMode, reason: str):
250-
"""模式切换."""
251-
old = self.state.mode
252-
self.state.mode = mode
253-
logger.info("Mode transition: %s -> %s (%s)", old.value, mode.value, reason)
254-
self.state.alerts.append({
255-
"time": str(datetime.now()),
256-
"from": old.value,
257-
"to": mode.value,
258-
"reason": reason,
259-
})
260-
261-
def _recover(self):
262-
"""崩溃恢复."""
263-
self._transition(SystemMode.RECOVERY, "auto_recovery")
264-
logger.info("Recovery attempt...")
265-
time.sleep(5)
266-
self.state.errors = []
267-
self._transition(SystemMode.NORMAL, "recovered")
268-
269-
# ── 报告 ──
134+
self.state.running = False
270135

271136
def report(self) -> str:
272137
dd = self.state.drawdown
273138
return (
274139
f"\n{'=' * 60}\n"
275140
f" TradingKernel — alpha-v1.0\n"
276141
f"{'=' * 60}\n"
277-
f" Status: {self.state.status.value}\n"
278-
f" Mode: {self.state.mode.value}\n"
279-
f" Equity: {self.state.equity:>12,.2f}\n"
280-
f" Drawdown: {dd*100:>10.2f}%\n"
281-
f" Positions: {self.state.n_positions:>6d}\n"
282-
f" Cycles: {self.state.cycle_count:>6d}\n"
283-
f" Alerts: {len(self.state.alerts):>6d}\n"
284-
f" Errors: {len(self.state.errors):>6d}\n"
285-
f" Next rebal: {self.state.next_rebalance}\n"
286-
f" Mode hist: {','.join(a['to'] for a in self.state.alerts[-5:])}\n"
142+
f" Mode: {self.state.mode}\n"
143+
f" Cycles: {self.state.cycle_count}\n"
144+
f" Equity: {self.state.equity:>12,.2f}\n"
145+
f" Drawdown: {dd*100:>10.2f}%\n"
146+
f" Positions: {self.state.n_positions:>6d}\n"
147+
f" Alerts: {len(self.state.alerts)}\n"
148+
f" Errors: {len(self.state.errors)}\n"
287149
f"{'=' * 60}"
288150
)

0 commit comments

Comments
 (0)