feat(engine): 新增 Python 策略引擎模块

- config/settings.py:Pydantic 解析 env.yaml
- data/db.py:asyncpg 连接池管理
- data/reader.py:KlineReader 只读查询 TimescaleDB
- data/models.py:KlineRecord 等 Pydantic 模型,镜像 TypeORM 实体
- example/test_db.py:数据库查询验证示例
- README.md:引擎架构文档
This commit is contained in:
Rekey
2026-06-08 18:19:50 +08:00
parent 5e385547c7
commit 1c9339a4db
18 changed files with 1711 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
"""
Python 数据模型 —— 镜像 data/db/entities/ 中的 TypeORM 实体。
命名约定:
- 类名与 TypeORM 实体类名一致(Kline, Exchange, TradingPair
- 字段名使用 Python snake_case,对应 TypeORM 的 camelCase / snake_case
- 所有模型使用 Pydantic,提供运行时校验 + IDE 智能提示
"""
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
class KlineRecord(BaseModel):
"""
K 线数据记录。
映射关系:
TypeORM Kline.time → KlineRecord.time
TypeORM Kline.exchange → KlineRecord.exchange
TypeORM Kline.symbol → KlineRecord.symbol
TypeORM Kline.interval → KlineRecord.interval
TypeORM Kline.open → KlineRecord.open
TypeORM Kline.high → KlineRecord.high
TypeORM Kline.low → KlineRecord.low
TypeORM Kline.close → KlineRecord.close
TypeORM Kline.volume → KlineRecord.volume
TypeORM Kline.quote_volume → KlineRecord.quote_volume
TypeORM Kline.taker_buy_base_vol → KlineRecord.taker_buy_base_vol
TypeORM Kline.taker_buy_quote_vol→ KlineRecord.taker_buy_quote_vol
TypeORM Kline.trade_count → KlineRecord.trade_count
TypeORM Kline.is_closed → KlineRecord.is_closed
"""
time: datetime
exchange: str
symbol: str
interval: str
open: float
high: float
low: float
close: float
volume: float
quote_volume: Optional[float] = None
taker_buy_base_vol: Optional[float] = None
taker_buy_quote_vol: Optional[float] = None
trade_count: Optional[int] = None
is_closed: bool = True
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
@classmethod
def from_record(cls, record) -> "KlineRecord":
return cls(
time=record["time"],
exchange=record["exchange"],
symbol=record["symbol"],
interval=record["interval"],
open=float(record["open"]),
high=float(record["high"]),
low=float(record["low"]),
close=float(record["close"]),
volume=float(record["volume"]),
quote_volume=float(record["quote_volume"]) if record["quote_volume"] is not None else None,
taker_buy_base_vol=float(record["taker_buy_base_vol"]) if record["taker_buy_base_vol"] is not None else None,
taker_buy_quote_vol=float(record["taker_buy_quote_vol"]) if record["taker_buy_quote_vol"] is not None else None,
trade_count=int(record["trade_count"]) if record["trade_count"] is not None else None,
is_closed=bool(record["is_closed"]),
created_at=record["created_at"] if "created_at" in record else None,
updated_at=record["updated_at"] if "updated_at" in record else None,
)
class TradingPairInfo(BaseModel):
"""交易对配置信息(轻量版,仅包含策略决策所需的字段)"""
symbol: str
exchange: str
base_asset: str
quote_asset: str
price_precision: int
quantity_precision: int
min_qty: Optional[float] = None
min_notional: Optional[float] = None
active: bool = True
class Signal(BaseModel):
"""交易信号"""
symbol: str
signal_type: str = Field(..., pattern=r"^(BUY|SELL|HOLD)$")
price: Optional[float] = None
quantity: Optional[float] = None
reason: str = ""
timestamp: datetime = Field(default_factory=datetime.utcnow)