Commit Graph

158 Commits

Author SHA1 Message Date
Adriano 5ce33b1b3c docs(api): note Hyperliquid get_historical paging in API_REFERENCE
get_historical now pages+trims wide ranges on Hyperliquid too (fe7f8a1),
not just Deribit. Generalize the start_date semantics note accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 12:10:18 +00:00
Adriano fe7f8a152b fix(hyperliquid): page+trim get_historical past 5000-candle cap
candleSnapshot caps each response at ~5000 candles and keeps the newest
slice, silently dropping older candles when the range needs more — so a
wide start_date was not honored. Mirror Deribit's approach: page the range
in windows of interval_ms * 5000, trim each page back to [start_ms, end_ms],
dedupe by timestamp, with a 500-page hard stop. Resolutions without a known
interval fall back to the original single-call path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 12:07:02 +00:00
Adriano 9a74052dc5 feat(deribit): get_open_orders (limit resting + trigger non scattati)
Espone private/get_open_orders_by_currency sul router Deribit (pattern gia'
presente per Hyperliquid/IBKR). Serve al reconciler resting di PythagorasGoal:
TP/DSL attesi dai libri vs ordini realmente in book (caso MR02_BTC 2026-06-12:
TP resting fillato di notte + disaster-SL sparito, scoperti solo al close sim).
NB: per i trigger untriggered interrogare anche type='trigger_all' e fare
merge per order_id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:23:01 +00:00
Adriano 350eeb4f76 chore(ops): add prune-acme-stale.sh to remove dead domains from Traefik acme.json
Stale certificate entries (mattermost/zar/registry.tielogic.xyz) in
Traefik's acme.json triggered repeated NXDOMAIN renewal errors. Script
atomically stops Traefik, backs up acme.json, filters out the listed
domains with JSON validation + auto-rollback, restores 600 root:adriano
perms, and restarts. Domain list is extensible via the STALE array.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:06:26 +00:00
Adriano ee799f3903 fix(deploy): health-check via docker network, not host port
The VPS doesn't publish the app port to the host (Traefik reaches it over
the docker network), so curl http://localhost:PORT/health always failed —
a false negative that could trigger an unwanted automatic rollback on a
successful deploy. Check /health from INSIDE the container via
`docker compose exec` instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:27:41 +00:00
Adriano 726984e4f1 chore: gitignore .claude/ session artifacts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 11:42:28 +00:00
Adriano 6fba410e6f docs(V2): move API_REFERENCE.md to repo root
git mv docs/API_REFERENCE.md → API_REFERENCE.md (history preserved) and
update the CLAUDE.md reference accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 11:09:02 +00:00
Adriano 8d7e2ca30f refactor(V2): get_instruments common-only on /mcp (enriched with Deribit filters)
Move get_instruments off the per-exchange Deribit router and onto the
unified interface, without losing the advanced Deribit filtering.

- /mcp/tools/get_instruments now accepts the Deribit-specific filters
  (currency, kind, expiry_from/to, strike_min/max, min_open_interest) and
  pagination (offset/limit). Venues that list everything (Hyperliquid)
  ignore them. Response adds `meta: {deribit: {total, offset, limit,
  has_more}}` surfacing per-source pagination.
- Remove /mcp-deribit/tools/get_instruments (endpoint + tool wrapper +
  GetInstrumentsReq schema). The DeribitClient.get_instruments METHOD
  stays — it's what the unified normalizer calls.

Tests: cover the enriched filters + meta passthrough; app-boot asserts
/mcp-deribit/tools/get_instruments is gone. Docs (API_REFERENCE/README/
CLAUDE) and smoke updated; also fixed the stale Hyperliquid tool count
(14 → 15) found during the doc check.

325 passed, ruff clean. Verified live: kind=option limit=5 → 5/940
has_more=true; /mcp-deribit/tools/get_instruments → 404.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 11:06:09 +00:00
Adriano 6faf21369d refactor(V2): get_historical & get_indicators are common-only on /mcp
Consolidate the cross-cutting data tools onto the unified interface and
remove their per-exchange duplicates.

