Neural ODEs: Does Continuous Time Beat a Delta-Time Feature?
Le equazioni differenziali ordinarie neurali (Chen et al., NeurIPS 2018) parametrizzano la derivata di uno stato nascosto con una rete neurale e consentono a un risolutore ODE di calcolare l'output. Il modello diventa a profondità continua e, più utilmente per i mercati, a tempo continuo: lo stato nascosto può essere valutato in qualsiasi momento , non solo nei punti della griglia su cui si trovano i tuoi dati.
Questa proprietà ha una tonalità ovvia per i dati finanziari, che arrivano a intervalli casuali. Ma la proposta nasconde un’affermazione molto più debole di quanto sembri fare. Un semplice GRU forniva una funzione di input extra: dall'osservazione precedente — "sa" anche quanto tempo è passato. La vera domanda non è se i modelli a tempo continuo possano assorbire dati irregolari. Dipende se la dinamica continua appresa tra le osservazioni estrae qualcosa che una caratteristica delta-tempo non ti dà già, a un costo da dieci a cento volte superiore al costo di inferenza.
Questo articolo imposta l'esperimento, fornisce l'intera catena di strumenti per eseguirlo e riporta ciò che il confronto dovrebbe mostrare per giustificare il macchinario. L'aspetto dell'argomento relativo alla costruzione delle barre, cioè che il timing stesso del trading trasmette un segnale, è stabilito con prove misurate su 17 tipi di barre in beyond time bars; qui l'unica conseguenza che conta è quella di modellazione: il risolutore valuta lo stato nascosto come arbitrario , quindi non è necessaria alcuna interpolazione o riempimento.
L'affermazione in fase di test

Tre ipotesi annidate, ciascuna strettamente più forte della precedente:
- H1 — Un fit ODE-RNN su un flusso commerciale BTC irregolare e grezzo batte un fit GRU sullo stesso flusso senza alcuna informazione temporale. Questo è quasi certo e quasi inutile: l’ODE-RNN contiene semplicemente informazioni che mancano alla linea di base.
- H2 — L'ODE-RNN batte lo stesso GRU una volta assegnato il GRU come funzionalità di input. Questo è il test onesto. È l’affermazione che viene solitamente interpretata nella letteratura sull’ODE neurale, ed è quella quasi mai dimostrata sui dati finanziari.
- H3 — Il vantaggio H2 sopravvive al budget di latenza, ovvero vale quando l'ODE-RNN viene distribuito con un risolutore a passaggio fisso sufficientemente veloce per l'inferenza live anziché adattiva
dopri5.
Protocollo dell'esperimento
Stesso flusso di tick, stesso target, stesso budget di ottimizzazione per entrambe le leve. Nessun ricampionamento a 1 milione di barre: il ricampionamento distrugge l'esatta struttura sotto test.
| Impostazione | |
|---|---|
| Dati | Stampe commerciali BTC, tempi di inter-arrivo variabili, non ricampionati |
| Armi | ODE-RNN; GRU+ ; GRU senza funzione temporale |
| Obiettivo | Lo stesso per tutte le armi; specificato con l'adattamento |
| Metriche | RMSE, probabilità di log mantenuta, orologio da parete per epoca, latenza di inferenza per passaggio |
| Spazzata del risolutore | dopri5 (rtol 1e-5) vs Eulero a passo fisso, confrontato sulla precisione dell'allenamento |
| Diviso | Walk forward, solo fuori campione |
| Braccio | RMSE | Log simile | s/epoca | Latenza/passo di inferenza |
|---|---|---|---|---|
| GRU (nessuna funzione temporale) | — | — | — | — |
| GRU + log(dt) | — | — | — | — |
| ODE-RNN (dopri5) | — | — | — | — |
| ODE-RNN (Eulero a passo fisso) | — | — | — | — |
Un risultato negativo su H2 è completamente pubblicabile e probabilmente il risultato più prezioso: lo stesso standard applicato in il risultato negativo onesto e Sharpe sgonfio sotto test multipli.
Esperimento secondario: CNF vs Student-t
Se il confronto ODE-RNN è troppo costoso, l’ancora empirica più economica è quella distributiva: adattare un flusso di normalizzazione continuo sui rendimenti giornalieri reali di BTC e confrontare la sua densità adattata con un adattamento Student-t e con la coda empirica, riportando errori del quantile di coda ai livelli dell’1% e del 5%. Ciò si collega direttamente al lavoro distributivo già misurato in Previsione della volatilità di GARCH e GARCH asimmetrico.
Il resto di questo articolo costituisce lo sfondo e la toolchain necessaria per eseguire quanto sopra.
Background: dalle ResNet alle dinamiche continue

