135 lines
5.2 KiB
Python
135 lines
5.2 KiB
Python
"""
|
||
Data Manager CLI — command-line interface for data library management.
|
||
|
||
Usage:
|
||
python -m data_manager.cli --status
|
||
python -m data_manager.cli --prefetch --symbols EURUSD XAUUSD --tf D1 M15
|
||
python -m data_manager.cli --history --symbol EURUSD --limit 20
|
||
python -m data_manager.cli --purge --symbol EURUSD --tf D1
|
||
python -m data_manager.cli --validate
|
||
"""
|
||
|
||
import argparse
|
||
import sys
|
||
import logging
|
||
from datetime import datetime, timedelta, timezone
|
||
from pathlib import Path
|
||
|
||
# Ensure parent dir is on path for relative imports
|
||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||
|
||
from data_manager import DataManager
|
||
|
||
|
||
def cmd_status(dm: DataManager):
|
||
"""Print catalog summary."""
|
||
summary = dm.get_catalog_summary()
|
||
if not summary:
|
||
print("\n [DATA LIBRARY] Empty — no datasets cached yet.\n")
|
||
return
|
||
print(f"\n [DATA LIBRARY] {dm.library_root}")
|
||
print(f" {'Symbol':<10} {'TF':<6} {'Type':<12} {'From':<12} {'To':<12} {'Rows':>8} {'Source':<6}")
|
||
print(" " + "─" * 72)
|
||
for r in summary:
|
||
d_from = r.get("date_from", "")[:10] if r.get("date_from") else "—"
|
||
d_to = r.get("date_to", "")[:10] if r.get("date_to") else "—"
|
||
print(f" {r['symbol']:<10} {r['timeframe']:<6} {r['data_type']:<12} "
|
||
f"{d_from:<12} {d_to:<12} {r.get('row_count',0):>8} {r.get('source','?'):<6}")
|
||
print()
|
||
|
||
|
||
def cmd_prefetch(dm: DataManager, symbols: list, timeframes: list, days: int):
|
||
"""Batch warm-up cache for given symbols/timeframes."""
|
||
date_from = datetime.now(timezone.utc) - timedelta(days=days)
|
||
date_to = datetime.now(timezone.utc)
|
||
print(f"\n Prefetching {len(symbols)} symbols × {len(timeframes)} timeframes "
|
||
f"({days} days back)...\n")
|
||
dm.prefetch(symbols, timeframes, date_from, date_to)
|
||
print("\n Done.\n")
|
||
|
||
|
||
def cmd_history(dm: DataManager, symbol: str, limit: int):
|
||
"""Show fetch history."""
|
||
history = dm.get_fetch_history(symbol, limit)
|
||
if not history:
|
||
print(f"\n No fetch history{' for '+symbol if symbol else ''}.\n")
|
||
return
|
||
print(f"\n {'Time':<22} {'Symbol':<10} {'TF':<6} {'Rows':>8} {'ms':>8} {'Status':<10}")
|
||
print(" " + "─" * 68)
|
||
for r in history:
|
||
ts = r.get("timestamp", "")[:19]
|
||
print(f" {ts:<22} {r['symbol']:<10} {r['timeframe']:<6} "
|
||
f"{r.get('rows_returned',0):>8} {r.get('duration_ms',0):>8} {r.get('status','?'):<10}")
|
||
print()
|
||
|
||
|
||
def cmd_validate(dm: DataManager):
|
||
"""Validate all cached Parquet files."""
|
||
summary = dm.get_catalog_summary()
|
||
if not summary:
|
||
print("\n Nothing to validate — library is empty.\n")
|
||
return
|
||
print(f"\n Validating {len(summary)} datasets...")
|
||
all_ok = True
|
||
for r in summary:
|
||
result = dm.store.validate(r["symbol"], r["timeframe"], r.get("data_type", "ohlcv"))
|
||
tag = "✓" if result.valid else "✗"
|
||
print(f" {tag} {r['symbol']:<10} {r['timeframe']:<6} rows={result.row_count}", end="")
|
||
if not result.valid:
|
||
all_ok = False
|
||
print(f" ISSUES: {'; '.join(result.issues)}")
|
||
else:
|
||
print()
|
||
print(f"\n {'All datasets valid.' if all_ok else 'Some datasets have issues — see above.'}\n")
|
||
|
||
|
||
def cmd_purge(dm: DataManager, symbol: str, timeframe: str, data_type: str):
|
||
"""Remove a specific dataset from cache."""
|
||
dm.purge(symbol, timeframe, data_type)
|
||
print(f"\n Purged {symbol} {timeframe} {data_type}\n")
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="Data Manager CLI")
|
||
parser.add_argument("--status", action="store_true", help="Show catalog summary")
|
||
parser.add_argument("--prefetch", action="store_true", help="Batch prefetch data")
|
||
parser.add_argument("--history", action="store_true", help="Show fetch history")
|
||
parser.add_argument("--validate", action="store_true", help="Validate all cached files")
|
||
parser.add_argument("--purge", action="store_true", help="Purge a dataset")
|
||
|
||
parser.add_argument("--symbols", nargs="+", default=[])
|
||
parser.add_argument("--symbol", type=str, default=None)
|
||
parser.add_argument("--tf", nargs="+", default=["D1"])
|
||
parser.add_argument("--days", type=int, default=1500, help="Days back for prefetch")
|
||
parser.add_argument("--limit", type=int, default=50)
|
||
parser.add_argument("--data-type", type=str, default="ohlcv")
|
||
parser.add_argument("--mt5-path", type=str, default=None)
|
||
|
||
args = parser.parse_args()
|
||
|
||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||
dm = DataManager(mt5_terminal_path=args.mt5_path, auto_init_mt5=True)
|
||
|
||
if args.status:
|
||
cmd_status(dm)
|
||
elif args.prefetch:
|
||
if not args.symbols:
|
||
print(" --prefetch requires --symbols")
|
||
sys.exit(1)
|
||
cmd_prefetch(dm, args.symbols, args.tf, args.days)
|
||
elif args.history:
|
||
cmd_history(dm, args.symbol, args.limit)
|
||
elif args.validate:
|
||
cmd_validate(dm)
|
||
elif args.purge:
|
||
if not args.symbol or not args.tf:
|
||
print(" --purge requires --symbol and --tf")
|
||
sys.exit(1)
|
||
cmd_purge(dm, args.symbol, args.tf[0], args.data_type)
|
||
else:
|
||
parser.print_help()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|