Baseline before Postgres migration (WS0)
Snapshot before the DuckDB->PostgreSQL migration. data/, archive/, the 232MB rebuild zip, the Windows .venv, and .env are excluded via .gitignore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
007cd95c79
133 files changed
+17321
No files matched your search
+102
@@ -0,0 +1,102 @@
|
||||
"""Research utilities CLI.
|
||||
|
||||
python research_cli.py symbols --date 2026-06-11
|
||||
python research_cli.py gappers --date 2026-06-11
|
||||
python research_cli.py news
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from datetime import date
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from research import query as rq
|
||||
from research.ingestion.rss import ingest_all_feeds
|
||||
from research.ingestion.signals import scan_gappers, store_scanner_snapshot
|
||||
from research.multi_schema import open_research_db
|
||||
|
||||
DEFAULT_FEEDS = {
|
||||
"globenewswire_public": "https://www.globenewswire.com/RssFeed/orgclass/1/feedTitle/GlobeNewswire%20-%20News%20about%20Public%20Companies",
|
||||
"prnewswire_news": "https://www.prnewswire.com/rss/news-releases-list.rss",
|
||||
}
|
||||
|
||||
|
||||
def cmd_symbols(args) -> int:
|
||||
con = open_research_db("market")
|
||||
rows = rq.query_session_symbols(
|
||||
con, args.session_date, price_min=args.price_min,
|
||||
price_max=args.price_max, limit=args.limit,
|
||||
)
|
||||
if not rows:
|
||||
print("no symbols — ingest minute bars first (fetch_minute_bars.py)")
|
||||
return 1
|
||||
for r in rows:
|
||||
print(f"{r['symbol']:>6} last={r['last_price']:<8.2f} bars={r['bar_count']}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_gappers(args) -> int:
|
||||
con = open_research_db("market")
|
||||
candidates = scan_gappers(
|
||||
con, args.session_date,
|
||||
min_gap_pct=args.min_gap, min_relative_volume=args.min_rvol,
|
||||
price_min=args.price_min, price_max=args.price_max,
|
||||
)
|
||||
if not candidates:
|
||||
print("no gappers found for this session")
|
||||
return 0
|
||||
stored = store_scanner_snapshot(con, candidates)
|
||||
for c in candidates:
|
||||
print(f"#{c.rank:<2} {c.symbol:>6} gap={c.gap_pct:>6.1f}% rvol={c.relative_volume:>5.1f}x "
|
||||
f"price={c.price:<8.2f} vol={c.cumulative_volume:,}")
|
||||
print(f"\nstored {stored} rows in scanner_snapshots")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_news(args) -> int:
|
||||
con = open_research_db("news")
|
||||
feeds = dict(DEFAULT_FEEDS)
|
||||
extra = os.environ.get("NEWS_RSS_FEEDS", "")
|
||||
for pair in extra.split(";"):
|
||||
if "=" in pair:
|
||||
name, url = pair.split("=", 1)
|
||||
feeds[name.strip()] = url.strip()
|
||||
for result in ingest_all_feeds(con, feeds):
|
||||
status = result.error or f"http {result.http_status}"
|
||||
print(f"{result.source:<28} items={result.item_count:<4} new={len(result.new_items):<4} ({status})")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
load_dotenv()
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
def add_common(p):
|
||||
p.add_argument("--date", default=date.today().isoformat())
|
||||
p.add_argument("--price-min", type=float, default=1.0)
|
||||
p.add_argument("--price-max", type=float, default=20.0)
|
||||
|
||||
p_sym = sub.add_parser("symbols", help="list symbols with bars for a session")
|
||||
add_common(p_sym)
|
||||
p_sym.add_argument("--limit", type=int, default=50)
|
||||
|
||||
p_gap = sub.add_parser("gappers", help="scan for gappers and store snapshot")
|
||||
add_common(p_gap)
|
||||
p_gap.add_argument("--min-gap", type=float, default=5.0)
|
||||
p_gap.add_argument("--min-rvol", type=float, default=2.0)
|
||||
|
||||
sub.add_parser("news", help="ingest RSS feeds into news.duckdb")
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
if hasattr(args, "date"):
|
||||
args.session_date = date.fromisoformat(args.date)
|
||||
return {"symbols": cmd_symbols, "gappers": cmd_gappers, "news": cmd_news}[args.command](args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user