Viene calcolata una rete residua . Riduci la dimensione del passo e aumenta il numero di livelli e ti avvicini a un limite continuo:
Invece di strati discreti con parametri separati, un'unica rete specifica il tasso di variazione istantaneo. L'output al momento risolve un problema sui valori iniziali:
Un solutore a scatola nera (Eulero, Runge-Kutta, Dormand-Prince) calcola l'integrale numericamente, scegliendo in modo adattivo le dimensioni del passo dalle stime dell'errore locale.
Il metodo aggiunto
La propagazione all'indietro attraverso ogni passaggio del risolutore memorizza tutti gli stati intermedi, con un consumo di memoria proporzionale al conteggio dei passaggi. Chen et al. risolvi invece un'ODE all'indietro con memoria. Definire lo stato aggiunto , che soddisfa
e accumulare il gradiente del parametro lungo il passaggio all'indietro:
Il passaggio all'indietro risolve un sistema informatico aumentato , E contemporaneamente, eseguendo il risolutore al contrario da A .
Il compromesso: ricostruire all'indietro si accumula l'errore numerico, particolarmente grave nel caso di dinamiche rigide o caotiche. Il checkpoint è la via di mezzo: il negozio in alcuni momenti intermedi, ricalcolare tra di loro. I processi di prezzo sono semimartingale continui e moderatamente fluidi, quindi l'aggiunto generalmente regge; la rigidità attorno agli eventi della microstruttura può forzare risolutori adattivi o ibridi.
ODE-RNN: Stato nascosto continuo tra le osservazioni

L'ODE-RNN è l'architettura su cui si basa l'esperimento principale. Tra le osservazioni lo stato nascosto evolve sotto l'ODE:
Quando l'osservazione arriva a , viene attivato un aggiornamento discreto:
Gli apici denotano lo stato appena prima e subito dopo l'osservazione. Tra le osservazioni, apprese dinamiche continue; alle osservazioni, nuove informazioni.
Il meccanismo analizzato dall’ablazione con H2 è questo: un lungo divario significa che lo stato nascosto si è evoluto molto tempo fa – decadendo verso una linea di base, o divergente – e la forma di tale evoluzione viene appresa anziché fornita come scalare. Se tale espressività si ripaga sulla base dei dati commerciali reali è esattamente ciò che non viene misurato.
Adattamenti naturali oltre i tick: portafogli multi-asset in cui ogni asset ha il proprio programma di osservazione e lo stato latente degli asset correlati continua ad evolversi mentre ne viene osservato solo uno; e segnali guidati dagli eventi (notizie, utili, comunicati macro) che arrivano in orari irregolari.
SDE neurali: aggiunta di componenti stocastici

