feat: 自定义数据源扩展 + 限频集中化 (v0.1.80)

This commit is contained in:
shy3130
2026-07-05 22:57:46 +08:00
parent e524ea4893
commit fa08981851
38 changed files with 2819 additions and 123 deletions
@@ -0,0 +1,43 @@
# 自定义数据源 mock 联调示例
这个目录提供一个本地 mock HTTP 数据源,用于验证项目的自定义数据源接入链路。
## 运行 mock 服务
```bash
cd docs/examples/custom-data-source
python mock_server.py
```
服务默认监听:
```text
http://127.0.0.1:3021
```
端点:
| 端点 | 数据 |
| --- | --- |
| `/daily` | 日K |
| `/adj_factor` | 除权因子 |
| `/realtime` | 全市场实时快照 |
## 接入项目
复制示例 YAML 到运行数据目录:
```bash
mkdir -p data/data_sources
cp docs/examples/custom-data-source/mock_source.yaml data/data_sources/mock_source.yaml
```
然后在项目里打开:
```text
设置 -> 数据源 -> 重新加载
```
选择 `mock_source` 后,可用「试拉测试」验证 `daily``adj_factor``realtime`
完整说明见 [../../custom-data-source.md](../../custom-data-source.md)。
@@ -0,0 +1,129 @@
"""Mock custom market data source for local integration tests.
Run:
python mock_server.py
Then copy mock_source.yaml to data/data_sources/mock_source.yaml and reload data
sources in the app settings page.
"""
from __future__ import annotations
from datetime import date, datetime, timedelta
from typing import Any
import uvicorn
from fastapi import FastAPI, Request
app = FastAPI(title="Mock Custom Market Data Source")
SYMBOLS = {
"000001.SZ": "平安银行",
"600000.SH": "浦发银行",
"300750.SZ": "宁德时代",
}
BASE = {
"000001.SZ": 10.20,
"600000.SH": 8.60,
"300750.SZ": 186.00,
}
def _parse_symbols(value: Any) -> list[str]:
if isinstance(value, list):
return [str(v) for v in value if str(v) in SYMBOLS]
if isinstance(value, str) and value:
return [s.strip() for s in value.split(",") if s.strip() in SYMBOLS]
return list(SYMBOLS)
async def _payload(request: Request) -> dict:
if request.method == "POST":
try:
return await request.json()
except Exception:
return {}
return dict(request.query_params)
def _parse_date(value: Any, fallback: date) -> date:
if not value:
return fallback
text = str(value)[:10]
try:
return date.fromisoformat(text)
except ValueError:
return fallback
@app.api_route("/daily", methods=["GET", "POST"])
async def daily(request: Request):
body = await _payload(request)
symbols = _parse_symbols(body.get("symbols"))
end = _parse_date(body.get("end_time"), date.today())
start = _parse_date(body.get("start_time"), end - timedelta(days=5))
rows = []
cur = start
while cur <= end:
if cur.weekday() < 5:
offset = (cur - start).days
for sym in symbols:
base = BASE[sym] + offset * 0.03
rows.append({
"ts_code": sym,
"trade_date": cur.isoformat(),
"open": round(base, 2),
"high": round(base * 1.015, 2),
"low": round(base * 0.985, 2),
"close": round(base * 1.004, 2),
"vol": 120000 + offset * 1000,
"amt": round((120000 + offset * 1000) * base, 2),
})
cur += timedelta(days=1)
return {"code": 0, "data": rows}
@app.api_route("/adj_factor", methods=["GET", "POST"])
async def adj_factor(request: Request):
body = await _payload(request)
symbols = _parse_symbols(body.get("symbols"))
today = date.today()
return {
"code": 0,
"data": [
{"ts_code": sym, "trade_date": today.isoformat(), "factor": 1.0}
for sym in symbols
],
}
@app.api_route("/realtime", methods=["GET", "POST"])
async def realtime():
now = datetime.now().isoformat(timespec="seconds")
rows = []
for i, (sym, name) in enumerate(SYMBOLS.items()):
prev = BASE[sym]
last = round(prev * (1 + (i + 1) * 0.006), 2)
change = round(last - prev, 2)
rows.append({
"ts_code": sym,
"name": name,
"last": last,
"pre_close": prev,
"open": round(prev * 1.002, 2),
"high": round(last * 1.01, 2),
"low": round(prev * 0.99, 2),
"vol": 150000 + i * 20000,
"amt": round((150000 + i * 20000) * last, 2),
"pct": change / prev,
"amount_change": change,
"amplitude": 0.025,
"turnover": 0.012 + i * 0.001,
"timestamp": now,
"session": "regular",
})
return {"code": 0, "data": rows}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=3021)
@@ -0,0 +1,58 @@
name: mock_source
display_name: "Mock 自定义数据源"
auth:
type: none
datasets:
daily:
url: http://127.0.0.1:3021/daily
method: POST
batch: 100
rpm: 200
response_path: data
field_map:
ts_code: symbol
trade_date: date
open: open
high: high
low: low
close: close
vol: volume
amt: amount
transforms:
date: "parse_date(value, '%Y-%m-%d')"
adj_factor:
url: http://127.0.0.1:3021/adj_factor
method: POST
batch: 100
rpm: 200
response_path: data
field_map:
ts_code: symbol
trade_date: trade_date
factor: ex_factor
transforms:
trade_date: "parse_date(value, '%Y-%m-%d')"
realtime:
url: http://127.0.0.1:3021/realtime
method: GET
rpm: 60
response_path: data
field_map:
ts_code: symbol
name: name
last: last_price
pre_close: prev_close
open: open
high: high
low: low
vol: volume
amt: amount
pct: change_pct
amount_change: change_amount
amplitude: amplitude
turnover: turnover_rate
timestamp: timestamp
session: session