- /mcp/tools/get_historical: single call, `exchange` now optional. Set →
  that venue's candles (native symbol); omitted → cross-exchange consensus
  (canonical symbol, median OHLC + div_pct). Folds in the former
  /mcp-cross consensus, whose router is deleted.
- /mcp/tools/get_indicators: new common tool computing sma/rsi/atr/macd/adx
  over the single-or-consensus series. Computation centralized in
  common/indicators.py::compute_indicators (was duplicated per exchange).
- Remove get_historical from /mcp-deribit and /mcp-hyperliquid; remove
  get_technical_indicators (deribit) and get_indicators (hyperliquid),
  including the now-dead client methods, Req schemas and tool wrappers.
  The client get_historical METHODS stay (used by the unified dispatch).

Tests: rewrite cross tests into test_unified (instruments, historical
single + consensus + failures, indicators single + consensus); drop the
old CrossClient test; tighten app-boot surface assertions (/mcp owns the
data tools; no /mcp-cross, no per-exchange historical/indicators).

Docs: API_REFERENCE / README / CLAUDE updated; smoke run_unified.sh now
covers consensus + indicators.

324 passed, ruff clean. Verified live against Deribit/Hyperliquid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 10:51:21 +00:00
Adriano 2272d5520b test(V2): add /mcp unified-interface smoke script
tests/smoke/run_unified.sh exercises the common interface against a live
server + real upstreams (Deribit/Hyperliquid public endpoints):
get_instruments (single + fan-out, asserting fees/history_start),
get_historical for both venues, and a 422 for an unsupported exchange.
Refresh tests/smoke/README.md (drop stale Bybit/Alpaca references).

Verified green end-to-end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 10:31:15 +00:00
Adriano 652e50ad99 docs(V2): update README + add CLAUDE.md for /mcp interface and Bybit/Alpaca retirement
- README: three active exchanges (Deribit/Hyperliquid/IBKR); document the
  unified /mcp interface (get_instruments uniform schema + per-exchange
  get_historical); drop Bybit/Alpaca from endpoints, tool list, audit
  filters, migration table and URL-override notes; update test count (323)
  and source layout (routers/unified.py, old/).
- CLAUDE.md: new project guide — commands (uv/pytest/ruff/mypy), runtime
  architecture, active exchanges, data interfaces, conventions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 10:07:17 +00:00
Adriano bc75d3980a feat(V2): unified /mcp interface; retire Bybit/Alpaca to old/
Add a common cross-exchange interface (/mcp) over the integrated venues
(deribit, hyperliquid):

- get_instruments: uniform schema where each row carries its own
  `exchange`, `fees` (maker/taker, live from Deribit, null where the
  venue has no per-instrument schedule) and `history_start` (listing
  date, live from Deribit creation_timestamp), plus type/tick_size and a
  lossless `native` blob. Optional `exchange` filter; fan-out otherwise.
- get_historical: generalized to {exchange, instrument, interval,
  start_date, end_date}, returning a single chosen venue's candles.
  Consensus merge stays available on /mcp-cross.

New: routers/unified.py, exchanges/cross/instruments.py (normalizers),
UnifiedClient in cross/client.py, schemas in cross/tools.py. Deribit
get_instruments now also surfaces maker/taker_commission and
creation_timestamp (additive).

Retire Bybit and Alpaca from the API surface: move clients, routers,
settings classes and their tests under old/ (history preserved via
git mv); drop them from the builder, /mcp-cross dispatch and symbol_map.
Bybit remains a public funding/OI data source in sentiment (not the
trading client). IBKR is intentionally excluded from /mcp for now.

Docs: rewrite API_REFERENCE.md (remove Bybit/Alpaca, document /mcp,
clarify that data_timestamp is injected globally by middleware).

