Knowledge Distillation: Compressing Trading Models for Low-Latency Deployment
De spanning tussen nauwkeurigheid en latentie in ML-gestuurde handel heeft al een gepubliceerd antwoord op deze blog. Spread modeling with machine learning beveelt een splitsing in twee fasen aan: een model met snelle gradiëntversterking voert de latentiekritische realtime quotering uit, terwijl een diep model asynchroon draait en het een secundair signaal geeft of de parameters aanpast. Twee modellen, twee klokken, één systeem.
Kennisdestillatie is een ander antwoord op dezelfde spanning. In plaats van het langzame model naast het snelle te gebruiken, gebruik je het eenmalig, offline, om het snelle model te trainen. De leerling leert de volledige waarschijnlijkheidsverdeling van de leraar over de uitkomsten, niet alleen de harde labels, en de leraar verlaat vervolgens het warme pad volledig. Eén model op het inferentietijdstip, geen asynchrone koppeling, geen verouderingsvenster.
Welk antwoord wint is empirisch, en dit artikel geeft daar nog geen antwoord op. Wat volgt is de machinerie, plus een expliciete verklaring van de metingen die hierover zouden beslissen. Niets hier is een benchmarkresultaat; waar normaal gesproken een nummer zou komen, staat een markering die aangeeft wat er moet worden uitgevoerd.
Eén framing-correctie vooraf, uit DeepLOB en deep learning in het orderboek: hoge classificatienauwkeurigheid vertaalt zich niet automatisch in winst – de voorspelde zet moet de bid-ask spread opheffen. "Behoud de richtingsnauwkeurigheid van de leraar" is daarom verkeerd om een destillatieopstelling tegen te optimaliseren.
Het leraar-leerlingkader

De oorspronkelijke formulering van Hinton, Vinylals en Dean (2015) is eenvoudig. Je hebt een lerarenmodel (groot, langzaam, nauwkeurig) en een studentenmodel (klein, snel, af te leren). De leerling leert tegelijkertijd van twee signalen:
- Harde doelstellingen: de labels van de grondwaarheid (bijvoorbeeld: de prijs ging omhoog of omlaag)
- Zachte doelen: de waarschijnlijkheidsverdeling van de leraar over alle klassen
De verliesfunctie van de student combineert beide:
waar En zijn de docenten- en leerlinglogits, is de softmax-functie, is de temperatuurparameter, en controleert het evenwicht tussen de twee verliescomponenten.
Waarom zachte doelen belangrijk zijn voor handelen
De drie-klasse up/stationaire/down middenprijsformulering, de drempelwaarden, en waarom de daaruit voortvloeiende onbalans betekent dat u gewogen F1 rapporteert in plaats van nauwkeurigheid, zijn allemaal ingesteld in DeepLOB – neem hier dat labelschema aan. Het destillatiespecifieke punt is wat de leraar vóór de argmax uitzendt: een harde "up" draagt één bit, terwijl 0,72/0,21/0,07 ook zegt dat de beweging kan stagneren en vrijwel zeker niet zal worden teruggedraaid. Die structuur over de klassen heen is het extra trainingssignaal, en daarom kan een leerling met een zachte doelgroep beter generaliseren dan dezelfde leerling die alleen op labels is getraind.
Een waarschuwing over wat dat vertrouwen niet is. Softmax-uitvoer is geen gekalibreerde onzekerheid, en het behandelen van 0,55 versus 0,85 als invoer voor positiebepaling is de kortere weg die [conforme voorspelling voor handelen] (/en/blog/post/conformal-prediction-trading) bestaat om te weigeren - het leid de grootte af van de intervalbreedte, een edge-ratio en een no-trade-filter wanneer het interval nul bedraagt, en geen daarvan geeft een ruwe softmax je. Het verdienen van de claim op maat betekent hier het meten van de kalibratie van de leerling aan de hand van die van de leraar (betrouwbaarheidsdiagram, ECE) en aantonen dat de destillatie deze behoudt. Dat resultaat staat nog niet in dit artikel.
Temperatuur en zachte doelen

