Sync OKX options closed trades into hub archive with a separate tab.
Mirror perpetual archive flow into archive_options_trade_cache for offline calendar and review. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+138
-19
@@ -79,6 +79,12 @@ from lib.hub.hub_symbol_archive_lib import (
|
||||
update_review_quote,
|
||||
upsert_trade_overlay,
|
||||
)
|
||||
from lib.hub.hub_options_archive_lib import (
|
||||
init_options_archive_db,
|
||||
list_archive_options_calendar,
|
||||
list_daily_options_trades,
|
||||
sync_options_exchange_archive,
|
||||
)
|
||||
from lib.hub.hub_entry_plan_lib import (
|
||||
compute_entry_plan_stats,
|
||||
create_entry_plan,
|
||||
@@ -355,9 +361,11 @@ def _schedule_board_refresh() -> None:
|
||||
async def _run_archive_sync_once() -> dict:
|
||||
global _last_archive_sync
|
||||
init_archive_db()
|
||||
init_options_archive_db()
|
||||
settings = load_settings()
|
||||
targets = enabled_exchanges(settings)
|
||||
results: list[dict] = []
|
||||
options_results: list[dict] = []
|
||||
for ex in targets:
|
||||
ex_key = str(ex.get("key") or "").strip().lower()
|
||||
if not ex_key:
|
||||
@@ -390,34 +398,71 @@ async def _run_archive_sync_once() -> dict:
|
||||
"msg": msg,
|
||||
}
|
||||
)
|
||||
else:
|
||||
trades = trades_resp.get("trades") or []
|
||||
for t in trades:
|
||||
if isinstance(t, dict):
|
||||
t["exchange_key"] = ex_key
|
||||
|
||||
def remote_fetch(**kwargs):
|
||||
return _fetch_instance_ohlcv_sync(
|
||||
ex,
|
||||
symbol=kwargs.get("symbol") or "",
|
||||
timeframe=kwargs.get("timeframe") or "5m",
|
||||
since_ms=kwargs.get("since_ms"),
|
||||
limit=int(kwargs.get("limit") or 500),
|
||||
)
|
||||
|
||||
r = await asyncio.to_thread(
|
||||
sync_exchange_symbol_archives,
|
||||
ex_key,
|
||||
trades,
|
||||
remote_fetch,
|
||||
)
|
||||
r["name"] = ex.get("name")
|
||||
r["trade_count"] = len(trades)
|
||||
results.append(r)
|
||||
|
||||
caps = [str(x).lower() for x in (ex.get("capabilities") or [])]
|
||||
if "options" not in caps:
|
||||
continue
|
||||
trades = trades_resp.get("trades") or []
|
||||
for t in trades:
|
||||
opt_resp = await asyncio.to_thread(
|
||||
_fetch_instance_options_review_archive_sync,
|
||||
ex,
|
||||
days=ARCHIVE_TRADE_DAYS,
|
||||
limit=ARCHIVE_TRADE_LIMIT,
|
||||
)
|
||||
if not opt_resp.get("ok"):
|
||||
options_results.append(
|
||||
{
|
||||
"exchange_key": ex_key,
|
||||
"name": ex.get("name"),
|
||||
"ok": False,
|
||||
"status": opt_resp.get("status"),
|
||||
"msg": opt_resp.get("msg")
|
||||
or opt_resp.get("error")
|
||||
or opt_resp.get("detail")
|
||||
or "拉取期权复盘失败",
|
||||
"product": "options",
|
||||
}
|
||||
)
|
||||
continue
|
||||
opt_trades = opt_resp.get("trades") or []
|
||||
for t in opt_trades:
|
||||
if isinstance(t, dict):
|
||||
t["exchange_key"] = ex_key
|
||||
|
||||
def remote_fetch(**kwargs):
|
||||
return _fetch_instance_ohlcv_sync(
|
||||
ex,
|
||||
symbol=kwargs.get("symbol") or "",
|
||||
timeframe=kwargs.get("timeframe") or "5m",
|
||||
since_ms=kwargs.get("since_ms"),
|
||||
limit=int(kwargs.get("limit") or 500),
|
||||
)
|
||||
|
||||
r = await asyncio.to_thread(
|
||||
sync_exchange_symbol_archives,
|
||||
orow = await asyncio.to_thread(
|
||||
sync_options_exchange_archive,
|
||||
ex_key,
|
||||
trades,
|
||||
remote_fetch,
|
||||
opt_trades,
|
||||
)
|
||||
r["name"] = ex.get("name")
|
||||
r["trade_count"] = len(trades)
|
||||
results.append(r)
|
||||
orow["name"] = ex.get("name")
|
||||
options_results.append(orow)
|
||||
out = {
|
||||
"ok": True,
|
||||
"exchanges": len(targets),
|
||||
"results": results,
|
||||
"options_results": options_results,
|
||||
"updated_at": __import__("datetime").datetime.now().isoformat(timespec="seconds"),
|
||||
}
|
||||
_last_archive_sync = out
|
||||
@@ -1365,6 +1410,34 @@ def _fetch_instance_trades_archive_sync(
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
def _fetch_instance_options_review_archive_sync(
|
||||
ex: dict,
|
||||
*,
|
||||
days: int = 365,
|
||||
limit: int = 2000,
|
||||
) -> dict:
|
||||
base = (ex.get("flask_url") or "").rstrip("/")
|
||||
if not base:
|
||||
return {"ok": False, "msg": "未配置 flask_url"}
|
||||
params = {"days": str(int(days)), "limit": str(int(limit))}
|
||||
url = f"{base}/api/hub/options/review/archive?{urlencode(params)}"
|
||||
try:
|
||||
with httpx.Client(timeout=max(HUB_FLASK_TIMEOUT, 120.0)) as client:
|
||||
r = client.get(url, headers=_hub_headers())
|
||||
if r.status_code >= 400:
|
||||
parsed = _parse_http_json_body(r)
|
||||
parsed.setdefault("ok", False)
|
||||
parsed.setdefault("status", r.status_code)
|
||||
return parsed
|
||||
data = r.json() if r.content else {}
|
||||
if isinstance(data, dict):
|
||||
data.setdefault("ok", True)
|
||||
return data
|
||||
return {"ok": False, "msg": "无效 JSON"}
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
def _fetch_instance_ohlcv_sync(
|
||||
ex: dict,
|
||||
*,
|
||||
@@ -3145,6 +3218,52 @@ def api_archive_calendar(
|
||||
return {"ok": True, **payload}
|
||||
|
||||
|
||||
@app.get("/api/archive/options/daily-trades")
|
||||
def api_archive_options_daily_trades(
|
||||
period: str = "",
|
||||
trading_day: str = "",
|
||||
date_from: str = "",
|
||||
date_to: str = "",
|
||||
exchange_key: str = "",
|
||||
filter_profit: str = "",
|
||||
filter_loss: str = "",
|
||||
search: str = "",
|
||||
source_type: str = "",
|
||||
):
|
||||
init_options_archive_db()
|
||||
payload = list_daily_options_trades(
|
||||
trading_day=trading_day,
|
||||
period=period or "today",
|
||||
date_from=date_from,
|
||||
date_to=date_to,
|
||||
exchange_key=exchange_key,
|
||||
filter_profit=(filter_profit or "").lower() in ("1", "true", "yes", "on"),
|
||||
filter_loss=(filter_loss or "").lower() in ("1", "true", "yes", "on"),
|
||||
search=search,
|
||||
source_type=source_type,
|
||||
)
|
||||
return {"ok": True, **payload}
|
||||
|
||||
|
||||
@app.get("/api/archive/options/calendar")
|
||||
def api_archive_options_calendar(
|
||||
year: int = 0,
|
||||
month: int = 0,
|
||||
exchange_key: str = "",
|
||||
):
|
||||
init_options_archive_db()
|
||||
if year <= 0 or month <= 0:
|
||||
td = today_trading_day()
|
||||
parts = td.split("-")
|
||||
year = int(parts[0])
|
||||
month = int(parts[1])
|
||||
try:
|
||||
payload = list_archive_options_calendar(year, month, exchange_key=exchange_key)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
return {"ok": True, **payload}
|
||||
|
||||
|
||||
@app.get("/api/archive/quotes")
|
||||
def api_archive_quotes():
|
||||
init_archive_db()
|
||||
|
||||
Reference in New Issue
Block a user