chore: 更新 README 架构文档与数据库测试脚本

- README.md: 更新数据层 Node.js→Bun,common→engine/common,同步目录树结构
- db_test.py: TimescaleDB 数据库连接与基础查询测试脚本
This commit is contained in:
Rekey
2026-06-12 10:27:11 +08:00
parent 515e61c517
commit b5cdb41993
2 changed files with 174 additions and 34 deletions
+147
View File
@@ -0,0 +1,147 @@
"""
数据库 K 线读取测试 — 只读,从 TimescaleDB 读取各周期 K 线并打印
用法:
python db_test.py # 使用 env.yaml 中的 host
python db_test.py --host localhost # 覆盖 host(如 SSH 隧道后)
"""
import asyncio
import sys
import asyncpg
from common.config import config as app_config
# ── 各周期对应的表/视图 ──
INTERVAL_TABLES: dict[str, str] = {
"1m": "klines",
"5m": "klines_5m",
"15m": "klines_15m",
"30m": "klines_30m",
"1h": "klines_1h",
"4h": "klines_4h",
"1d": "klines_1d",
"1w": "klines_1w",
}
LIMIT = 5
def parse_args() -> str | None:
"""解析命令行参数,返回 host 覆盖值"""
args = sys.argv[1:]
host_override = None
i = 0
while i < len(args):
if args[i] == "--host" and i + 1 < len(args):
host_override = args[i + 1]
i += 2
elif args[i].startswith("--host="):
host_override = args[i].split("=", 1)[1]
i += 1
else:
i += 1
return host_override
async def main():
host_override = parse_args()
db = app_config.db
if host_override:
db = db.model_copy(update={"host": host_override})
dsn = f"postgresql://{db.user}:{db.password}@{db.host}:{db.port}/{db.name}"
print(f"连接 {db.host}:{db.port}/{db.name} ...")
conn = await asyncpg.connect(dsn)
try:
print()
print("=" * 85)
print(" TimescaleDB K 线数据读取测试(只读)")
print("=" * 85)
for interval, table in INTERVAL_TABLES.items():
# 检查表/视图是否存在(含 TimescaleDB 连续聚合视图)
exists = await conn.fetchval(
"""
SELECT EXISTS (
SELECT 1 FROM pg_matviews WHERE matviewname = $1
UNION
SELECT 1 FROM pg_tables WHERE tablename = $1
UNION
SELECT 1 FROM pg_views WHERE viewname = $1
)
""",
table,
)
if not exists:
print(f"\n [{interval}] {table} — 不存在,跳过")
continue
# 获取该表/视图的实际列名,避免查询不存在的列报错
columns = await conn.fetch(
"""
SELECT column_name
FROM information_schema.columns
WHERE table_name = $1
ORDER BY ordinal_position
""",
table,
)
col_names = {r["column_name"] for r in columns}
# 选择存在的列进行查询
select_cols = ["time", "exchange", "symbol", "interval", "open", "high", "low", "close", "volume"]
if "trade_count" in col_names:
select_cols.append("trade_count")
if "is_closed" in col_names:
select_cols.append("is_closed")
rows = await conn.fetch(
f"""
SELECT {', '.join(select_cols)}
FROM {table}
WHERE interval = $1
ORDER BY time DESC
LIMIT $2
""",
interval,
LIMIT,
)
print(f"\n{'' * 85}")
print(f" [{interval}] {table}{len(rows)}")
print(f"{'' * 85}")
if not rows:
print(" (无数据)")
continue
for k in rows:
t = k["time"].strftime("%Y-%m-%d %H:%M:%S")
is_closed = k.get("is_closed")
if is_closed is not None:
mark = "" if is_closed else ""
else:
mark = " " # 聚合视图无此字段
trade_count = k.get("trade_count", "-")
print(
f" {t} [{mark}] {k['symbol']:10s} {k['interval']:4s}"
f" O={k['open']:>12.4f} H={k['high']:>12.4f}"
f" L={k['low']:>12.4f} C={k['close']:>12.4f}"
f" V={k['volume']:>10.4f} trades={trade_count}"
)
print(f"\n{'=' * 85}")
print(" 读取完成")
print("=" * 85)
finally:
await conn.close()
if __name__ == "__main__":
asyncio.run(main())