Tests: add unified-interface coverage; update cross/settings/builder/boot
tests for the reduced venue set. Fix a pre-existing flaky assertion in
the Hyperliquid signing test (r/s use eth_utils.to_hex like the official
SDK, so a leading zero byte yields <66 chars ~1/256 of the time).

323 passed, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:09:22 +00:00
Adriano Dal Pastro eb5f0148ad fix(V2): Deribit get_historical end_date day-inclusive + pagination
end_date as a bare date now covers the whole UTC day (23:59:59.999)
instead of stopping at midnight, so end_date=today returns intraday up
to the last closed candle. Dates are parsed as explicit UTC and inline
timestamps (YYYY-MM-DDTHH:MM:SS) are honored for precise windows.

Wide ranges are paged in <=5000-candle windows so Deribit's per-call
cap can no longer silently drop start_date and the oldest candles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 14:25:55 +00:00
Adriano Dal Pastro c5e48bf081 docs(V2): add API_REFERENCE.md with full MCP tool catalog
Inventory of all 8 MCP namespaces (~142 tools) generated from the
FastAPI router sources — single reference for client/bot integrators.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 08:06:32 +00:00
root 91aadaea6a docs(V2): document /mcp-cross historical aggregator
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:42:20 +00:00
root 0ba5a05219 feat(V2): /mcp-cross/tools/get_historical with cross-exchange consensus
Add a unified historical endpoint that fans out to every exchange
supporting the requested (asset_class, symbol) pair, then merges the
results into a single consensus candle series with per-bar divergence
metrics:
  - candles[i].close = median across sources
  - candles[i].sources = count of contributing exchanges
  - candles[i].div_pct = (max-min)/median for that bar's close

Crypto routes BTC/ETH/SOL across Bybit + Hyperliquid + Deribit; equities
route to Alpaca for now (IBKR omitted from MVP because its bars endpoint
takes a relative period instead of start/end). Partial failures return a
warning envelope (failed_sources) instead of failing the whole request;
all sources failing → HTTP 502.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:41:18 +00:00
root c94312d79f feat(V2): shared Candle validator + uniform 'candles' response key
Introduce common/candles.py with a Pydantic Candle model enforcing OHLC
consistency (high≥max, low≤min), non-negative volume and positive
timestamp. validate_candles() coerces upstream rows, sorts by timestamp
and raises HTTPException(502) on malformed data — surfacing upstream
data corruption as a retryable envelope instead of silently returning
nonsense.

Wired into all five exchange historical endpoints (Bybit, Hyperliquid,
Deribit, Alpaca, IBKR). BREAKING: Alpaca get_bars and IBKR get_bars now
return 'candles' (was 'bars') to align with the other exchanges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:19:20 +00:00
root 110ca7f5cf docs(V2): update README for IBKR integration
Add IBKR to the exchange list, endpoint table, audit filter values, and
Tool disponibili. Bump test count to 366 and reorder IBKR Setup before
Licenza.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 08:54:54 +00:00
root a56baad3dd fix(V2): hoist _IBKRRotateConfirmReq to module level
Defining the Pydantic body model inside make_admin_router() leaves an
unresolved forward reference under `from __future__ import annotations`,
which breaks /openapi.json generation with PydanticUserError.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 08:45:20 +00:00
root f8fb50cb83 fix(V2): map Deribit upstream 5xx / non-JSON to clean HTTPException 502
Cloudflare 5xx pages from Deribit testnet were leaking through the JSON
parser as JSONDecodeError → UNHANDLED_EXCEPTION. Wrap response parsing so
upstream errors surface as a retryable HTTP_502 envelope instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 08:27:33 +00:00
root 880faa7fd4 refactor(V2): IBKR final review fixes — WS shutdown, conid match, clock note
Final code-review fixes:
- __main__: lifespan stops IBKRWebSocket singletons before registry close
- close_position: resolve symbol→conid first, match positions on conid
  (was matching contractDesc which is a long display string, not ticker)