Gli ODE neurali sono deterministici e non possono rappresentare il rumore in un percorso di prezzo. Il trattamento classico (movimento browniano geometrico, deriva neutrale rispetto al rischio e modi in cui le ipotesi di volatilità costante falliscono contro il sorriso) è trattato in Prezzi delle opzioni di Black-Scholes. Gli SDE neurali sostituiscono la forma parametrica con un termine di diffusione appreso:
è la deriva, la diffusione, un processo di Wiener, entrambi E reti neurali.
Architettura: rete derivante e rete di diffusione
- Rete derivante : traiettoria attesa – trend, inversione della media, momentum. Addestrato per ridurre al minimo l'errore di previsione.
- Rete di diffusione : magnitudo del rumore, appresa in funzione dello stato. Alto nei regimi volatili, basso in quelli calmi.
La divisione rispecchia la finanza quantistica classica: la deriva è la dinamica neutrale al rischio (o misura P), la diffusione è la superficie della volatilità.
Addestramento degli SDE neurali
L'integrale stocastico non è classicamente differenziabile. Tre approcci:
- Gradienti percorso: trucco di riparametrizzazione: campiona percorsi browniani, differenziali tramite il risolutore trattando il rumore come input fisso.
- Corrispondenza del punteggio: stima e addestrarsi tramite obiettivi di denoising: lo stesso meccanismo utilizzato come modello generativo autonomo in modelli di diffusione per la previsione delle criptovalute, qui retrocesso a opzione di formazione per l'SDE.
- Corrispondenza della distribuzione a dimensione finita: abbina i marginali ai tempi di osservazione piuttosto che alle misure del percorso completo.
Pathwise è l'impostazione predefinita pratica. torchsde implementa Eulero-Maruyama, Milstein e Runge-Kutta stocastico con supporto autodiff.
Imparare la superficie di volatilità
I modelli classici (Heston, SABR) impongono forme parametriche sul coefficiente di diffusione. Un SDE neurale apprende come funzione arbitraria:
Questo è un approssimatore universale per i processi di diffusione: qualsiasi processo Ito con precisione arbitraria data una capacità sufficiente.
ODE latenti per dati mancanti e asincroni

L’ODE Latente (Rubanova, Chen, Duvenaud, 2019) accoppia un’ODE neurale con una VAE: le serie finanziarie sono osservazioni rumorose di un processo sottostante fluido, appreso in uno spazio latente a bassa dimensione.
- Rete di riconoscimento: un'ODE-RNN che corre a ritroso attraverso le osservazioni per produrre .
- Dinamiche latenti: .
- Decodificatore: , valutabili in ogni momento richiesto.
La perdita è l'ELBO standard:
Stima dello stato del portafoglio: da sparse osservazioni asincrone tra le risorse, dedurre uno stato latente continuo che cattura le dinamiche congiunte - essenzialmente una versione non lineare e appresa del filtro di Kalman.
Imputazione dei dati mancanti: le soste e gli intervalli del fine settimana ottengono una traiettoria latente dolcemente interpolata; il decodificatore produce percorsi plausibili attraverso lo spazio vuoto con incertezza dal VAE posteriore.
Fusione multifrequenza: chiusure giornaliere, VWAP intraday e dati tick in un unico modello senza griglia temporale condivisa.
Flussi normalizzanti continui per le distribuzioni dei rendimenti

I CNF utilizzano un ODE neurale per trasformare una semplice distribuzione di base in un bersaglio complesso. La trasformazione è e la densità logaritmica segue il cambiamento istantaneo delle variabili:
Integra lo stato e la densità di log in avanti da ottenere E . La traccia Jacobiana è stimata con lo stimatore stocastico di Hutchinson per la scalabilità.
I rendimenti delle criptovalute sono leptokurtici e distorti negativamente - misurati su dati reali in previsione della volatilità di GARCH e GARCH asimmetrico e effetto leva - e un CNF apprende quella forma invece di assumerla. Condizionare il flusso sulle variabili di stato fa sì che la forma cambi con il regime. Poiché la densità è esatta anziché approssimativa, può alimentare le metriche di coda calcolate in backtest Monte Carlo e bootstrap e la costruzione CVaR in la pipeline del portafoglio HRP/CVaR; la struttura della coda congiunta è gestita separatamente in modelli copula per rischio congiunto.
Stima condizionale della densità
L'inquadramento utile è un contrasto diretto tra tre approcci pubblicati allo stesso problema. TFT fornisce un insieme fisso di quantili da uno strato di output quantile. Previsione conforme ti offre intervalli calibrati con una garanzia di copertura. Un CNF condizionale fornisce una densità esatta e differenziabile:
La differenziabilità è la proprietà distintiva: la densità può risiedere all'interno di un obiettivo a valle ed essere propagata all'indietro, cosa che né i quantili fissi né gli intervalli conformi supportano. Se la densità esatta valga il suo costo rispetto ai quantili TFT sullo stesso target non è stato testato qui.
Confronto con alternative discrete

