|
1 | 1 | """Instruments routes - list instruments and timeframes from DuckDB.""" |
| 2 | +from datetime import datetime |
2 | 3 | from typing import Optional |
3 | 4 |
|
4 | 5 | from fastapi import APIRouter, Depends, Query, HTTPException |
5 | 6 |
|
6 | 7 | from src.config import ALLOW_PUBLIC_READS |
7 | 8 | from src.core.database import User |
8 | | -from src.core.datalake import list_instruments, list_timeframes, get_data_range, list_tick_instruments, get_tick_coverage |
9 | | -from src.services.validators import validate_instrument |
| 9 | +from src.core.datalake import ( |
| 10 | + list_instruments, |
| 11 | + list_timeframes, |
| 12 | + get_data_range, |
| 13 | + list_tick_instruments, |
| 14 | + get_tick_coverage, |
| 15 | + delete_ohlc_data, |
| 16 | + delete_tick_data, |
| 17 | +) |
| 18 | +from src.services.validators import validate_instrument, validate_timeframe |
10 | 19 | from src.auth.auth import ScopedAuth |
11 | 20 |
|
12 | 21 | router = APIRouter() |
@@ -62,6 +71,53 @@ def get_instrument_detail( |
62 | 71 | return {"symbol": symbol, "timeframes": coverage} |
63 | 72 |
|
64 | 73 |
|
| 74 | +@router.delete("/instruments/{symbol}") |
| 75 | +def delete_instrument_data( |
| 76 | + symbol: str, |
| 77 | + timeframe: Optional[str] = Query(None, description="If set, only delete this timeframe (or 'TICK' for ticks)."), |
| 78 | + start: Optional[datetime] = Query(None, description="Half-open window start (UTC). Inclusive."), |
| 79 | + end: Optional[datetime] = Query(None, description="Half-open window end (UTC). Exclusive."), |
| 80 | + include_ticks: bool = Query(True, description="Also delete tick_data when timeframe is unset."), |
| 81 | + confirm: bool = Query(False, description="Must be true — guard against accidental DELETE."), |
| 82 | + current_user: User = Depends(ScopedAuth("admin")), |
| 83 | +): |
| 84 | + """ |
| 85 | + Delete OHLC and/or tick rows for a symbol. Admin-only. |
| 86 | +
|
| 87 | + - No `timeframe`: wipes every OHLC timeframe for the symbol; also wipes ticks |
| 88 | + unless `include_ticks=false`. |
| 89 | + - `timeframe=TICK`: only ticks. |
| 90 | + - `timeframe=M1` (etc): only that OHLC timeframe. |
| 91 | + - `start`/`end` scope by window; omit for full range. |
| 92 | + """ |
| 93 | + if not confirm: |
| 94 | + raise HTTPException(status_code=400, detail="Pass confirm=true to actually delete.") |
| 95 | + |
| 96 | + symbol = validate_instrument(symbol) |
| 97 | + |
| 98 | + deleted = {} |
| 99 | + if timeframe is None: |
| 100 | + deleted["ohlc"] = delete_ohlc_data(symbol, start=start, end=end) |
| 101 | + if include_ticks: |
| 102 | + deleted["ticks"] = delete_tick_data(symbol, start=start, end=end) |
| 103 | + elif timeframe.upper() == "TICK": |
| 104 | + deleted["ticks"] = delete_tick_data(symbol, start=start, end=end) |
| 105 | + else: |
| 106 | + tf = validate_timeframe(timeframe) |
| 107 | + deleted["ohlc"] = delete_ohlc_data(symbol, timeframe=tf, start=start, end=end) |
| 108 | + |
| 109 | + return { |
| 110 | + "status": "ok", |
| 111 | + "symbol": symbol, |
| 112 | + "timeframe": timeframe, |
| 113 | + "window": { |
| 114 | + "start": start.isoformat() if start else None, |
| 115 | + "end": end.isoformat() if end else None, |
| 116 | + }, |
| 117 | + "deleted": deleted, |
| 118 | + } |
| 119 | + |
| 120 | + |
65 | 121 | @router.get("/timeframes") |
66 | 122 | def get_timeframes( |
67 | 123 | instrument: str | None = Query(None), |
|
0 commit comments