De temperatuurparameter controleert de "zachtheid" van de waarschijnlijkheidsverdeling. Gegeven logits , de softmax met temperatuur is:
Wanneer (standaard softmax), is de verdeling piekerig: de dominante klasse krijgt het grootste deel van de waarschijnlijkheidsmassa. Als toeneemt, wordt de verdeling vlakker, waardoor de relatieve grootheden van de logits duidelijker zichtbaar worden.
| Temperatuur | Effect | Gebruiksscenario |
|---|---|---|
| Standaard softmax, peaky | Normale gevolgtrekking | |
| Matige verzachting | Algemene distillatie | |
| Zware verzachting | Wanneer de leraar veel zelfvertrouwen heeft | |
| Bijna uniform | Zelden bruikbaar, signaal wegvaagt |
Er is een plausibel argument dat handelsmodellen een gematigde temperatuur willen: financiële voorspellingen zijn veel minder betrouwbaar dan beeldclassificatie, dus een leraar kan 0,55/0,30/0,15 geven in plaats van 0,99/0,005/0,005, waardoor er minder pieken overblijven voordat het signaal wegspoelt. Dat is een argument, geen bevinding: de bandbreedte moet afkomstig zijn van een analyse van echte gegevens, gescoord door gewogen F1, en kan per regime verschillen.
De factor in de KL-divergentieterm compenseert de verminderde gradiëntgroottes bij hogere temperaturen. Zonder dit zou het destillatieverlies verwaarloosbaar klein worden neemt toe.
Temperatuur kiezen via rasterzoeken
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from sklearn.metrics import f1_score
def distillation_loss(
student_logits: torch.Tensor,
teacher_logits: torch.Tensor,
labels: torch.Tensor,
temperature: float,
alpha: float,
) -> torch.Tensor:
"""Combined hard-target + soft-target distillation loss."""
hard_loss = F.cross_entropy(student_logits, labels)
soft_teacher = F.log_softmax(teacher_logits / temperature, dim=-1)
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
soft_loss = F.kl_div(
soft_student,
soft_teacher,
log_target=True,
reduction="batchmean",
)
return alpha * hard_loss + (1.0 - alpha) * (temperature ** 2) * soft_loss
def search_temperature(
teacher: nn.Module,
student_factory, # callable returning a fresh student
train_loader: DataLoader,
val_loader: DataLoader,
temperatures: list[float] = [1, 2, 3, 5, 8, 12],
alpha: float = 0.3,
epochs: int = 30,
lr: float = 1e-3,
device: str = "cuda",
):
"""Grid search over temperature, scored by weighted F1 (not accuracy:
the up/flat/down label scheme is heavily imbalanced toward flat)."""
best_f1, best_T, best_student = 0.0, 1.0, None
for T in temperatures:
student = student_factory().to(device)
optimizer = torch.optim.AdamW(student.parameters(), lr=lr)
for epoch in range(epochs):
student.train()
for X, y in train_loader:
X, y = X.to(device), y.to(device)
with torch.no_grad():
teacher_logits = teacher(X)
student_logits = student(X)
loss = distillation_loss(
student_logits, teacher_logits, y, T, alpha
)
optimizer.zero_grad()
loss.backward()
optimizer.step()
student.eval()
preds, targets = [], []
with torch.no_grad():
for X, y in val_loader:
preds.append(student(X.to(device)).argmax(dim=-1).cpu())
targets.append(y)
f1 = f1_score(
torch.cat(targets), torch.cat(preds), average="weighted"
)
print(f"T={T:>4.1f} val_weighted_f1={f1:.4f}")
if f1 > best_f1:
best_f1, best_T, best_student = f1, T, student
print(f"\nBest temperature: T={best_T}, val_weighted_f1={best_f1:.4f}")
return best_T, best_student
Ensembles distilleren tot één enkel model