| Immobile | LSTM/GRU | Trasformatore | ODE neurale | SDE neurale |
|---|---|---|---|---|
| Gestione irregolare del tempo | Scarso (necessita di imbottitura) | Codifica posizionale | Nativo | Nativo |
| Memoria (formazione) | aggiunto | aggiunto | ||
| Quantificazione dell'incertezza | No (deterministico) | No (deterministico) | Via ensemble | Nativo |
| Interpolazione tra osservazioni | No | No | Sì | Sì |
| Densità di tempo continuo | No | No | Via CNF | Tramite misura del percorso |
| Costo computazionale | Basso | Moderato | Variabile (risolutore) | Alto (solutore SDE) |
La metà di questa tabella relativa al rapporto LSTM rispetto all'attenzione è discussa in dettaglio, con benchmark, nell'articolo TFT — vedere le sezioni "TFT vs LSTM vs Vanilla Transformer" e "Quando LSTM vince ancora". Le colonne nuove qui sono le due a destra, e le righe che decidono le domande H2/H3 sono le ultime due.
Implementazione Python con torchdiffeq

torchdiffeq fornisce solutori ODE con propagazione all'indietro aggiunta.
ODE neurale di base per la dinamica dei prezzi
import torch
import torch.nn as nn
from torchdiffeq import odeint_adjoint as odeint
class PriceDynamics(nn.Module):
"""Neural network defining dh/dt = f(h, t)."""
def __init__(self, hidden_dim: int = 64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(hidden_dim, 128),
nn.Tanh(),
nn.Linear(128, 128),
nn.Tanh(),
nn.Linear(128, hidden_dim),
)
def forward(self, t, h):
return self.net(h)
class NeuralODEPredictor(nn.Module):
"""
Encode observed features -> latent state,
evolve via Neural ODE,
decode to price prediction.
"""
def __init__(self, input_dim: int, hidden_dim: int = 64):
super().__init__()
self.encoder = nn.Linear(input_dim, hidden_dim)
self.dynamics = PriceDynamics(hidden_dim)
self.decoder = nn.Linear(hidden_dim, 1)
def forward(self, x0, eval_times):
"""
x0: (batch, input_dim) features at t=0
eval_times: (T,) times at which to evaluate the ODE
Returns: (T, batch, 1) predictions
"""
h0 = self.encoder(x0) # (batch, hidden_dim)
h_traj = odeint(self.dynamics, h0, eval_times,
method='dopri5', rtol=1e-5, atol=1e-7)
return self.decoder(h_traj)
ODE-RNN per dati di tick irregolari
Questo è il braccio sperimentale. La linea di base che deve battere è nn.GRUCell sullo stesso flusso con log(t_next - t_prev) concatenato a x.
class ODERNNCell(nn.Module):
"""Single step: ODE-evolve, then RNN-update."""
def __init__(self, input_dim: int, hidden_dim: int = 64):
super().__init__()
self.dynamics = PriceDynamics(hidden_dim)
self.gru_cell = nn.GRUCell(input_dim, hidden_dim)
def forward(self, h, x, t_prev, t_next):
times = torch.tensor([t_prev, t_next], dtype=torch.float32)
h_evolved = odeint(self.dynamics, h, times,
method='dopri5')[-1] # state at t_next
h_updated = self.gru_cell(x, h_evolved)
return h_updated
class ODERNN(nn.Module):
"""Process irregularly-sampled sequence."""
def __init__(self, input_dim: int, hidden_dim: int = 64):
super().__init__()
self.cell = ODERNNCell(input_dim, hidden_dim)
self.decoder = nn.Linear(hidden_dim, 1)
self.hidden_dim = hidden_dim
def forward(self, observations, times):
"""
observations: list of (batch, input_dim) tensors
times: list of floats, observation timestamps
"""
batch_size = observations[0].shape[0]
h = torch.zeros(batch_size, self.hidden_dim)
outputs = []
for i in range(len(observations)):
t_prev = 0.0 if i == 0 else times[i - 1]
h = self.cell(h, observations[i], t_prev, times[i])
outputs.append(self.decoder(h))
return torch.stack(outputs) # (seq_len, batch, 1)
Ciclo di allenamento
def train_neural_ode(model, train_loader, epochs=100, lr=1e-3):
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer, T_max=epochs
)
for epoch in range(epochs):
epoch_loss = 0.0
for batch in train_loader:
features, times, targets = batch
optimizer.zero_grad()
predictions = model(features, times)
loss = torch.nn.functional.mse_loss(predictions, targets)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
epoch_loss += loss.item()
scheduler.step()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f"Epoch {epoch+1}/{epochs}, Loss: {avg_loss:.6f}")
Flusso di normalizzazione continuo per le distribuzioni dei rendimenti
from torchdiffeq import odeint
class CNFDynamics(nn.Module):
"""Dynamics for continuous normalizing flow."""
def __init__(self, dim: int = 1, hidden_dim: int = 64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(dim + 1, hidden_dim), # +1 for time
nn.Tanh(),
nn.Linear(hidden_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, dim),
)
self.dim = dim
def forward(self, t, state):
z = state[..., :self.dim]
t_expand = t.expand(z.shape[0], 1)
zt = torch.cat([z, t_expand], dim=-1)
dz = self.net(zt)
e = torch.randn_like(z)
e_dz = torch.autograd.grad(
dz, z, e, create_graph=True
)[0]
trace_jac = (e_dz * e).sum(dim=-1, keepdim=True)
return torch.cat([dz, -trace_jac], dim=-1)
class ReturnDistributionCNF(nn.Module):
"""Model return distributions with continuous normalizing flows."""
def __init__(self, dim: int = 1):
super().__init__()
self.dynamics = CNFDynamics(dim)
self.dim = dim
def log_prob(self, x):
"""Compute log probability of observed returns."""
log_p0 = torch.zeros(x.shape[0], 1)
state0 = torch.cat([x, log_p0], dim=-1)
state0.requires_grad_(True)
times = torch.tensor([1.0, 0.0]) # backward
state_T = odeint(self.dynamics, state0, times,
method='dopri5')[-1]
z_T = state_T[..., :self.dim]
delta_log_p = state_T[..., self.dim:]
log_p_base = -0.5 * (z_T ** 2 + torch.log(
torch.tensor(2 * torch.pi)
)).sum(dim=-1, keepdim=True)
return log_p_base + delta_log_p
def sample(self, n_samples: int):
"""Generate samples from learned distribution."""
z0 = torch.randn(n_samples, self.dim)
log_p0 = torch.zeros(n_samples, 1)
state0 = torch.cat([z0, log_p0], dim=-1)
times = torch.tensor([0.0, 1.0]) # forward
state_T = odeint(self.dynamics, state0, times,
method='dopri5')[-1]
return state_T[..., :self.dim]
Note pratiche

