readme

chstrategy.xyz

Piattaforma personale per gestire in un unico posto strategia di investimento, patrimonio e operatività crypto. Una homepage semplice con tre bottoni porta a tre sezioni indipendenti, ognuna con la propria dashboard, come tre piattaforme a sé stanti: Strategia, Assets e Trading.

Un concetto attraversa tutte le sezioni: il portafoglio. Ogni portafoglio (il proprio privato, quello di una cliente) è un'entità separata nel database, con le sue piattaforme, le sue posizioni e il suo track record. L'accesso alle tre sezioni richiede il login (Clerk, pagine /accedi e /registrati in italiano e con il tema del sito): al clic su una sezione dalla home si apre un selettore minimale al centro della pagina con i portafogli dell'utente loggato, dove se ne sceglie uno o se ne crea uno nuovo, e solo allora si entra. Il portafoglio attivo resta visibile nell'intestazione della sezione, da cui si può cambiare, e chi entra in una sezione da un link diretto senza selezione trova lo stesso selettore ad attenderlo.

I dati su Convex seguono la stessa gerarchia a tre livelli: prima l'utente loggato (il subject del JWT Clerk), poi il portafoglio selezionato, poi la o le piattaforme scelte (per esempio l'account con la sua venue nel chatbot di trading). Ogni dato applicativo nasce etichettato così. La sezione Trading è riservata agli amministratori elencati, separati da virgola, in NEXT_PUBLIC_ADMIN_EMAIL: gli altri utenti loggati vedono solo Strategia e Assets e la voce Trading non compare in home. L'accesso in sola lettura per i clienti al proprio portafoglio in Strategia e Assets resta il passo successivo, ora che l'identità c'è.

Direzione a lungo termine

L'obiettivo è un unico chatbot da cui aprire e chiudere posizioni su qualsiasi piattaforma tra le più usate: exchange centralizzati e decentralizzati, protocolli DeFi e aggregatori come 1inch e NEAR Intents, ognuno integrato con i suoi strumenti ufficiali tenuti all'ultima versione (vedi "Manutenzione dello stack"). La difficoltà principale del piano è tenere ordine nella crescita: ogni piattaforma in più porta simboli, quote, credenziali e regole sue, e senza una forma comune la codebase degenera in casi speciali.

La regola è quindi mettere ogni cosa al posto giusto e restare minimali, classificando ogni singola chiamata verso una piattaforma, non la piattaforma nel suo insieme: la stessa piattaforma può comparire in più punti con ruoli diversi (Hyperliquid, per esempio, è sia una venue sia una fonte dati). Due domande in sequenza decidono dove va il codice. Primo: la chiamata firma qualcosa o muta uno stato reale (un conto, un wallet)? Se no, è layer dati: entra come provider in convex/providers dietro l'interfaccia di ingestione comune, alimenta le tabelle Convex (marketLatest/marketSeries per i prezzi, accountSnapshots per saldi e posizioni) e la chat legge da lì invece che in diretta, riservando il fetch live ai soli momenti pre-trade. Se sì, seconda domanda: lo stato risultante vive in un conto tenuto dalla piattaforma stessa (margine, order book, posizioni gestite da lei) o in token dentro il wallet dell'utente? Nel primo caso è layer esecuzione, comparto venue: un VenueAdapter registrato in src/lib/execution/venues, con le sue capacità dichiarate e nessuna credenziale conservata, diviso a sua volta in una faccia VenueReader (sola lettura, usata ovunque) e una faccia VenueExecutor (firma, usata solo dalla route execute e dall'anteprima dell'ordine). Nel secondo caso è layer esecuzione, comparto DeFi: un modulo in src/lib/execution/defi con client fetch-only e logica pura testabile senza rete, come src/lib/execution/defi/nearIntents (oggi solo quote in sola lettura: il primo swap davvero eseguito è ciò che darà forma al primo WalletCredentials usato per davvero). Le integrazioni DeFi devono conservare tutte le capacità tecniche delle API; l'autorizzazione cambia in base al valore, e sopra 50 USD richiede sempre una firma Ledger. Una piattaforma nuova si aggiunge implementando l'interfaccia del comparto giusto e registrandosi, senza toccare motore di rischio, flusso di conferma o chat; se un'integrazione non risponde in modo netto a queste due domande, prima si decide la forma nuova e poi si scrive il codice. Gli SDK ufficiali si adottano solo dove fanno risparmiare lavoro vero, tipicamente la firma degli ordini; le letture restano fetch diretti, bundlabili sia in Next sia in Convex. Con la crescita dei tool della chat, il dispatch oggi concentrato nella route parse andrà spezzato in moduli per tool, mantenendo la route come solo smistamento.

Strategia

Definisce l'allocazione ideale del capitale, distinguendo per tipologia di asset e per piattaforma usata per investirci. È il punto di riferimento contro cui confrontare la fotografia reale della sezione Assets (il confronto resta a occhio: le sezioni non si parlano). I contenuti sono separati per portafoglio nel backend Convex: un portafoglio senza strategia mostra uno stato vuoto. Il primo caso reale è VK, con la proposta del 18 giugno 2026: 600'000 CHF divisi in parti uguali tra comparto a medio e ad alto rischio, con componenti, piattaforme e regole di rischio mostrate in sola lettura. Gli asset concreti assegnati a una componente hanno uno stato binario: idea (rosso) o executed (verde); il prezzo d'ingresso resta un campo opzionale a parte.

L'evoluzione prevista aggiunge due forme equivalenti di modifica: editing inline sui singoli blocchi della pagina e un chatbox che applica le modifiche descritte in linguaggio naturale. Entrambi scriveranno le stesse tabelle, l'ultimo salvataggio vincerà e Convex conserverà la storia delle revisioni.

Assets

