532 lines
11 KiB
Python
532 lines
11 KiB
Python
"""Database schema management for the investment assistant."""
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
from dataclasses import dataclass, field
|
|
from typing import Iterable, List
|
|
|
|
from app.utils.db import db_session
|
|
|
|
|
|
SCHEMA_STATEMENTS: Iterable[str] = (
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS stock_basic (
|
|
ts_code TEXT PRIMARY KEY,
|
|
symbol TEXT,
|
|
name TEXT,
|
|
area TEXT,
|
|
industry TEXT,
|
|
market TEXT,
|
|
exchange TEXT,
|
|
list_status TEXT,
|
|
list_date TEXT,
|
|
delist_date TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS daily (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
open REAL,
|
|
high REAL,
|
|
low REAL,
|
|
close REAL,
|
|
pre_close REAL,
|
|
change REAL,
|
|
pct_chg REAL,
|
|
vol REAL,
|
|
amount REAL,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS daily_basic (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
close REAL,
|
|
turnover_rate REAL,
|
|
turnover_rate_f REAL,
|
|
volume_ratio REAL,
|
|
pe REAL,
|
|
pe_ttm REAL,
|
|
pb REAL,
|
|
ps REAL,
|
|
ps_ttm REAL,
|
|
dv_ratio REAL,
|
|
dv_ttm REAL,
|
|
total_share REAL,
|
|
float_share REAL,
|
|
free_share REAL,
|
|
total_mv REAL,
|
|
circ_mv REAL,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS factors (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
mom_20 REAL,
|
|
mom_60 REAL,
|
|
volat_20 REAL,
|
|
turn_20 REAL,
|
|
updated_at TEXT,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS adj_factor (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
adj_factor REAL,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS suspend (
|
|
ts_code TEXT,
|
|
suspend_date TEXT,
|
|
resume_date TEXT,
|
|
suspend_type TEXT,
|
|
ann_date TEXT,
|
|
suspend_timing TEXT,
|
|
resume_timing TEXT,
|
|
reason TEXT,
|
|
PRIMARY KEY (ts_code, suspend_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS trade_calendar (
|
|
exchange TEXT,
|
|
cal_date TEXT,
|
|
is_open INTEGER,
|
|
pretrade_date TEXT,
|
|
PRIMARY KEY (exchange, cal_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS stk_limit (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
up_limit REAL,
|
|
down_limit REAL,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS index_basic (
|
|
ts_code TEXT PRIMARY KEY,
|
|
name TEXT,
|
|
fullname TEXT,
|
|
market TEXT,
|
|
publisher TEXT,
|
|
index_type TEXT,
|
|
category TEXT,
|
|
base_date TEXT,
|
|
base_point REAL,
|
|
list_date TEXT,
|
|
weight_rule TEXT,
|
|
desc TEXT,
|
|
exp_date TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS index_daily (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
close REAL,
|
|
open REAL,
|
|
high REAL,
|
|
low REAL,
|
|
pre_close REAL,
|
|
change REAL,
|
|
pct_chg REAL,
|
|
vol REAL,
|
|
amount REAL,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS fund_basic (
|
|
ts_code TEXT PRIMARY KEY,
|
|
name TEXT,
|
|
management TEXT,
|
|
custodian TEXT,
|
|
fund_type TEXT,
|
|
found_date TEXT,
|
|
due_date TEXT,
|
|
list_date TEXT,
|
|
issue_date TEXT,
|
|
delist_date TEXT,
|
|
issue_amount REAL,
|
|
m_fee REAL,
|
|
c_fee REAL,
|
|
benchmark TEXT,
|
|
status TEXT,
|
|
invest_type TEXT,
|
|
type TEXT,
|
|
trustee TEXT,
|
|
purc_start_date TEXT,
|
|
redm_start_date TEXT,
|
|
market TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS fund_nav (
|
|
ts_code TEXT,
|
|
nav_date TEXT,
|
|
ann_date TEXT,
|
|
unit_nav REAL,
|
|
accum_nav REAL,
|
|
accum_div REAL,
|
|
net_asset REAL,
|
|
total_netasset REAL,
|
|
adj_nav REAL,
|
|
update_flag TEXT,
|
|
PRIMARY KEY (ts_code, nav_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS fut_basic (
|
|
ts_code TEXT PRIMARY KEY,
|
|
symbol TEXT,
|
|
name TEXT,
|
|
exchange TEXT,
|
|
exchange_full_name TEXT,
|
|
product TEXT,
|
|
product_name TEXT,
|
|
variety TEXT,
|
|
list_date TEXT,
|
|
delist_date TEXT,
|
|
trade_unit REAL,
|
|
per_unit REAL,
|
|
quote_unit TEXT,
|
|
settle_month TEXT,
|
|
contract_size REAL,
|
|
tick_size REAL,
|
|
margin_rate REAL,
|
|
margin_ratio REAL,
|
|
delivery_month TEXT,
|
|
delivery_day TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS fut_daily (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
pre_settle REAL,
|
|
open REAL,
|
|
high REAL,
|
|
low REAL,
|
|
close REAL,
|
|
settle REAL,
|
|
change1 REAL,
|
|
change2 REAL,
|
|
vol REAL,
|
|
amount REAL,
|
|
oi REAL,
|
|
oi_chg REAL,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS fx_daily (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
bid REAL,
|
|
ask REAL,
|
|
mid REAL,
|
|
high REAL,
|
|
low REAL,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS hk_daily (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
close REAL,
|
|
open REAL,
|
|
high REAL,
|
|
low REAL,
|
|
pre_close REAL,
|
|
change REAL,
|
|
pct_chg REAL,
|
|
vol REAL,
|
|
amount REAL,
|
|
exchange TEXT,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS us_daily (
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
close REAL,
|
|
open REAL,
|
|
high REAL,
|
|
low REAL,
|
|
pre_close REAL,
|
|
change REAL,
|
|
pct_chg REAL,
|
|
vol REAL,
|
|
amount REAL,
|
|
PRIMARY KEY (ts_code, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS news (
|
|
id TEXT PRIMARY KEY,
|
|
ts_code TEXT,
|
|
pub_time TEXT,
|
|
source TEXT,
|
|
title TEXT,
|
|
summary TEXT,
|
|
url TEXT,
|
|
entities TEXT,
|
|
sentiment REAL,
|
|
heat REAL
|
|
);
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_news_time ON news(pub_time DESC);
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_news_code ON news(ts_code, pub_time DESC);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS heat_daily (
|
|
scope TEXT,
|
|
key TEXT,
|
|
trade_date TEXT,
|
|
heat REAL,
|
|
top_topics TEXT,
|
|
PRIMARY KEY (scope, key, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS bt_config (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT,
|
|
start_date TEXT,
|
|
end_date TEXT,
|
|
universe TEXT,
|
|
params TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS bt_trades (
|
|
cfg_id TEXT,
|
|
ts_code TEXT,
|
|
trade_date TEXT,
|
|
side TEXT,
|
|
price REAL,
|
|
qty REAL,
|
|
reason TEXT,
|
|
PRIMARY KEY (cfg_id, ts_code, trade_date, side)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS bt_risk_events (
|
|
cfg_id TEXT,
|
|
trade_date TEXT,
|
|
ts_code TEXT,
|
|
reason TEXT,
|
|
action TEXT,
|
|
target_weight REAL,
|
|
confidence REAL,
|
|
metadata TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS bt_nav (
|
|
cfg_id TEXT,
|
|
trade_date TEXT,
|
|
nav REAL,
|
|
ret REAL,
|
|
pos_count INTEGER,
|
|
turnover REAL,
|
|
dd REAL,
|
|
info TEXT,
|
|
PRIMARY KEY (cfg_id, trade_date)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS bt_report (
|
|
cfg_id TEXT PRIMARY KEY,
|
|
summary TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS run_log (
|
|
ts TEXT PRIMARY KEY,
|
|
stage TEXT,
|
|
level TEXT,
|
|
msg TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS agent_utils (
|
|
trade_date TEXT,
|
|
ts_code TEXT,
|
|
agent TEXT,
|
|
action TEXT,
|
|
utils TEXT,
|
|
feasible TEXT,
|
|
weight REAL,
|
|
PRIMARY KEY (trade_date, ts_code, agent)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS alloc_log (
|
|
trade_date TEXT,
|
|
ts_code TEXT,
|
|
target_weight REAL,
|
|
clipped_weight REAL,
|
|
reason TEXT,
|
|
PRIMARY KEY (trade_date, ts_code)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS investment_pool (
|
|
trade_date TEXT,
|
|
ts_code TEXT,
|
|
score REAL,
|
|
status TEXT,
|
|
rationale TEXT,
|
|
tags TEXT,
|
|
metadata TEXT,
|
|
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
PRIMARY KEY (trade_date, ts_code)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS portfolio_positions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ts_code TEXT NOT NULL,
|
|
opened_date TEXT NOT NULL,
|
|
closed_date TEXT,
|
|
quantity REAL NOT NULL,
|
|
cost_price REAL NOT NULL,
|
|
market_price REAL,
|
|
market_value REAL,
|
|
realized_pnl REAL DEFAULT 0,
|
|
unrealized_pnl REAL DEFAULT 0,
|
|
target_weight REAL,
|
|
status TEXT NOT NULL DEFAULT 'open',
|
|
notes TEXT,
|
|
metadata TEXT,
|
|
updated_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS portfolio_trades (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
trade_date TEXT NOT NULL,
|
|
ts_code TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
quantity REAL NOT NULL,
|
|
price REAL NOT NULL,
|
|
fee REAL DEFAULT 0,
|
|
order_id TEXT,
|
|
source TEXT,
|
|
notes TEXT,
|
|
metadata TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS portfolio_snapshots (
|
|
trade_date TEXT PRIMARY KEY,
|
|
total_value REAL,
|
|
cash REAL,
|
|
invested_value REAL,
|
|
unrealized_pnl REAL,
|
|
realized_pnl REAL,
|
|
net_flow REAL,
|
|
exposure REAL,
|
|
notes TEXT,
|
|
metadata TEXT
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS tuning_results (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
experiment_id TEXT,
|
|
strategy TEXT,
|
|
action TEXT,
|
|
weights TEXT,
|
|
reward REAL,
|
|
metrics TEXT,
|
|
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
);
|
|
"""
|
|
)
|
|
|
|
REQUIRED_TABLES = (
|
|
"stock_basic",
|
|
"daily",
|
|
"daily_basic",
|
|
"factors",
|
|
"adj_factor",
|
|
"suspend",
|
|
"trade_calendar",
|
|
"stk_limit",
|
|
"index_basic",
|
|
"index_daily",
|
|
"fund_basic",
|
|
"fund_nav",
|
|
"fut_basic",
|
|
"fut_daily",
|
|
"fx_daily",
|
|
"hk_daily",
|
|
"us_daily",
|
|
"news",
|
|
"heat_daily",
|
|
"bt_config",
|
|
"bt_trades",
|
|
"bt_risk_events",
|
|
"bt_nav",
|
|
"bt_report",
|
|
"run_log",
|
|
"agent_utils",
|
|
"alloc_log",
|
|
"investment_pool",
|
|
"portfolio_positions",
|
|
"portfolio_trades",
|
|
"portfolio_snapshots",
|
|
"tuning_results",
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class MigrationResult:
|
|
executed: int
|
|
skipped: bool = False
|
|
missing_tables: List[str] = field(default_factory=list)
|
|
|
|
|
|
def _missing_tables() -> List[str]:
|
|
try:
|
|
with db_session(read_only=True) as conn:
|
|
rows = conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
|
|
except sqlite3.OperationalError:
|
|
return list(REQUIRED_TABLES)
|
|
existing = {row["name"] for row in rows}
|
|
return [name for name in REQUIRED_TABLES if name not in existing]
|
|
|
|
|
|
def initialize_database() -> MigrationResult:
|
|
"""Create tables and indexes required by the application."""
|
|
|
|
missing = _missing_tables()
|
|
if not missing:
|
|
return MigrationResult(executed=0, skipped=True, missing_tables=[])
|
|
|
|
executed = 0
|
|
with db_session() as conn:
|
|
cursor = conn.cursor()
|
|
for statement in SCHEMA_STATEMENTS:
|
|
cursor.executescript(statement)
|
|
executed += 1
|
|
return MigrationResult(executed=executed, skipped=False, missing_tables=missing)
|