Queste sono le cose che ti morderanno durante l'esecuzione dell'esperimento sopra.
Scelta e velocità del risolutore
dopri5 fornisce garanzie di accuratezza ma costi di calcolo variabili, motivo per cui H3 è un'ipotesi separata da H2: un vantaggio che esiste solo con un risolutore adattivo potrebbe non sopravvivere a un budget di latenza attivo. I solutori a passo fisso (Eulero, RK4) forniscono una latenza prevedibile a scapito della precisione. Compromesso pratico: allenarsi con dopri5, eseguire l'implementazione con un solutore a passo fisso calibrato per corrispondere all'output del solutore adattivo su dati rappresentativi e misurare il divario anziché dare per scontato che sia piccolo.
odeint(f, h0, t, method='dopri5', rtol=1e-6, atol=1e-8)
odeint(f, h0, t, method='euler', options={'step_size': 0.1})
Per problemi rigidi in prossimità di salti discontinui, method='implicit_adams' O method='scipy_solver'.
Stabilità numerica
Se emette valori elevati, lo stato diverge. Mitigazioni:
- Normalizzazione spettrale su strati di per controllare la costante di Lipschitz.
- Ritaglio gradiente durante l'allenamento (mostrato nel ciclo di allenamento sopra).
- Normalizzazione temporale: ridimensiona i timestamp su .
- Regolamentazione sulla norma dinamica: add alla perdita.
Intervallo di integrazione
Per i dati che coprono mesi, non integrare da A minuti:
t_normalized = (timestamps - timestamps[0]) / (timestamps[-1] - timestamps[0])
Ciò mantiene il risolutore in un regime numericamente amichevole e rende le dinamiche apprese invarianti di scala. È importante anche per il confronto: un ODE-RNN non normalizzato può perdere rispetto alla linea di base del GRU per ragioni puramente numeriche, il che sarebbe un artefatto di misurazione piuttosto che un risultato.
Gestione di scale temporali multiple
I mercati hanno dinamiche su scala di microsecondi, secondi, minuti e giornalieri contemporaneamente, e un singolo ODE neurale potrebbe avere difficoltà a mantenerli tutti. Opzioni: impila più blocchi ODE su scale temporali diverse; espandere la dimensione nascosta sia per la capacità veloce che per quella lenta; oppure eseguire ODE neurali separati per banda di frequenza e uscite con fusibile. (Questa è una domanda sulla capacità del modello, distinta dalla domanda sulla fedeltà del backtest in approfondimento sulla risoluzione adattiva.)
Direzioni di ricerca aperte