Risponde a due domande: "dove sono i soldi?" oggi e "come sono andati?" nel tempo. In cima mostra due grafici affiancati del portafoglio attivo: il capitale ripartito tra gli asset delle posizioni e la liquidità restante, e lo stesso totale ripartito per venue. Sotto resta il track record, che aggrega le operazioni realizzate archiviate da Hyperliquid, OKX e Lighter, incluse le vendite spot OKX con costo medio ponderato mobile. La tabella elenca lo storico completo del portafoglio, non un estratto. Mostra inoltre le posizioni Hyperliquid ancora aperte, il risultato netto, la percentuale di operazioni vincenti, il profit factor, il rendimento sul margine, commissioni, funding, durata media, curva cumulata e max drawdown.

Quali conti appartengono a quale portafoglio lo si decide in integrazione (env + assegnazione Convex), non dalla UI: niente click «collega». I badge Trading mostrano le venue attive su quel portafoglio. Un portafoglio senza conti non vede i conti altrui, e un errore nella lettura della mappa non ricade mai sui conti degli altri portafogli. Un job Convex aggiorna ogni ora lo storico di entrambe le venue e il pulsante aggiorna permette di rileggerlo subito. La fotografia patrimoniale completa, con liquidità fiat e stablecoin esterne alle venue collegate, e l'inserimento manuale via chat restano da realizzare.

Trading

Operatività multipiattaforma in linguaggio naturale. Il chatbot è un agente con strumenti: legge mercati, conti, storico, dati aggregati e fonti web, poi prepara ordini, protezioni su posizioni aperte, cambi leva e cancellazioni senza affidare al modello i numeri reali o i controlli di rischio.

La sottopagina /trading/markets classifica per volume nelle ultime 24 ore i mercati delle venue collegate al portafoglio. Permette di passare dalle singole coppie agli asset aggregati e mantiene spot e perpetual separati; nello spot il confronto include solo quote USD o stablecoin, così i volumi restano omogenei.

L'interfaccia permette di scegliere il modello OpenRouter e il livello di ragionamento; la ricerca web usa una chiamata dedicata e restituisce le fonti. Ogni conversazione mostra nell'header il costo OpenRouter cumulativo in dollari: il server somma tutte le chiamate del giro agentico, inclusa la ricerca web, mentre token e dettaglio dei costi per modello restano salvati ma nascosti. Ogni risposta generata mostra accanto a «chstrategy» il modello effettivo restituito da OpenRouter. Le conversazioni precedenti all'attivazione del conteggio non vengono stimate. La cronologia resta associata al portafoglio selezionato e permette di riaprire, rinominare inline ed eliminare le conversazioni salvate. L'esecuzione reale è collegata a Hyperliquid, OKX, KuCoin e Lighter. Ogni azione passa dalla validazione deterministica, produce un'anteprima e richiede un consenso finale: l'utente può rispondere in linguaggio naturale, per esempio «sì», «esatto» o «procedi», oppure usare il pulsante. Il consenso instrada un token firmato monouso, non lascia al modello la decisione di eseguire. Le connessioni operative possono leggere e fare trading. I trasferimenti di capitale vivono in un sottosistema separato, con destinazioni verificate e un gate spento per default. Il motore Python indipendente in engine/ conserva LIVE_TRADING come seconda conferma locale per la modalità --live. Le regole complete sono in SECURITY.md.

La venue non è fissata quando si apre la chat. Ogni richiesta viene instradata dal server verso la piattaforma nominata dall'utente; se manca, il server cerca il mercato nei conti operativi collegati e chiede una scelta soltanto quando lo stesso prodotto è disponibile su più venue. Lo stesso flusso copre perpetual, spot cash e spot margin cross/isolated dove supportato. OKX espone tutte queste modalità; Hyperliquid espone perpetual e spot cash. Il piano normalizzato, la venue e il conto risolti vengono firmati insieme nel token monouso, poi riletti e rivalidati prima dell'invio.

Le letture private riguardano invece un solo conto per volta. Se l'utente nomina una posizione con ticker e direzione, il server la cerca nei conti collegati e seleziona l'unico riscontro reale; chiede il conto soltanto quando la posizione compare in più conti o il contesto verificato della piattaforma e della conversazione non basta a identificarla. Una volta risolto, lo stato comprende tutti i saldi e comparti esposti, posizioni aperte, ordini standard e condizionali; ogni sezione non leggibile resta esplicitamente incompleta con il bottleneck restituito dall'API. Lo storico normalizzato copre ordini ed esecuzioni: su OKX legge l'archivio API di tre mesi per spot, margin, swap e futures, inclusi gli ordini algo; su Hyperliquid vale il limite API di 2.000 righe. Gli adapter convertono sempre le size dei derivati nell'asset sottostante. Le posizioni chiuse includono anche il margine iniziale calcolabile e restano disponibili nell'archivio permanente closedPositions di Convex.

Le domande sul capitale aggiunto mentre una posizione era aperta usano una lettura storica dedicata: il server identifica la posizione chiusa in tutti i conti collegati, applica l'intervallo esatto tra apertura e chiusura e classifica le esecuzioni avvenute dentro quella finestra. Il totale esclude il margine iniziale della posizione che definisce l'intervallo e le operazioni di chiusura; somma gli esborsi spot cash e il margine dei soli aumenti perp. Il notional resta separato. Quando manca la leva storica ma la posizione è ancora aperta, la leva attuale produce una stima dichiarata; senza nessuna leva il totale resta esplicitamente parziale.

Tech stack

Il sito: TypeScript, React, Tailwind, Convex per backend e dati, Clerk per l'autenticazione, deploy su Vercel, Bun come runtime e package manager. Il motore della sezione Trading: Python 3.11+, SDK ufficiale Hyperliquid e ccxt per leggere le posizioni sugli altri exchange.

Struttura del repository

Alla radice vive l'app web Next.js (src/app/ con homepage e le tre sezioni, src/components/ per i componenti condivisi). In engine/ vive il motore Python della sezione Trading, con i suoi src/, tests/ e plans/.

Backend dati (Convex)