Een kwantitatief ensemble combineert inductieve vooroordelen: een gradiënt-versterkte boom over orderboekkenmerken, een 1D-CNN over recente ticks, een transformator over multi-timeframe-vensters, een lineair model over macrofactoren. Middeling is stabieler dan welk lid dan ook, en het uitvoeren van alle vier leden vermenigvuldigt latentie en kosten – de situatie die de tweetrapssplitsing van spread modeling with machine learning afhandelt door langzame leden te degraderen naar een asynchrone zijkanaal. Door destillatie worden ze alle vier samengevoegd tot één student in het hete pad.
De output van de ensembledocent is het gemiddelde van de softmax-outputs van zijn leden:
waar is het aantal ensembleleden. De student wordt getraind tegen deze gemiddelde verdeling.
class EnsembleTeacher(nn.Module):
"""Wraps K models, returns averaged logits for distillation."""
def __init__(self, models: list[nn.Module]):
super().__init__()
self.models = nn.ModuleList(models)
@torch.no_grad()
def forward(self, x: torch.Tensor) -> torch.Tensor:
logits = torch.stack([m(x) for m in self.models], dim=0)
return logits.mean(dim=0) # average logits, not softmax
class TradingStudent(nn.Module):
"""Lightweight MLP for sub-millisecond inference."""
def __init__(self, input_dim: int, hidden: int = 64, n_classes: int = 3):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, hidden),
nn.ReLU(),
nn.BatchNorm1d(hidden),
nn.Linear(hidden, hidden),
nn.ReLU(),
nn.BatchNorm1d(hidden),
nn.Linear(hidden, n_classes),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.net(x)
De asymmetrie van het aantal parameters is het hele punt: een tweelaags MLP met 64 verborgen eenheden ligt in de orde van 8.000 parameters voor een taak met 60 functies en 3 klassen, tegen een ensemble waarvan het gecombineerde aantal in de miljoenen loopt.
Wat de leerling behoudt en wat hij verliest
Dit is de dragende empirische vraag en het artikel geeft er geen antwoord op. De intuïtie is dat de student de verdeling van het ensemble volgt en afhaakt in gestresste regimes, waar de diversiteit van het ensemble het werk doet – maar een retentiecijfer betekent alleen iets gemeten op basis van echte orderboekgegevens, opgesplitst per regime, en gerapporteerd als gewogen F1. Een student die op rustige dagen standhoudt en tijdens een liquidatiecascade instort, is een ander product dan een product dat gracieus degradeert, en een totaal aantal kan ze niet van elkaar onderscheiden.
Drie oplossingen zijn het waard om aan die meting te toetsen in plaats van vooraf te beweren:
- Neem stressperioden op in de destillatieset, zodat de leerling de regimes ziet waar de kloof naar verwachting zal ontstaan.
- Op kenmerken gebaseerde destillatie — match tussenliggende representaties, niet alleen de uiteindelijke outputs.
- Hulpregime-hoofd op de leerling, waardoor regimebewuste kenmerken in de gedeelde trunk worden gedwongen.
Zelfdistillatie: wanneer de student de leraar wordt