SDE di salto neurale: aggiungi una componente di salto appreso per dislocazioni improvvise (arresti improvvisi, sorprese sugli utili):
Dove è una misura casuale di Poisson compensata e un kernel di salto appreso.
Equazioni differenziali controllate neuralmente (CDE neurali): sostituisci il processo di Wiener con un segnale di guida generale, lasciando che i flussi di dati osservati guidino la dinamica. Naturale per il flusso degli ordini, dove il flusso di scambi e quotazioni guida lo stato latente del mercato.
Simulazione di mercato differenziabile: utilizza un SDE neurale come modello generativo all'interno di un simulatore differenziabile e addestra strategie end-to-end propagandosi all'indietro attraverso di esso tramite l'aggiunto. Strategia e modello di mercato coevolvono.
ODE neurali basate sulla fisica: i PINN incorporano un residuo di equazione differenziale nella perdita: la tecnica stessa è introdotta nell'articolo di Navier-Stokes. L’applicazione finanziaria consiste nell’imporre condizioni di non arbitraggio, parità put-call e martingala come termini di penalità o vincoli rigidi sulle dinamiche apprese.
Conclusione

L’inquadramento temporale continuo è strutturalmente corretto: i mercati sono processi continui osservati in tempi discreti e irregolari, e i modelli dovrebbero rispettarlo. La toolchain è matura – torchdiffeq, torchsde, semplice PyTorch per il resto - e i costi noti sono la velocità del risolutore e la stabilità numerica su lunghi intervalli di integrazione.
Ciò che non è stabilito è la parte che decide se qualcosa di questo appartiene a uno stack di produzione. La correttezza strutturale non è prova di un vantaggio predittivo, e il vantaggio specifico rivendicato per la dinamica continua rispetto a a questa caratteristica non è stata misurata qui sui dati commerciali reali. Fino a quando la tabella H2 sopra non viene compilata, tratta tutto ciò che segue come un'ipotesi ben specificata con un'implementazione funzionante allegata, non come un risultato.
Riferimenti
- Chen, R.T.Q., Rubanova, Y., Bettencourt, J., Duvenaud, D. (2018). Equazioni differenziali ordinarie neurali. NeurIPS 2018. arXiv:1806.07366
- Rubanova, Y., Chen, R.T.Q., Duvenaud, D. (2019). ODE latenti per serie temporali campionate in modo irregolare. NeurIPS 2019. arXiv:1907.03907
- Jia, J., Benson, A.R. (2019). Equazioni differenziali stocastiche di salto neurale. NeurIPS 2019.
- Kidger, P., Morrill, J., Foster, J., Lyons, T. (2020). Equazioni differenziali a controllo neurale per serie temporali irregolari. NeurIPS 2020.
- Hasan, A., Pereira, J.M., Farsiu, S., Carin, L. (2021). Modelli di equazioni differenziali stocastiche della rete neurale con applicazioni alla previsione dei dati finanziari. arXiv:2111.13164
- torchdiffeq: github.com/rtqichen/torchdiffeq
- Tutorial di deep learning UvA - ODE neurali: uvadlc-notebooks.readthedocs.io
Autori
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.