- close_all_positions: prefer ticker field, fallback to contractDesc
- get_clock: explicit approximate=true + note about US holidays/half-days

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:46:11 +00:00
root cddf88afb4 feat(V2): IBKR OAuth setup script + docker secrets mount + docs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:40:06 +00:00
root 55bfeca88e feat(V2): IBKR key rotation admin endpoints + health probe
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:37:29 +00:00
root bea37fd734 feat(V2): IBKR router wiring + build_client + WS singleton DI
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:35:28 +00:00
root 6940e2865b feat(V2): IBKR key rotation manager with auto-rollback
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:32:25 +00:00
root bdc40929d4 feat(V2): IBKR complex order tools (bracket/OCO/OTO)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:30:05 +00:00
root 9bbc8c05f1 feat(V2): IBKR complex order payload builders (bracket/OCO/OTO)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:27:26 +00:00
root 3510605fdd feat(V2): IBKR simple write tools (place/amend/cancel/close)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:25:34 +00:00
root 8914d613ec feat(V2): IBKR streaming tools (tick/depth/subscribe)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:23:39 +00:00
root 531b7b019c feat(V2): IBKR read tool schemas + dispatch functions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:21:24 +00:00
root 6266708e15 refactor(V2): IBKR WebSocket — fix stop/start cycle, guard rails, log disconnect
Code review fixes (commit 17700d2):
- _stopped reset on start() (was stuck True after stop→start)
- _require_started guard on subscribe_*/unsubscribe (clear WSError vs AttributeError)
- _reader_loop logs disconnect via logger.warning + sets _ws=None for `connected` signal
- Class docstring documents stale-snapshot behavior + deferred reconnect
- New tests: subscribe-before-start, stop→start cycle resumption

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:18:57 +00:00
root 17700d27a0 feat(V2): IBKR WebSocket layer + tick/depth snapshot cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:15:25 +00:00
root 12002642e5 refactor(V2): IBKR client — remove dead whitelist + max_cycles test
Code review polish (commit b9c58a3):
- Remove unused _AUTO_CONFIRM_WHITELIST (was scaffolding, never wired)
- Replace with policy comment documenting auto-confirm behavior
- New test: test_place_order_too_many_confirmations

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:37:37 +00:00
root b9c58a376f feat(V2): IBKR write methods + auto-confirm warning flow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:34:43 +00:00
root ded4414b32 refactor(V2): IBKR client read methods — defensive conid + sec_type DRY
Code review fixes (commit 611a269):
- resolve_conid validates conid key presence (was raw KeyError on malformed)
- _SEC_TYPE_MAP module constant — reused in get_ticker + get_bars
  (also fixes get_bars previously missing "forex": "CASH")
