Architecture: - Restructure into 5 subsystems: data/, features/, market/, signals/, execution/, apps/ - Unified ts_code conversion in core/codes.py (idempotent, kills 4 duplicate copies) - analytics_conn() + kline_glob() — zero hardcoded DB/Parquet paths - Fixed double-suffix bug (.SZ.SZ) in backfill pipeline root cause Signal Intelligence (the moat): - 14 signal types: EMA52, Vegas, Chan, ORB, Gap, NR7, Inside Bar - 640K+ historical signal instances across 8 backfilled types - Multi-signal Expectancy Engine with breadth-similarity matching - Signal backfill CLI: ashare-dp backfill signals Market Intelligence: - 8 engines: State, Leadership, Opportunity, Flow, Sentiment, Memory, Knowledge Graph, Recommendations - Real limit-up/down sentiment via akshare (108 ZT, 19 DT, 52 broken board) - Knowledge Graph: 8 themes × 30+ concepts with keyword matching - Money-flow stock recommendations with entry/stop/target trade plans Dashboard Command Center: - Decision-first layout: COMMAND → WHERE → WHY → RISK → EXPECTANCY - Multi-signal Expectancy comparison table (8 types ranked by WR) - Theme Map visualization with rotation detection - Intraday Replay infrastructure (30min state snapshots) - RECOMMENDATIONS card with actionable trade plans Trading Memory: - trade_log table + POST/GET/PUT API for trade recording - Performance stats aggregation Code Quality: - 0 hardcoded DB paths, 0 REPLACE hacks, 0 dead ts_code copies - EMA52 screening deduplicated (CLI + scheduler share one function) - read_parquet_sql() helper for 28 duplicate patterns - 6 bugs fixed from code review (NR7 window, theme matching, column indices, etc.) Co-Authored-By: Claude <noreply@anthropic.com>
105 lines
3.3 KiB
Python
105 lines
3.3 KiB
Python
"""Query CLI subcommands for ad-hoc data queries."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
|
|
import typer
|
|
|
|
from ashare_dp.core.models import Freq
|
|
from ashare_dp.data.store.database import get_db
|
|
from ashare_dp.data.store.repository import KLineRepository
|
|
|
|
query_app = typer.Typer()
|
|
|
|
|
|
@query_app.command("kline")
|
|
def query_kline(
|
|
freq: str = typer.Argument(..., help="Frequency: 1m,5m,15m,30m,1h,2h,1d,1w,1M"),
|
|
ts_code: str = typer.Argument(..., help="Stock code, e.g. 000001.SZ"),
|
|
start: str = typer.Option(None, help="Start date YYYY-MM-DD"),
|
|
end: str = typer.Option(None, help="End date YYYY-MM-DD"),
|
|
limit: int = typer.Option(100, help="Max records"),
|
|
):
|
|
"""Query K-line data from the command line."""
|
|
freq_enum = Freq(freq)
|
|
repo = KLineRepository()
|
|
|
|
sd = date.fromisoformat(start) if start else None
|
|
ed = date.fromisoformat(end) if end else None
|
|
|
|
df = repo.read_klines(freq=freq_enum, ts_code=ts_code, start_date=sd, end_date=ed, limit=limit)
|
|
|
|
if df.empty:
|
|
typer.echo("No data found")
|
|
return
|
|
|
|
typer.echo(f"\n{freq} K-line for {ts_code}:")
|
|
typer.echo(df.to_string(index=False))
|
|
typer.echo(f"\n{len(df)} records")
|
|
|
|
|
|
@query_app.command("latest")
|
|
def query_latest(
|
|
freq: str = typer.Option("1d", help="Frequency"),
|
|
ts_code: str = typer.Option(None, help="Stock code (optional)"),
|
|
):
|
|
"""Show latest K-line data."""
|
|
freq_enum = Freq(freq)
|
|
repo = KLineRepository()
|
|
df = repo.get_latest(freq=freq_enum, ts_code=ts_code)
|
|
|
|
if df.empty:
|
|
typer.echo("No data found")
|
|
return
|
|
|
|
typer.echo(f"\nLatest {freq} K-line:")
|
|
typer.echo(df.to_string(index=False))
|
|
typer.echo(f"\n{len(df)} records")
|
|
|
|
|
|
@query_app.command("stocks")
|
|
def query_stocks(
|
|
exchange: str = typer.Option(None, help="Exchange: SH, SZ, BJ"),
|
|
limit: int = typer.Option(50, help="Max records"),
|
|
):
|
|
"""List stocks."""
|
|
with get_db(read_only=True) as db:
|
|
if exchange:
|
|
rows = db.query(
|
|
"SELECT ts_code, symbol, name, exchange, market, list_date FROM stock_info WHERE exchange = ? LIMIT ?",
|
|
(exchange.upper(), limit),
|
|
)
|
|
else:
|
|
rows = db.query(
|
|
"SELECT ts_code, symbol, name, exchange, market, list_date FROM stock_info LIMIT ?",
|
|
(limit,),
|
|
)
|
|
|
|
typer.echo(f"\n{'ts_code':<12} {'symbol':<8} {'name':<12} {'exchange':<8} {'market':<10} {'list_date'}")
|
|
typer.echo("-" * 60)
|
|
for row in rows:
|
|
ts, sym, name, ex, mkt, ld = row
|
|
ld_str = str(ld) if ld else ""
|
|
typer.echo(f"{ts:<12} {sym:<8} {name:<12} {ex:<8} {mkt or '':<10} {ld_str}")
|
|
|
|
|
|
@query_app.command("stats")
|
|
def query_stats():
|
|
"""Show database statistics."""
|
|
repo = KLineRepository()
|
|
|
|
typer.echo("\nDatabase Statistics:")
|
|
typer.echo("-" * 40)
|
|
|
|
# Stock count
|
|
with get_db(read_only=True) as db:
|
|
n = db.query("SELECT count(*) FROM stock_info")[0][0]
|
|
typer.echo(f" Stocks: {n}")
|
|
|
|
# Record count and date range per frequency
|
|
for freq_repr in [Freq.d1, Freq.w1, Freq.M1, Freq.h1, Freq.m5, Freq.m1]:
|
|
count = repo.count_records(freq_repr)
|
|
dr = repo.get_date_range(freq_repr)
|
|
typer.echo(f" {freq_repr.value}: {count} records, range {dr[0]} ~ {dr[1]}")
|