Il backend dati vive in convex/. Lo schema (schema.ts) copre i portafogli per utente (portfolios), le connessioni di piattaforma per portafoglio (connections), le conversazioni della chat di trading (conversations e messages, con tracce d'audit su cosa ha prodotto ogni turno e la partizione per utente e portafoglio), una cache della directory dei dex builder (builders, chi ha deployato cosa), le tabelle di market intelligence descritte sotto, accountSnapshots e il catalogo tool/docs (toolDocs + coda toolDocsQueue).

Account snapshots e sync account

accountSnapshots tiene saldi, posizioni, storico ordini e fill per (venue, account, kind, scope), popolata ogni 5 minuti da convex/accountIngest.ts con lo stesso collettore fetch-only dei provider di mercato. La chat legge da qui invece di interrogare la venue in diretta a ogni domanda; se il dato manca o ha più di 10 minuti (src/lib/marketData/freshness.ts) ricade sul fetch live di sempre, senza mai mescolare un pezzo fresco con uno stantio nella stessa risposta. L'elenco account da fotografare non è una variabile d'ambiente Convex da tenere allineata a mano: la rotta parse lo sincronizza su trackedAccounts a ogni turno di chat (best-effort, mai bloccante), a partire dallo stesso TRADING_ACCOUNTS già letto lato Vercel, che resta l'unica fonte di verità (indirizzi e dex builder insieme). Le connessioni sono la mappa di quali account di trading appartengono a quale portafoglio: le credenziali restano solo nelle variabili d'ambiente del server, la rotta accounts filtra gli account configurati sulle connessioni del portafoglio attivo e un portafoglio senza connessioni registrate li vede tutti, così un portafoglio nuovo non nasce cieco.

Quando l'utente chiede una lezione su un trade già chiuso, la chat non cerca la posizione tra quelle aperte. Il server identifica prima l'ultima posizione archiviata con quel ticker in tutti i conti collegati al portafoglio, fissa venue e conto, legge le candele pubbliche del mercato sulla stessa venue e passa al modello un blocco obbligatorio con trade, percorso del prezzo e punto pertinente del Regolamento Interno. Una risposta generica che salta questi riscontri viene rigenerata. Il massimo profitto aperto e il tipo esatto dell'ordine di uscita restano non verificati finché non compaiono nello storico ordini o fill.

Storico operazioni realizzate (track record)

closedPositions è l'esatto contrario di accountSnapshots: accumula invece di sovrascrivere. Serve perché le venue conservano lo storico per una finestra limitata, circa tre mesi su OKX e circa 10.000 fill su Hyperliquid. La forma è neutra rispetto alla venue e contiene strumento, verso o tipo spot, prezzi medi di apertura e chiusura, PnL lordo, commissioni, funding e PnL netto. Le statistiche del portafoglio sono quindi aggregate senza uno strato di unione.

Due job orari archiviano lo storico. closedPositions:ingestOkx legge positions-history, dove OKX restituisce già i derivati aggregati, e fills-history per lo spot. Ogni vendita spot realizza il PnL contro il costo medio ponderato mobile degli acquisti precedenti. Il ledger conserva ogni fill, mentre statistiche e lista accorpano quelli dello stesso ordine OKX in una sola operazione; se l'id ordine manca usano una finestra di un'ora per strumento. Fill elaborati e inventario aperto restano in Convex, quindi la finestra di tre mesi serve solo al bootstrap e al recupero. Una vendita senza costo iniziale noto non viene valorizzata a costo zero: resta diagnosticabile come non abbinata e Assets mostra quali asset hanno ancora una base di costo incompleta. closedPositions:ingestHyperliquid ricompone invece i cicli di apertura e chiusura dai fill degli account tracciati. Poiché i fill storici non contengono la leva, il job legge la configurazione dell'asset da activeAssetData alla prima archiviazione e la congela sulla riga: per lo storico precedente al primo sync è la configurazione disponibile al momento del recupero, non una prova di eventuali variazioni avvenute mentre la posizione era aperta. Il pulsante aggiorna in Assets esegue entrambi senza attendere il giro successivo.

Il client OKX del job Convex (convex/lib/okx.ts) e quello usato da Next per le letture live (src/lib/execution/venues/okx/) sono separati perché girano in runtime diversi e leggono le proprie variabili d'ambiente. Due trappole già coperte: l'account EEA risponde solo su eea.okx.com tramite OKX_API_BASE e OKX riusa il posId tra posizioni successive sullo stesso strumento. La chiave logica è quindi (venue, account, positionId, closedAt).

closedPositions:stats calcola il track record sul PnL netto: totale, percentuale di vincenti, vincita e perdita media, profit factor, rendimento sul margine, curva cumulata giornaliera, massima discesa dal picco e suddivisioni per asset, verso e mese. Il drawdown è misurato sul PnL realizzato, non sul patrimonio: senza una serie storica del capitale è l'approssimazione onesta. La query è riservata agli amministratori; probeOkx è una diagnostica di collegamento in sola lettura.

Catalogo tool/docs (backend condiviso, UI solo admin)

Obiettivo: tenere sotto traccia le pagine ufficiali di documentazione dei tool usati e dei candidati trovati navigando sul web, per aggiornare lo stack, per valutare connettività genetica prima di integrare, perché gli agenti IDE possano chiedere "cosa non è ancora implementato che potrebbe servire?" (toolDocs:listOpportunities), e per monitorare le nuove capacità agentiche dell'AI nel tempo: i docs incollati vanno letti in profondità (browser/llms.txt/ OpenAPI/guides), non solo classificati, così emergono idee di implementazione per la sezione trading. Il catalogo alimenta le connessioni agentiche del trading avanzato: a valle serve a tutti gli account, ma nessun utente non-admin lo vede o ci accede in UI.

  • Tabelle: toolDocsQueue (coda di valutazione) e toolDocs (catalogo ammesso).
  • UI /trading/tools-docs: solo amministratori (stesso controllo di /trading/*). Dump URL, coda, classify, seed, refresh ↻ (merge su fornitore già noto), toggle implemented, status, note. Se il dump è un nuovo path dello stesso fornitore già in catalogo → stessa pipeline refresh (skill update-tool.md), non una seconda entry.
  • UI /api-status: integrazioni raggruppate per categoria Tool docs (venue_cex, venue_dex, defi, market_data, …): collegamento portafoglio, credenziali runtime, presenza dei nomi env su Vercel Production (via VERCEL_API_TOKEN) e presenza delle chiavi attese sul deployment Convex (query envPresence, senza valori), scadenza sempre dichiarata — illimitata o data in <VENUE>_API_KEY_EXPIRES_AT — e ping live delle API a ogni apertura. Per ogni venue implementata: elenco delle funzioni con nome nativo della piattaforma e switch attivo/non attivo (gate prodotto chstrategy). I trasferimenti restano spenti per default e possono essere attivati solo se implementati. Modifica switch: solo admin. Stesso sblocco password di /trading. Catalogo host attesi: SERVICE_ENV_CHECKS / expectedEnvKeysForVenue in src/lib/apiStatus.ts; regola agenti: .cursor/rules/env-hosts.mdc.
  • Ciclo di vita (status): watchcandidateintegrated; se un tool viene sostituito da uno migliore → deprecated (resta in catalogo come storico; non confondere con category prodotto tipo venue_cex / broker_api / market_data). archived = fuori scope.
  • Lettura client Convex (listCatalog, coda): requireAdmin.
  • Lettura e accodamento server / agenti (listCatalogForServer, listOpportunities, enqueueUrlsForServer + CHAT_WRITE_SECRET): usati dalle route Next e dagli agenti IDE senza esporre la pagina catalogo.
  • Flusso admin: dump URL (anche candidati web) → coda → POST /api/trading/tools-docs/classify (1 URL per richiesta) → gate fit+tier e gate categoria (3 domande sì/no; categorie built-in o slug nuovi) → catalogo. Preferenza URL: homepage docs o getting started / for developers. Se un URL in coda/catalogo è morto: web_search per lo slug docs ufficiale aggiornato, poi nuovo docsRootUrl (skill classify + rule Cursor). Ogni doc ha category (identità), integrationRoles (usi chstrategy), capabilities (funzioni API) e serviceTargets con il tipo di accesso per retail, professionisti e istituzioni. Le note amministrative diventano il brief vincolante del refresh successivo.
  • Seed iniziale: seedImplementedCatalog (admin) o seedImplementedCatalogServer (secret); lista in convex/toolDocsSeed.ts e skill src/lib/toolsDocs/skills/codebase-context.md. Regola Cursor: .cursor/rules/tool-docs-catalog.mdc.
  • Fase 2 (parziale): refresh manuale (bottone ↻ in catalogo) e auto-refresh quando il dump URL appartiene a un fornitore già classificato (skill update-tool.md, merge su targetToolDocId). Restano: refresh periodico, score DeFiLlama, storico check.

Auth sulle funzioni

Le funzioni che leggono o scrivono le conversazioni restano protette da un segreto condiviso (CHAT_WRITE_SECRET): il loro chiamante legittimo è il server Next, che aggiunge ai turni salvati l'identità Clerk verificata dal middleware, mentre un client del browser non conosce il segreto e non può toccare quei dati. Questo vale doppio qui, dato che è la stessa app che firma ordini reali. Le funzioni dei portafogli (portfolios.ts) usano invece direttamente l'identità del JWT Clerk validato da Convex: ogni utente vede solo i suoi. Il catalogo toolDocs è dato di backend gestito solo dall'admin in UI, ma leggibile dal server (secret) per alimentare tool agentici a beneficio di tutti gli account.

Lo schema crescerà con le tabelle di Strategia e Assets, che nasceranno già agganciate al portafoglio giusto.

Autenticazione (Clerk)

L'autenticazione è su tre livelli, tutti in src/proxy.ts. Il primo è il login Clerk, che protegge le tre sezioni e rende disponibile l'identità su tutta l'app. Il secondo riserva /trading e /api/trading/* agli amministratori: il middleware confronta il claim email del token di sessione Clerk con l'elenco separato da virgola in NEXT_PUBLIC_ADMIN_EMAIL (vedi src/lib/admin.ts); gli altri utenti vengono rimandati in home e non vedono la voce Trading. Il terzo è lo sblocco a cookie preesistente, che resta a guardia della sola esecuzione anche per l'amministratore, perché lì passano ordini reali. Il ponte con Convex passa dal template JWT convex creato nella dashboard Clerk: il suo issuer va nella variabile CLERK_JWT_ISSUER_DOMAIN del deployment Convex (vedi convex/auth.config.ts), mentre le chiavi pubblica e segreta stanno nelle variabili d'ambiente di Next (vedi .env.example). Il template JWT convex deve includere il claim email ({{user.primary_email_address}}), altrimenti requireAdmin lato Convex non può confrontare l'email (fallback: ADMIN_CLERK_SUBJECT). Nel layout radice ConvexProviderWithClerk fa viaggiare il JWT con ogni query reattiva del browser. Il menu utente mostra nome e foto e apre un pannello per modificarli o disconnettere la sessione.

Market intelligence

In convex/ vive anche il layer di raccolta dati di mercato: cron di ingestione (crons.ts, ingest.ts) che leggono periodicamente da Hyperliquid, Coinalyze (aggregati cross-exchange: open interest, funding, liquidazioni) e Coin Metrics (metriche on-chain), tramite i provider in convex/providers/. L'ultimo valore per fonte, asset e metrica sta in marketLatest (upsert, dimensione fissa), lo storico orario e giornaliero in marketSeries con pruning per età, le run in ingestRuns per l'osservabilità (inclusa diagnostica di degrado parziale senza marcare tutta la run come fallita). È dato pubblico di mercato, volutamente globale e non partizionato per utente o portafoglio. Lo legge il chatbot della sezione Trading con l'azione market_intel, instradata dal MarketDataRouter di src/lib/marketData (etichette e formati dei numeri unificati in format.ts), che per i dati aggregati attinge solo allo storico raccolto e dichiara sempre fonte ed età di ogni numero. Il pannello MarketIntelPanel esiste ma al momento non è montato: la pagina Trading mostra solo chat e posizioni. Gli asset tracciati e la retention si regolano con le variabili documentate in .env.example (TRACKED_ASSETS, RETENTION_1H_DAYS, COINALYZE_API_KEY).

In sospeso (luglio 2026): il watcher di spike di volume (convex/volumeWatch.ts) dipende oggi in modo rigido da Coinalyze, la stessa fonte di OI/funding/liquidazioni sopra: senza COINALYZE_API_KEY la run si salta silenziosamente. Dopo l'attivazione di una chiave nuova le chiamate hanno preso 429 in modo persistente per oltre 14 ore nonostante il traffico minimo (solo il cron ogni 5-15 minuti), ben sotto il limite dichiarato di 40 richieste/minuto: sospetto legato al burst di chiamate di verifica fatto subito dopo l'attivazione, o a una limitazione a monte non visibile dal pannello utente (che non mostra contatori d'uso). Prossimo passo non ancora eseguito: rigenerare la chiave su coinalyze.net/account/api-key/ e testarla SOLO con il cron naturale, senza nessuna chiamata manuale di verifica. Se il blocco si ripresenta identico, convex/providers/laevitas.ts è già pronto come fallback per il solo volume watcher (stesso tipo VenueCandles[], buy/sell volume confermato a 5 minuti su Binance/OKX/Bybit/Deribit/Hyperliquid contro la documentazione Laevitas), ma non è collegato a nessun cron né attivato: prima di attivarlo vanno verificati contro l'API live con una chiave reale sia l'euristica che riconosce i perpetual nel catalogo (isPerpetualInstrument, oggi validata solo sugli esempi della documentazione) sia il prefisso /api/v1 nel path di base. Non sostituirebbe comunque il job di OI/funding/liquidazioni aggregati, che resta su Coinalyze finché non si verificano gli endpoint Laevitas equivalenti.

Lo scaffolding (dipendenza, schema, funzioni) è in repository. Il collegamento a Convex avviene tramite l'integrazione Convex su Vercel: quella mette da sola su Vercel la chiave di deploy in Production, e il buildCommand in vercel.json (convex deploy --cmd 'bun run build') fa sì che ogni build di produzione pubblichi prima il backend Convex (schema e funzioni, generando convex/_generated) e poi costruisca il sito, con NEXT_PUBLIC_CONVEX_URL già disponibile. Sulle Preview, se CONVEX_DEPLOY_KEY manca, la build fa solo bun run build (niente deploy Convex da branch): lo schema live resta quello di produzione, aggiornato al merge su main. Non serve il login locale: quel comando lo si usa solo per sviluppare Convex sul proprio Mac (bunx --bun convex dev).

main è il ramo di produzione: ogni push avvia automaticamente un deploy Vercel in produzione, compreso il deploy Convex eseguito dal comando di build. Le operazioni destinate a chstrategy.xyz, inclusi catalogo tool/docs, code e dati Trading, usano sempre il deployment Convex di produzione; quello di sviluppo si usa solo su richiesta esplicita per prove locali.

Attenzione all'ordine: il primo deploy con questo buildCommand funziona solo dopo che l'integrazione Convex ha messo la sua chiave su Vercel, altrimenti la build fallisce cercandola. Il segreto CHAT_WRITE_SECRET va impostato in due posti con lo stesso valore, sulle variabili d'ambiente di Vercel e su quelle del deployment Convex. Imposta anche ADMIN_EMAIL e, come ripiego opzionale, ADMIN_CLERK_SUBJECT sul deployment Convex. Entrambe accettano più valori separati da virgola; ADMIN_EMAIL deve restare allineata con NEXT_PUBLIC_ADMIN_EMAIL. Servono a requireAdmin per coda e classify di /trading/tools-docs. La route src/app/api/trading/parse/route.ts salva ogni turno via le funzioni conversations/messages come scrittura best-effort, che non fa mai fallire la chat né il percorso di invio ordini, e allega a ogni conversazione l'utente Clerk e il portafoglio selezionato. Il conto della conversazione diventa contesto per le letture successive solo dopo che il server lo ha verificato da testo, dati o tool; il primo conto configurato non vale come scelta implicita dell'utente.

Manutenzione dello stack e canali da monitorare

L'infrastruttura del trading dipende da componenti esterni che evolvono in fretta, quindi conviene sapere dove guardare quando qualcosa cambia. La fonte di verità dei link ufficiali da tenere sotto traccia è il catalogo Convex toolDocs (pagina admin /trading/tools-docs, seed in convex/toolDocsSeed.ts): homepage o getting started di Hyperliquid, NEAR Intents, Coinalyze, Coin Metrics, OpenRouter, Finnhub, Convex, Clerk, CCXT, Laevitas (fallback), ecc. Quando si integra un tool nuovo, il link docs entra in coda, passa il classify, e se ammesso resta nel catalogo con implemented e note di fit.

L'SDK Hyperliquid TypeScript che firma e legge tutto è @nktkas/hyperliquid: le sue release su GitHub e la pagina npm dicono quando esce una versione nuova, da confrontare con quella fissata in package.json e nel lockfile. Le regole di mercato, gli endpoint e il comportamento dei dex builder vivono nelle Hyperliquid docs, in particolare le pagine su HIP-3 e sulle HIP-3 deployer actions, che è anche dove verificare periodicamente le note curate in BUILDER_NOTES (chi c'è dietro un deployer). Questa regola vale per ogni componente futuro, non solo per Hyperliquid: ogni nuova venue dietro VenueAdapter (Aster, Lighter, Uniswap su Unichain, ...) porta con sé le proprie fonti, SDK con release e pagina npm, docs ufficiali, e le aggiunge al catalogo toolDocs con la stessa logica, versione pubblicata contro versione in uso. Lo stesso se l'agent wallet passerà da un MCP dedicato: il repository di quel server entra nella lista.

Perché il controllo non dipenda dalla memoria di qualcuno, è prevista una routine schedulata settimanale (stack-watch, vedi la skill /schedule) che farebbe una ricerca web su queste fonti (a partire dal catalogo), confronterebbe le versioni pubblicate con quelle del lockfile, eseguirebbe bun audit per le vulnerabilità note e riporterebbe solo i delta, con i link diretti. È ancora una bozza in pausa, da rivedere e aggiornare insieme al resto del codice in lavorazione prima di riattivarla; quando sarà attiva, l'aggiornamento resterà comunque un intervento manuale (bun add, lettura del changelog, test), mai automatico su codice che firma ordini reali.

Contro i pacchetti npm compromessi il repository ha due difese committate: bunfig.toml impone minimumReleaseAge di 7 giorni, così un'installazione rifiuta versioni appena pubblicate (quasi tutti gli attacchi supply-chain vengono scoperti e rimossi entro pochi giorni), e trustedDependencies in package.json limita gli script postinstall ai soli pacchetti che ne hanno davvero bisogno; tutto il resto viene installato senza poter eseguire codice.

Stato attuale

Il motore Python del trading è costruito, testato e utilizzabile da terminale: anteprima dry-run, esecuzione reale con triplo controllo, lettura di margine e ordini aperti, indicatore OBV e posizioni su altri exchange via ccxt. L'app web ha la homepage, le tre dashboard e, nella sezione Trading, un agente OpenRouter che usa gli strumenti server per leggere dati e preparare azioni. Le azioni confermate nella web app possono partire realmente quando le credenziali di firma della venue sono configurate; LIVE_TRADING resta solo la seconda conferma del motore Python indipendente avviato con --live. Le piattaforme collegate sono Hyperliquid, OKX, KuCoin e Lighter, tutte in lettura ed esecuzione, dietro l'interfaccia VenueAdapter in src/lib/execution/venues: ogni piattaforma espone risoluzione asset, dati di mercato, stato del conto ed esecuzione dietro la stessa forma (divisa in VenueReader e VenueExecutor, credenziali come unione discriminata per venue), e l'account porta con sé la sua venue nell'elenco degli account attivi, nel token di autorizzazione e nelle conversazioni salvate. L'intenzione è aggiungere Aster e Uniswap su Unichain come nuovi adapter, riusando immutati il motore di rischio e il flusso anteprima più consenso.

Aggiungere una venue. L'id del conto non ha più forma imposta: è una stringa e a giudicarla è l'adapter (isValidAccountId), quindi convivono un indirizzo 0x di Hyperliquid e un'etichetta di conto come main su OKX/KuCoin. I conti si dichiarano una venue per volta, <VENUE>_ACCOUNTS (HYPERLIQUID_ACCOUNTS, OKX_ACCOUNTS, KUCOIN_ACCOUNTS, LIGHTER_ACCOUNTS), così collegare una piattaforma non obbliga mai a riscrivere la configurazione di un'altra; TRADING_ACCOUNTS, lista unica di voci venue:label:id, resta come scorciatoia e ha la precedenza quando c'è. KuCoin usa api.kucoin.com (conto global); le function Vercel girano su fra1 per evitare il blocco geografico 400302. Override host con KUCOIN_API_BASE / KUCOIN_FUTURES_API_BASE solo se il conto è sul sito EU. Le piattaforme che si autenticano con chiave API partono da src/lib/execution/venues/keyedRest.ts, che risolve le credenziali dalle env per convenzione (<VENUE>_API_KEY, _API_SECRET, _API_PASSPHRASE, _API_BASE), firma in HMAC-SHA256, costruisce la query e normalizza gli errori: è la famiglia più numerosa del catalogo tools docs (QFEX, Variational, Propr, Robinhood, Alpaca). Lighter usa invece le API ufficiali per le letture e un servizio Python isolato basato sull'SDK ufficiale per firmare ordini. Chi firma con un wallet (Blink, Nado, RISEx, tradeXYZ) è un'altra famiglia e avrà una base propria.

Gli spostamenti di capitale sono due caratteri distinti, non due facce dello stesso adapter. Intravenue (stesso conto, sezioni interne tipo Funding ↔ Trading): API nativa tipo asset/transfer, capability in src/lib/execution/transfers/internal.ts (canInternalTransfer). Oggi solo OKX. La chat usa propose_internal_transfer e non deve fare if (venue === "okx"). Intervenue (esce dalla venue): API nativa tipo withdrawal, rotte con indirizzi già verificati, cooldown, limiti e Ledger sopra 50 USD. Registry in src/lib/execution/transfers/registry.ts, chat propose_capital_transfer.

Lighter espone un conto unified: total_asset_value è l'equity corrente già comprensiva del PnL non realizzato, available_balance è la liquidità operativa e cross_initial_margin_requirement è l'IMR cross. L'adapter usa questi campi una sola volta e aggiunge allocated_margin soltanto per le posizioni isolate; collateral non va sommato di nuovo a total_asset_value.

Tre differenze di modello che l'adapter deve assorbire, e che OKX mostra tutte. Su un CEX anche leggere il conto è autenticato (l'adapter risolve da solo le chiavi di lettura dalle env, le credenziali di firma restano fuori dal tipo VenueReader). Le size dei perpetual si contano in contratti, non nell'asset, quindi la conversione va fatta dentro la cartella della venue perché fuori i numeri restino confrontabili tra piattaforme; nel senso opposto, un piano approvato dal motore di rischio si converte in contratti troncando sempre per difetto, perché arrotondare per eccesso manderebbe a mercato una size diversa da quella confermata. E gli id degli ordini hanno forma propria per venue (numeri su Hyperliquid, stringhe di 18 cifre su OKX, che un numero JavaScript non rappresenta esattamente): a normalizzarli è parseOrderId dell'adapter, come isValidAccountId fa per gli id dei conti.

Chi può firmare. Il conto operativo è uno per piattaforma (operativeAccountFor), non uno solo in assoluto: le credenziali di una venue non autorizzano nulla sull'altra. Su Hyperliquid resta HYPERLIQUID_ACCOUNT_ADDRESS; sulle altre è il conto configurato, ma solo se ce n'è esattamente uno, perché con due conti dietro le stesse chiavi non si potrebbe stabilire dove atterrerebbe l'ordine. Tutti gli altri account restano di sola lettura, e la route execute verifica che la venue del token coincida con quella del conto operativo che porta quell'id.

Le connessioni operative usano chiavi con permessi di lettura e trading. Per OKX la stessa chiave può ricevere anche i permessi Transfer e Withdraw e servire il sottosistema trasferimenti; una chiave separata Read + Withdraw resta supportata come override più isolato. In entrambi i casi i prelievi restano confinati alle rotte verificate.

Trasferimenti di capitale

La pagina /trading/transfers e il chatbot possono spostare esplicitamente USDC fra OKX Funding e OKX Trading, in entrambe le direzioni. L'azione usa POST /api/v5/asset/transfer con tipo main-account, controlla il saldo della sezione di origine, richiede una conferma monouso e legge poi lo stato nativo del trasferimento. Non viene mai concatenata automaticamente a un ordine o a un prelievo. Il gate asset_transfer resta spento per default. Sopra 50 USD serve una firma Ledger e quindi almeno un wallet verificato nel portafoglio.

La pagina /trading/transfers gestisce la prima rotta: OKX Funding → Rabby/Ledger verificato oppure OKX Funding → Lighter, in USDC su Arbitrum. Non esiste un campo indirizzo libero. Ogni destinazione nasce da una firma del wallet, resta in cooldown per 24 ore e viene richiamata solo tramite id interno. Lighter deriva l'indirizzo di deposito dall'API ufficiale e lo lega allo stesso wallet L1.

La rotta account-to-account OKX Funding → Lighter e' disponibile anche dal chatbot: l'utente indica origine, destinazione e importo, poi autorizza la proposta nel normale blocco di conferma. Entrambi gli estremi vengono risolti contro i conti collegati al portafoglio e la chat non accetta indirizzi o identificativi esterni. Le altre coppie di venue restano bloccate finche' non esiste un provider e una rotta verificata specifici.

Ogni anteprima include rete, fee e indirizzo finale, viene firmata in un token monouso e viene riletta prima dell'invio. Fino a 50 USD basta la conferma API; sopra 50 USD serve anche una firma Ledger sul piano esatto. I limiti predefiniti sono 50 USD per trasferimento, 100 USD al giorno e 10 minuti tra invii ripetuti. Il gate nativo OKX asset_withdrawal resta spento per default. Il dry-run non invia fondi. Le direzioni inverse e le rotte Hyperliquid restano in roadmap.

Configurazione Next/Vercel:

OKX_API_KEY
OKX_API_SECRET
OKX_API_PASSPHRASE
LIGHTER_ACCOUNTS
LIGHTER_L1_ADDRESS
LIGHTER_API_PRIVATE_KEY
LIGHTER_ACCOUNT_INDEX
LIGHTER_API_KEY_INDEX
LIGHTER_SIGNER_SECRET

La chiave operativa OKX deve avere Read, Trade e Transfer per gli spostamenti Funding ↔ Trading, e anche Withdraw quando viene riusata per i prelievi esterni. In alternativa OKX_WITHDRAWAL_API_KEY, OKX_WITHDRAWAL_API_SECRET e OKX_WITHDRAWAL_API_PASSPHRASE definiscono un override separato Read + Withdraw, preferito automaticamente quando presente. La chiave attiva va limitata agli IP del runtime quando OKX lo permette. Il signer Lighter gira come Python Function nello stesso progetto Vercel e usa LIGHTER_API_KEY_INDEX tra 4 e 254. LIGHTER_SIGNER_URL è un override locale opzionale: in produzione l'endpoint deriva automaticamente da VERCEL_URL. Il signer non usa né chiede la chiave del wallet L1 o del Ledger. Avvio locale:

cd engine
source .venv/bin/activate
python -m src.lighter_signer_service

La route /api/lighter_signer richiede il bearer secret e accetta soltanto token di lettura, leva, creazione, modifica e cancellazione ordini. L'API key Lighter può tecnicamente firmare anche prelievi, ma la route non espone né inoltra depositi, prelievi o trasferimenti. Le quattro variabili privilegiate restano solo su Vercel e non vanno duplicate su Convex.

Il consenso in chat accetta risposte naturali brevi soltanto quando esiste una proposta attiva; negazioni e frasi che chiedono modifiche tornano invece al chatbot. Il token firmato resta monouso e l'esito viene salvato nello stesso thread Convex. Gli id ordine viaggiano nel tool come testo, così le 18 o 19 cifre di OKX non vengono arrotondate da JavaScript. Gli helper OpenRouter (src/lib/agent/) sono condivisi tra chat trading e classificatore tools-docs. I 400 generici di un provider OpenRouter ricevono un retry limitato e i log estraggono la causa reale da metadata.raw; anche l'eventuale risposta di errore viene salvata nel thread, così la cronologia non nasconde i turni falliti. Le correzioni interne di una risposta restano istruzioni di sistema anche sui modelli Anthropic: non vengono aggiunte alla conversazione come falsi messaggi dell'utente.

Per una nuova entrata perpetual, stop loss e take profit sono opzionali. Entrano nel piano firmato e vengono inviati alla venue soltanto se l'utente li ha chiesti o ha accettato esplicitamente quei livelli. L'anteprima rende visibile quando lo stop non è specificato e non calcola una perdita allo stop o un rapporto R:R con dati assenti. La regola 2 del Regolamento Interno resta un riferimento da citare, non un ordine di protezione implicito.

Stop loss e take profit su una posizione già aperta hanno un flusso dedicato: il server rilegge posizione, size, mark e ordini condizionali, blocca duplicati e livelli già oltre il mark, poi firma soltanto uscite reduce-only. Questa azione non crea una nuova entrata e non cambia la leva.

Quando una richiesta ridimensiona o riprezza più ordini limite già aperti, la chat prepara un unico piano coordinato fino a 20 righe e chiede un solo consenso. Il server ricava dal messaggio l'eventuale obiettivo monetario, rifiuta se il modello lo omette o lo cambia, rilegge l'intero gruppo prima della prima modifica e impedisce aumenti sia della quantità sia del notional. Size e prezzo vengono allineati ai passi reali della venue prima dell'anteprima e del token. OKX e Hyperliquid usano la modifica nativa dell'ordine, compresi gli ordini spot OKX, senza passare dalla coppia fragile cancellazione più reinserimento. Se una venue rifiuta una singola riga, l'esito distingue quelle riuscite da quelle fallite, calcola la liquidità stimata sulle sole righe riuscite e riporta a parte la variazione osservata del capitale disponibile dopo l'operazione.

L'identità del chatbot vive in src/lib/tradingAgent/chatbot-soul.md, le regole di scrittura in src/lib/tradingAgent/style.md e il Regolamento Interno completo in src/lib/tradingAgent/regolamento-interno.md. Il prompt contiene sempre identità, stile e indice delle dieci regole; lo strumento read_regolamento carica solo la regola pertinente quando serve. soul.md alla radice descrive invece il progetto e non entra nel prompt. Sui modelli Anthropic il prefisso stabile usa il prompt caching, cioè il riuso della parte invariata, per ridurre il costo dei turni con più strumenti.

Il login Clerk è attivo: le tre sezioni richiedono l'accesso, la sezione Trading è riservata agli amministratori, ogni utente ha i suoi portafogli su Convex e li sceglie dal selettore all'ingresso delle sezioni, e le conversazioni della chat nascono partizionate per utente e portafoglio. I conti di trading del portafoglio attivo restano tutti visibili insieme, senza selettore. Il pannello può aggregarli, mentre ogni lettura privata del chatbot risolve un solo conto dichiarato dall'utente e chiede una scelta quando il contesto è ambiguo. L'esecuzione reale resta confinata all'account operativo configurato sul server.

Il pannello unisce tutte le venue in due tabelle, una per le posizioni e una per gli ordini aperti. In cima mostra liquidità disponibile, valore complessivo e margine impiegato; un clic sulla cifra apre per tutti e tre la ripartizione per venue e comparto. Ogni riga riporta venue, tipo di strumento e prezzo mark. Nelle posizioni, la direzione include la leva e una colonna separata mostra il margine impiegato in USD. Gli ordini mostrano anche il proprio controvalore in USD. Entrambe le tabelle partono dal controvalore maggiore e permettono di scegliere altri ordinamenti pertinenti. Gli ordini condizionati sono descritti come take profit, stop loss o entrata e mostrano la distanza dal prezzo corrente. La stessa classificazione nativa viene passata al chatbot; quando la venue espone solo un trigger generico, la chat lo descrive come uscita condizionale senza indovinare se sia take profit o stop loss. La piccola X in fondo a destra di ogni riga permette di annullare l'ordine dopo un controllo esplicito dell'utente; il server verifica di nuovo portafoglio, account operativo e ordine aperto prima di firmare la cancellazione. Hyperliquid si aggiorna anche via websocket, mentre le altre venue usano la lettura REST e il pulsante comune.

Ogni adapter dichiara gli scope del conto con id ed etichetta e restituisce uno snapshot normalizzato con valore, liquidità, margine e sezioni patrimoniali. La costruzione dei tre indicatori è comune: una nuova venue registrata entra automaticamente nei totali e nei popup senza condizioni specifiche nella route. I comparti che richiedono trasferimento o riscatto contano nel valore, ma non nella liquidità immediatamente utilizzabile.

Sopra il chatbot i badge delle trading platform, oggi DEX e CEX e in futuro anche broker, sono raggruppati per stato: verde se account e firma sono pronti, giallo per sola lettura o configurazione parziale, rosso se il collegamento è ancora previsto. La rotta legge dal catalogo Tool docs tutte le CEX, DEX, integrazioni DeFi e gli aggregatori con un ruolo di esecuzione; le voci PLANNED_VENUES restano la roadmap locale e il ripiego se il catalogo non è disponibile. Il link Tools docs sta nell'header della sezione. Le conversazioni passate vivono in una barra laterale a sinistra della chat, come nelle interfacce LLM correnti: un clic riapre la conversazione e la si prosegue da lì, mentre il cestino elimina la conversazione e i suoi messaggi. Un comando nell'intestazione espande la chat a tutto schermo e permette di tornare alla vista normale, anche con Esc. I tabulati tecnici prodotti dagli strumenti non vengono mostrati: restano nel contesto della conversazione per permettere al chatbot di sintetizzarli e rispondere alle domande successive. I cron di raccolta dati di mercato e account snapshots girano; il chatbot risponde sui dati aggregati e on-chain storicizzati con market_intel e sulle letture di conto da Convex con paracadute live. La pagina Trading mostra la chat, i suggerimenti rapidi e il pannello di posizioni e ordini.

Per avviare l'app web in locale:

bun install
cp .env.example .env.local   # compila OPENROUTER_API_KEY, ENGINE_DIR, ENGINE_PYTHON, le chiavi Clerk e NEXT_PUBLIC_ADMIN_EMAIL
bun dev

Per provare il motore di trading:

cd engine
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python -m src.main --plan plans/spcx_short.example.yaml --offline
pytest