Zelfdistillatie is een techniek waarbij een model kennis uit zichzelf destilleert.
Born-Again Networks (BAN's)
Train een student met architectuur die identiek is aan de leraar. De ‘wedergeboren’ leerling presteert vaak beter dan het origineel, en het proces herhaalt zich:
Elke generatie traint op zachte doelstellingen van de vorige, waarbij de winst doorgaans na een paar generaties verzadigd raakt. Voor handelsmodellen kost dit architectonisch niets – geen nieuwe functies, geen nieuwe gegevens, alleen een andere trainingsprocedure – wat ook betekent dat het goedkoop is om te testen en dat er geen excuus is om het als ongetest te melden.
Diepte-wijze zelfdistillatie
Bevestig hulpclassificatoren op tussenlagen. De diepste uitgang dient als leraar voor de ondiepere uitgangen. Bij inferentie kies je een uitgang: ondiep voor lagere latentie, diep voor maximale nauwkeurigheid.
Dit is hier het idee dat het beste past bij een handelssysteem, omdat exitdiepte een runtime-latentieknop wordt: één getraind netwerk dekt een reeks budgetten in plaats van zich tijdens de training aan één enkele architectuur te binden. Als het boek snel beweegt, neem je de ondiepe uitgang en accepteer je een slechtere achterkant; als het stil is betaal je voor volle diepgang. Zowel de nauwkeurigheid-per-exit- als de latentie-per-exit-curven zijn meetbaar, en hun crossover beslist of de knop de moeite waard is.
class SelfDistillingNet(nn.Module):
"""Network with early-exit classifiers for variable-latency inference."""
def __init__(self, input_dim: int, n_classes: int = 3):
super().__init__()
self.block1 = nn.Sequential(
nn.Linear(input_dim, 128), nn.ReLU(), nn.BatchNorm1d(128)
)
self.block2 = nn.Sequential(
nn.Linear(128, 64), nn.ReLU(), nn.BatchNorm1d(64)
)
self.block3 = nn.Sequential(
nn.Linear(64, 32), nn.ReLU(), nn.BatchNorm1d(32)
)
self.exit1 = nn.Linear(128, n_classes)
self.exit2 = nn.Linear(64, n_classes)
self.exit3 = nn.Linear(32, n_classes) # final exit
def forward(
self, x: torch.Tensor, exit_layer: int = 3
) -> torch.Tensor:
h1 = self.block1(x)
if exit_layer == 1:
return self.exit1(h1)
h2 = self.block2(h1)
if exit_layer == 2:
return self.exit2(h2)
h3 = self.block3(h2)
return self.exit3(h3)
def forward_all_exits(self, x: torch.Tensor):
"""Return logits from all exits (for self-distillation training)."""
h1 = self.block1(x)
h2 = self.block2(h1)
h3 = self.block3(h2)
return self.exit1(h1), self.exit2(h2), self.exit3(h3)
def self_distillation_step(
model: SelfDistillingNet,
x: torch.Tensor,
y: torch.Tensor,
temperature: float = 4.0,
alpha: float = 0.5,
) -> torch.Tensor:
"""One training step with self-distillation from deepest exit."""
logits_1, logits_2, logits_3 = model.forward_all_exits(x)
loss_hard = F.cross_entropy(logits_3, y)
loss_distill_1 = distillation_loss(
logits_1, logits_3.detach(), y, temperature, alpha
)
loss_distill_2 = distillation_loss(
logits_2, logits_3.detach(), y, temperature, alpha
)
return loss_hard + 0.5 * loss_distill_1 + 0.5 * loss_distill_2
Waar het gevolgtrekkingsbudget vandaan komt

Distillatie is alleen van belang als gevolgtrekking binnen een hard budget moet plaatsvinden, en de volledige tick-to-trade-ladder – NIC-naar-userspace, kernel-bypass, het sub-100 µs totaal en het sub-10 µs niveau dat FPGA en gedeeld geheugen afdwingt – is al uiteengezet in [data en communicatie in algoritmische handel] (/en/blog/post/data-communication-algotrading). De rij die de ladder openlaat is modelgevolgtrekking, en dat is de rij die destillatie probeert te vullen.
Weersta het vullen van de andere rijen met een latentietabel van modelklasse. Spread modeling with machine learning publiceert al de vergelijking tussen GBM en deep-learning, plus het voorbehoud dat belangrijker is dan de cijfers: latentie is implementatieafhankelijk, en hetzelfde LightGBM-model neemt tientallen microseconden per rij van Python, maar een paar microseconden van een gecompileerde voorspeller. Elke latentieclaim hier moet het raamwerk, de kern en de batchgrootte noemen, anders is het ruis.
Specifiek op GPU's: de vaste overheadkosten per lancering moeten worden afgeschreven voordat een apparaat überhaupt helpt, en de gevolgtrekking van één rij bevindt zich ver links van de daklijn, waar dit nooit het geval is. When the GPU pays off meet die afschrijvingscurve op de juiste manier met een batch sweep, inclusief hoe een discrete PCIe-kaart de nok verder naar rechts duwt - lees dat in plaats van te vertrouwen op een constante uit het geheugen.
Kwantisering na destillatie
Een gedestilleerde student comprimeert verder: INT8-gewichten (ongeveer 2x op CPU met AVX-512 VNNI), binaire/ternaire gewichten die vermenigvuldigingen in optellingen omzetten, en snoeien om berekeningen van bijna nul over te slaan.
De verleidelijke bewering is dat door distillatie en vervolgens kwantisering meer nauwkeurigheid behouden blijft dan door kwantisering alleen, omdat de leerling al een compacte representatie heeft geleerd. Verzend er niet mee. De GPU-precisieval is het standpunt van de blog over verminderde numerieke precisie: het retourneerde stilletjes plausibel ogende rommel, en wat het snelle pad verzendbaar maakte was een gekwantificeerde equivalentiepoort – vullingen verschoven, PnL-delta in bps – en geen bewering. Een INT8-student is een ander model totdat die poort wordt vergeleken met de FP32-student.
import torch.quantization as quant
def quantize_student(student: nn.Module, calibration_loader: DataLoader):
"""Post-training static quantization for CPU deployment."""
student.cpu()
student.eval()
student.qconfig = quant.get_default_qconfig("x86")
student_prepared = quant.prepare(student)
with torch.no_grad():
for X, _ in calibration_loader:
student_prepared(X)
student_quantized = quant.convert(student_prepared)
return student_quantized
FPGA-implementatie: de distill-naar-bitstream-pijplijn

FPGA's vormen het sub-10 µs-niveau in de latency ladder, en de Tbricks/Broadridge review omvat ze in productie naast kernel-bypass NIC's - deterministische latentie, geen OS-jitter, op dezelfde locatie als de netwerkstack. Wat nergens op deze blog wordt behandeld, is hoe een gedestilleerd model er één krijgt.
DeepLOB's production notes noemen ONNX/TensorRT, INT8-kwantisering en FPGA-implementatie als drie opties en stop daar. Dit is waar de derde zich in uitbreidt:
1. Train ensemble teacher (offline, GPU cluster, hours/days)
|
2. Distill to small MLP student (offline, single GPU, minutes)
|
3. Quantize student to INT8 / fixed-point (offline, CPU)
|
4. Convert to HLS (High-Level Synthesis) or RTL
|
5. Synthesize FPGA bitstream (offline, hours)
|
6. Deploy to FPGA card in production server
|
7. Inference: market data -> FPGA -> trading signal
De bindende beperking is dat het model moet passen in de logische elementen van het apparaat: LUT's, DSP-slices, blok-RAM. Als een begroting van de orde van grootte in plaats van een meting: een tweelaags MLP met 64 verborgen eenheden en INT8-gewichten ligt in de orde van 8.000 vermenigvuldig-accumuleert per gevolgtrekking en ~16 KB aan gewichten, een kleine fractie van een middenklasse-onderdeel. Dit is waar distillatie zijn brood verdient: de ensembleleraar past niet in welk budget dan ook; de leerling is nog lang niet aan de grens.
Tools die PyTorch/ONNX automatiseren tot synthetiseerbare hardware zijn onder meer AMD/Xilinx Vitis AI, hls4ml (van CERN) en FINN (van Xilinx Research).
Voorbeeld: hls4ml-conversie
import hls4ml
import onnx
dummy_input = torch.randn(1, 60) # 60 input features
torch.onnx.export(student, dummy_input, "student.onnx", opset_version=13)
hls_config = hls4ml.utils.config_from_onnx_model(
onnx.load("student.onnx"),
granularity="name",
default_precision="ap_fixed<16,8>",
default_reuse_factor=1, # full parallelism
)
hls_model = hls4ml.converters.convert_from_onnx_model(
"student.onnx",
hls_config=hls_config,
output_dir="hls_student",
backend="VivadoAccelerator",
board="alveo-u250",
)
hls_model.compile()
hls_model.build(csim=True, synth=True)
hls_model.report()
hls_model.report() is de enige geloofwaardige bron van resource- en latentiecijfers voor een bepaald model, bord, precisie en hergebruikfactor - de cijfers bewegen aanzienlijk mee default_reuse_factor alleen. Het citeren van een "typische" synthesetabel zonder deze uit te voeren is gissen.
Praktische overwegingen

Lerarenlogboeken vooraf berekenen
Voor distillatie zijn voorspellingen van leraren nodig over de hele trainingsset – eenmalige offline kosten die de moeite waard zijn om bewust te betalen: voer het ensemble één keer uit, bewaar de logits, train studenten tegen de cache. Temperatuur-sweeps en architectuuronderzoeken kosten dan niets extra bij de voorwaartse passen van leraren, wat de bovenstaande sweeps überhaupt praktisch maakt.
De enige distillatiespecifieke monitor
Functie-pipeline-hygiëne, voortschrijdende normalisatie omdat z-score-parameters afwijken, monitoring van inputdistributie-shift en door het regime geactiveerde hertraining worden allemaal behandeld in [DeepLOB's productiesectie] (/en/blog/post/deeplob-deep-learning-order-book) en zijn hier ongewijzigd van toepassing.
De specifieke monitor voor destillatie is leraar-student KL-divergentie op live data. De leraar bestaat nog steeds offline; voer het uit op een steekproef van live-invoer en vergelijk distributies. Stijgende KL betekent dat de benadering van de student vernederend is in regimes waar het niet op is gebaseerd – en het vuurt voordat de nauwkeurigheid dat doet, omdat het niet op labels wacht. De drempel voor herscholing moet worden gekalibreerd tegen de waargenomen KL in perioden waarvan bekend is dat ze goed zijn en waarvan bekend is dat ze verslechterd zijn; a priori gekozen, is het willekeurig.
Wanneer niet distilleren?
- De leraar is al klein (een lineair model, een ondiepe GBM): destillatie voegt een pijplijnfase toe zonder compressie.
- Latentie is geen beperking (dagelijkse herbalancering, eindedagsignalen): zet de leraar in.
- De interpreteerbaarheid gaat boven snelheid: een gedestilleerd netwerk is moeilijker uit te leggen dan het bomenensemble dat het vervangt.
- De tweetrapssplitsing werkt al: als het asynchrone, langzame model in de spread-modeling-architectuur resultaten oplevert, moet destillatie het op basis van een afgemeten vergelijking verslaan voordat het de vervanging van een werkend systeem rechtvaardigt.
Samenvatting

Distillatie is een samenhangend alternatief voor de snelle/langzame splitsing in twee fasen: train de beste leraar die je je offline kunt veroorloven, breng de soft-target-structuur over naar een leerling die klein genoeg is voor het warme pad, kwantiseer, implementeer op CPU of FPGA. De diepgaande variant gaat verder en maakt latentie tot een runtime-keuze in plaats van een trainingstijd-keuze.
Wat dit artikel bewust niet beweert, is dat het beter is dan wat de blog al publiceert. Voor dat oordeel zijn drie metingen nodig op basis van echte orderboekgegevens: de student-vs-ensemble-gewogen F1-retentiecurve opgesplitst per regime, de temperatuursweep en een INT8-pariteitspoort in de stijl van [de GPU-precisieval] (/en/blog/post/gpu-precision-trap-fp32-backtest). Zolang deze niet bestaan, is dit een beschrijving van een techniek en geen aanbeveling om deze in te zetten.
Referenties
-
Hinton, G., Vinylals, O., en Dean, J. (2015). De kennis destilleren in een neuraal netwerk. arXiv:1503.02531
-
Furlanello, T., Lipton, ZC, Tschannen, M., Itti, L., & Anandkumar, A. (2018). Opnieuw geboren neurale netwerken. ICML. arXiv:1805.04770
-
Zhang, L., Song, J., Gao, A., Chen, J., Bao, C., en Ma, K. (2019). Wees je eigen leraar: verbeter de prestaties van convolutionele neurale netwerken via zelfdestillatie. ICCV. arXiv:1905.08094
-
Romero, A., Ballas, N., Kahou, SE, Chassang, A., Gatta, C., & Bengio, Y. (2015). FitNets: tips voor dunne, diepe netten. ICLR. arXiv:1412.6550
-
Gou, J., Yu, B., Maybank, SJ, & Tao, D. (2021). Kennisdistillatie: een onderzoek. Internationaal tijdschrift voor computervisie, 129, 1789-1819. arXiv:2006.05525
-
Duarte, J., et al. (2018). Snelle inferentie van diepe neurale netwerken in FPGA's voor deeltjesfysica (hls4ml). Journal of Instrumentation, 13, P07027. arXiv:1804.06913
-
Umuroglu, Y., et al. (2017). FINN: een raamwerk voor snelle, schaalbare gebinariseerde neurale netwerkinferentie. FPGA '17. arXiv:1612.07119
-
Zhang, Z., Zohren, S., & Roberts, S. (2019). DeepLOB: diepe convolutionele neurale netwerken voor limietorderboeken. IEEE-transacties over signaalverwerking, 67(11), 3001-3012. arXiv:1808.03668
Auteurs
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.