- New tests: empty response + malformed response error paths

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:32:50 +00:00
root 611a2695a9 feat(V2): IBKR client read methods + conid LRU cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:29:11 +00:00
root f4f4e4efd7 refactor(V2): IBKR client — log tickle, type _http, retry once on 401
Code review fixes (commit 0c74691):
- _maybe_tickle logs failures via logger.debug instead of silent pass
- _http typed as httpx.AsyncClient | None
- 30s timeout commented (matches Alpaca, IBKR gateway latency)
- _request retries once on 401 with forced LST refresh (spec §4 IBKR_AUTH_FAILED)
- New tests: test_request_retries_once_on_401, test_request_raises_on_persistent_401

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:27:37 +00:00
root 0c74691e7c feat(V2): IBKR client base + auth header + tickle keep-alive
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:22:51 +00:00
root b49b2b36e0 refactor(V2): IBKR OAuth — named constants, explicit raises, lifted import
Code review fixes (commit 92da6aa):
- LST refresh buffer / fallback TTL extracted as named module constants
- Replace `assert` with explicit `if/raise` (asserts stripped under -O)
- Move IBKRAuthError above OAuth1aSigner (forward declaration)
- async_client import lifted to module level
- Test uses actual prime (23) instead of composite (255)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:20:40 +00:00
root 92da6aa842 feat(V2): IBKR live session token mint via DH key exchange
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:15:17 +00:00
root a90c5c4d6f refactor(V2): IBKR OAuth signer — type tightening + verify-based test
Code review polish:
- _signature_key/_encryption_key typed as RSAPrivateKey | None
- sign() uses assert instead of type: ignore
- test_oauth_signer_signs_with_rsa verifies signature against public key
- Clarifying comments on %3D/%26 manual encoding and Task 3 imports

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:12:48 +00:00
root ae63aaf69a feat(V2): IBKR OAuth1a signer + RSA-SHA256 signature
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:08:15 +00:00
root 92cc45c896 refactor(V2): IBKR settings — TypedDict return + docstrings
Code review polish:
- credentials() returns IBKRCredentials TypedDict (was bare dict)
- Method docstring matching Deribit pattern
- Inline comment explaining account_id env-only design

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:04:08 +00:00
root 3a85ff05e6 feat(V2): IBKR settings + env-specific credentials
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:00:15 +00:00
root 391f2c02e0 docs(V2): IBKR integration implementation plan
16 task TDD-disciplinati con 94 step checkbox, riferimento allo spec
2026-05-03. Ogni task: red-green-commit con codice completo nello step.
Copre: settings, OAuth1a signer + DH LST mint, IBKRClient REST + conid
cache + tickle, IBKRWebSocket tick/depth snapshot-on-demand, simple +
complex orders (bracket/OCO/OTO), KeyRotationManager con auto-rollback,
admin endpoints, router wiring, OAuth setup script, docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:55:38 +00:00
root 109b8e4686 docs(V2): IBKR integration design spec
Approach A2 (Client Portal Web API + OAuth 1.0a Self-Service): fully
unattended REST con setup iniziale one-time, niente sidecar Java.
Scope V1 include: simple+complex orders (bracket/OCO/OTO), WebSocket
streaming snapshot-on-demand (tick+depth), key rotation semi-automatica
con auto-rollback. 8-commit plan atomico, ~6-8 giorni dev.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:23:08 +00:00
root 1ca1687c9b feat(V2): Deribit credenziali per env (CLIENT_ID/SECRET _TESTNET / _LIVE)
DeribitSettings ora supporta coppie credenziali distinte per testnet e
mainnet via DERIBIT_CLIENT_ID_TESTNET/_LIVE e DERIBIT_CLIENT_SECRET_TESTNET/_LIVE.
Le coppie env-specifiche prevalgono sulla coppia base
DERIBIT_CLIENT_ID/DERIBIT_CLIENT_SECRET (mantenuta per backward compat).

build_client risolve la coppia giusta tramite settings.deribit.credentials(env);
ValueError esplicito se nessuna coppia configurata per l'env richiesto.

+4 test (legacy single, per-env, override, missing). Fix anche isolation
da .env reale via monkeypatch.chdir(tmp_path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:46:47 +00:00
root 8a0f37ebc2 fix(V2): get_account_summary error path → numeric fields None invece di 0
Su errore di Deribit (auth fallita, ecc.) i campi equity/balance/margin/
available/unrealized_pnl/total_pnl ora sono None: signal chiaro di "valore
ignoto" vs "saldo realmente a zero". Risolve ambiguità lato client che
leggevano equity=0 senza accorgersi del campo error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:00:57 +00:00
root 6640ede3df fix(V2): Deribit _authenticate gestisce error envelope (no più KeyError 'result')
Quando Deribit risponde con {"error": {...}} su public/auth (creds errate,
scope mancante, env mismatch), il client esplodeva con KeyError: 'result' →
500 UNHANDLED_EXCEPTION sui tool privati (get_account_summary, get_positions).
Ora _authenticate solleva DeribitAuthError tipizzata, _request la converte
in error envelope coerente con il resto del flusso.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:52:43 +00:00
root d8136713b9 feat(V2): integrazione Traefik con TLS + watchtower, rimosso port mapping diretto
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:21:52 +00:00