Implementation shortfall et TCA maison : mesurer ce que l'exécution vous coûte réellement
Chaque desk institutionnel possède un pipeline d'analyse des coûts de transaction. Presque personne parmi ceux qui font tourner un bot crypto n'en a un. La configuration typique enregistre les exécutions, additionne les frais et qualifie de « slippage » la différence entre le PnL du backtest et le PnL en réel — un résidu unique et inexpliqué qui absorbe la latence, le spread, l'impact, la sélection adverse et chaque ordre non exécuté qui s'est envolé. Vous ne pouvez pas corriger un coût que vous mesurez comme un seul chiffre. La machinerie pour le mesurer correctement existe depuis 1988, elle n'est pas propriétaire, et sur vos propres logs d'exécution elle représente à peu près 200 lignes de Python. Cet article la construit.
Le bénéfice n'est pas un tableau de bord plus joli. Votre backtest contient un modèle de coût — une constante de slippage, une probabilité d'exécution, un coefficient d'impact — et chaque paramètre qu'il contient est actuellement une supposition. La TCA sur exécutions réelles est la seule vérité terrain contre laquelle ces paramètres peuvent être calibrés. Nous avons construit le versant simulation de cette boucle dans Simulation d'exécution : de la fantaisie du prix de clôture à la réalité de la file d'attente ; cet article construit le versant mesure.
Le papier contre la réalité : ce que Perold a réellement mesuré
L'astuce fondatrice revient à André Perold (1988, « The Implementation Shortfall: Paper Versus Reality », Journal of Portfolio Management 14(3), 4–9). Faites tourner deux portefeuilles en parallèle. Le portefeuille papier exécute chaque décision instantanément, en taille illimitée, à coût nul, au prix en vigueur au moment de la décision. Le portefeuille réel est ce que votre bot a réellement fait : exécutions partielles, cotations poursuivies, reliquats annulés, frais. L'implementation shortfall est la différence entre leurs rendements.
La définition compte à cause de ce qu'elle refuse de cacher. Un relevé de frais montre les commissions. Un rapport exécution-contre-prix-limite ne montre rien du tout (vous n'exécutez jamais moins bien que votre limite — par construction). Le portefeuille papier vous facture tout : la dérive entre décision et arrivée, le spread que vous avez franchi, l'impact que vous avez causé et — de façon cruciale — les ordres qui n'ont jamais été exécutés pendant que le prix s'envolait. Wagner et Edwards (1993, « Best Execution », Financial Analysts Journal 49(1), 65–71) qualifiaient les frais visibles de sommet de l'iceberg ; pour tout ce qui a de la rotation, la partie immergée domine.
Fixons la notation. Un ordre parent : sens (achat/vente), taille . Le prix de décision est le mid que votre stratégie a vu quand le signal s'est déclenché. Les exécutions arrivent sous la forme avec une quantité totale exécutée . À l'horizon (parent complété, annulé ou expiré), le mid est . Les frais explicites sont . L'implementation shortfall en termes monétaires :
normalisé en points de base en divisant par le notionnel papier . Positif signifie que vous avez payé. Le premier terme est ce que vos exécutions ont coûté par rapport au portefeuille papier ; le second est le coût d'opportunité de Perold — le reliquat non exécuté valorisé au prix vers lequel il s'est échappé ; le troisième est la seule partie que votre relevé d'exchange reconnaît.
Une note de convention : l'industrie dit « prix d'arrivée », et dans la plupart des TCA actions, l'arrivée désigne le mid au moment où l'ordre a atteint le marché. Pour un bot, le moment de la décision et le moment de l'arrivée diffèrent de votre propre latence interne plus la mise en file d'attente due au rate-limiting — un coût réel et mesurable. Nous conservons donc les deux timestamps et les deux prix, et laissons la décomposition les séparer.
La décomposition : délai, impact, timing, opportunité, frais
Un seul chiffre d'IS vous dit que l'exécution est chère. Il ne vous dit pas pourquoi, et les corrections pour les différentes composantes sont complètement différentes — vous ne résolvez pas le coût de délai et le coût d'impact avec le même changement. L'implementation shortfall étendu de Robert Kissell (Kissell, 2006, « The Expanded Implementation Shortfall: Understanding Transaction Cost Components », Journal of Trading 1(3), 6–16 ; développé au format livre dans The Science of Algorithmic Trading and Portfolio Management, Academic Press, 2013) décompose le total en composantes, chacune attribuable à une étape distincte du cycle de vie de l'ordre. La version du praticien, utilisant le mid d'arrivée (mid au premier accusé de réception de l'exchange) :
L'identité se télescope exactement pour revenir à la définition de Perold — développez les termes et s'annule. Chaque morceau a un propriétaire distinct :
Coût de délai : la dérive du prix entre le déclenchement du signal et le passage en ligne de votre premier ordre enfant sur l'exchange. C'est votre infrastructure — sérialisation, réseau, files d'attente de rate-limiting, contrôles de risque. Si votre signal a un véritable alpha à court horizon, le coût de délai est là où il fuit en premier ; une moyenne systématiquement positive dit que le marché bouge dans votre sens avant que vous n'arriviez — alpha de momentum en déclin, ou quelqu'un de plus rapide tradant le même signal.
Coût de trading : ce que vos exécutions ont payé par rapport à l'arrivée — spread franchi plus impact de marché plus dérive intra-planning. C'est le bulletin de notes de l'algorithme d'exécution, et la quantité que la recherche sur l'exécution modélise réellement. Almgren, Thum, Hauptmann et Li (2005, « Direct Estimation of Equity Market Impact », Risk 18(7), 58–62) l'ont mesurée sur environ 700 000 ordres d'actions américaines des desks de Citigroup (décembre 2001–juin 2003) et ont trouvé que le coût de trading évolue avec la volatilité quotidienne et le taux de participation — impact temporaire suivant une loi de puissance dans le taux de trading avec un exposant proche de 3/5, impact permanent proche du linéaire en taille. Nous réutiliserons leur forme fonctionnelle lors du calibrage.
Risque de timing : pas un terme dans la décomposition de la moyenne, mais la variance autour d'elle. Étaler un parent dans le temps réduit l'impact attendu et vous expose à la volatilité ; pour un planning avec position restante , l'écart-type du coût évolue comme . C'est précisément l'arbitrage qu'optimise le cadre d'Almgren–Chriss. Dans votre rapport TCA, cela apparaît comme la dispersion de l'IS entre les parents — reportez l'écart-type à côté de chaque moyenne, sinon la moyenne captera toute l'attention et les queues capteront tout votre argent.
Coût d'opportunité : la quantité non exécutée valorisée au prix terminal. Pour les stratégies passives, c'est régulièrement la composante la plus grande et la moins examinée, et c'est le terme qui rend tout le cadre honnête — nous y reviendrons dans la section des pièges, car l'omettre est la façon la plus courante de se mentir à soi-même avec la TCA.
Frais : la partie explicite. En crypto, enregistrez les frais après remises (paliers VIP, rebates en tokens convertis au prix du moment de l'exécution) et gardez les rebates maker signés — un frais négatif est une donnée, pas du bruit.
Un exemple concret
Acheter BTC. Le signal se déclenche avec ; notionnel papier 600 000 $. Premier ordre enfant accusé avec un mid . Sur les deux minutes suivantes, 8 BTC s'exécutent au VWAP 60 072 ; le prix s'écarte en tendance, l'algo respecte sa limite, et les 2 BTC restants sont annulés avec le mid à . Frais mélangés de 2,5 bps sur le notionnel exécuté.
| Composante | Formule | USD | bps du papier |
|---|---|---|---|
| Délai | 120 | 2.0 | |
| Coût de trading | 480 | 8.0 | |
| Opportunité | 456 | 7.6 | |
| Frais | 120 | 2.0 | |
| IS total | 1,176 | 19.6 |
Le relevé de l'exchange montre 120 $. Le coût réel de la mise en œuvre de la décision était de 1 176 $ — un facteur dix, avec les deux plus grandes composantes invisibles pour une comptabilité fondée sur les frais. Environ 40 % de ce coût provient de quantité qui n'a jamais été tradée. Un rapport TCA qui n'aurait analysé que les exécutions aurait noté ce parent à 8 bps et l'aurait déclaré correct.

Markouts : tarifer la sélection adverse
L'implementation shortfall note l'ordre parent. Il ne dit rien sur la qualité des exécutions individuelles — spécifiquement, sur le fait que vous tradiez systématiquement ou non avec des contreparties qui savent quelque chose que vous ignorez. Cela se mesure par les markouts : valoriser chaque exécution au mid à un horizon fixe après qu'elle a eu lieu.
où est le prix d'exécution au temps et est le mid au temps . C'est le PnL mark-to-market par unité de l'exécution à l'horizon , et il a une anatomie nette à : une exécution maker démarre à un demi-spread (vous avez acheté au bid, le mid est au-dessus de vous) ; une exécution taker démarre à un demi-spread. Ce qui se passe quand grandit est le contenu informationnel du trade :
- s isole le sniping et le pickoff sur cotation obsolète. Si vos exécutions maker sont déjà sous l'eau une seconde après l'exécution, des participants plus rapides frappent vos cotations au moment où elles deviennent mal tarifées — votre boucle de mise à jour de cotation est plus lente que leur boucle de déclenchement. Ce markout est un diagnostic de latence, pas un diagnostic de stratégie.
- s mesure la sélection adverse classique : des exécutions suivies d'un mouvement continu à travers votre prix. Pour un market maker, c'est le coût que la capture de spread doit battre ; la microstructure actions appelle la quantité connexe realized spread, institutionnalisée dans le reporting SEC Rule 605 à un horizon de 5 minutes. La crypto bouge plus vite ; 10–60s est la bande équivalente.
- s vous dit si les exécutions portent du momentum contre vous au-delà de l'horizon de microstructure — pour les stratégies taker, si l'alpha de votre signal à 60s dépasse le spread-plus-impact que vous avez payé pour entrer. Une courbe de markout taker qui démarre à bps et ne franchit jamais zéro est une stratégie qui paie pour des entrées que son alpha ne peut pas financer.
L'asymétrie maker/taker est toute l'économie du trading passif en deux courbes. Une courbe maker démarrant à bps (demi-spread) et déclinant à bps à 60s dit : vous capturez le spread et en rendez davantage, et aucun palier de rebate ne corrige cela. La même courbe se stabilisant à bps dit que le moteur de cotation gagne sa croûte. C'est aussi précisément ce que les backtests naïfs à exécution-au-contact supposent inexistant — un simulateur qui vous exécute chaque fois que le prix touche votre limite ignore qu'être exécuté est corrélé au fait d'avoir tort, ce qui explique pourquoi le barreau conscient de la file d'attente de l'échelle de simulation d'exécution a besoin de markouts mesurés comme entrée plutôt que comme hypothèse.

Le calcul est un merge_asof sur votre propre flux de mid :
import pandas as pd
def markouts(fills: pd.DataFrame, mids: pd.DataFrame,
horizons=("1s", "10s", "60s")) -> pd.DataFrame:
"""fills: [ts, price, qty, side, liquidity]; mids: [ts, mid].
Both UTC-indexed and sorted. Mid stream must be from YOUR captured
feed, not candles reconstructed later."""
fills = fills.sort_values("ts").reset_index(drop=True)
mids = mids.sort_values("ts")
out = fills.copy()
for h in horizons:
probe = fills[["ts"]].copy()
probe["ts"] = probe["ts"] + pd.Timedelta(h)
m = pd.merge_asof(probe, mids, on="ts", direction="backward")
out[f"mo_{h}"] = (fills["side"] * (m["mid"].values - fills["price"])
/ fills["price"] * 1e4)
return out
def markout_report(mo: pd.DataFrame) -> pd.DataFrame:
"""Qty-weighted markouts by liquidity flag. Weighting matters:
a 0.001 BTC fill and a 2 BTC fill are not equal evidence."""
cols = [c for c in mo.columns if c.startswith("mo_")]
def agg(g):
w = g["qty"] / g["qty"].sum()
return pd.Series({c: (g[c] * w).sum() for c in cols}
| {"n": len(g), "qty": g["qty"].sum()})
return mo.groupby("liquidity").apply(agg)
Découpez le rapport plus finement par symbole, heure de la journée et distance de la cotation au mid. La coupe la plus actionnable pour une stratégie maker est le markout-par-position-dans-la-file-à-l'exécution : les exécutions en tête de file fraîche se tarifient très différemment des exécutions où le niveau a été balayé à travers vous.
Le pipeline : ce qu'il faut logger
La TCA meurt à la couche de logging, pas à la couche mathématique. Les mathématiques ci-dessus ont besoin de chiffres que la plupart des bots jettent, et aucun d'entre eux ne peut être reconstruit après coup à partir de l'historique de l'exchange. Les incontournables :
- Le mid de décision, depuis votre propre flux, au moment du signal. Pas la clôture de bougie de l'exchange, pas une reconstruction ultérieure. Le benchmark est « le prix que ma stratégie croyait quand elle a décidé » — seul votre process à ce moment le connaît.
- Les deux timestamps : le moment de la décision et le moment du premier accusé de réception, sinon le coût de délai est immesurable et se fond silencieusement dans le coût de trading.
- Chaque exécution avec le timestamp de l'exchange, les frais et le flag maker/taker — votre heure de réception locale est polluée par votre propre latence entrante.
- Les parents annulés et expirés, loggés comme tout le reste. Les parents à zéro exécution sont les lignes les plus chères du tableau.
- Un flux de mid persistant (ou flux L1) à une granularité de 100–250ms, conservé assez longtemps pour calculer les markouts. Si vous enregistrez déjà les carnets pour le simulateur d'exécution, c'est gratuit.
Deux tables plates suffisent :
PARENTS = {
"parent_id": "str",
"strategy": "str",
"symbol": "str",
"venue": "str",
"algo": "str", # twap | pov | sniper | quote | ...
"side": "int8", # +1 buy, -1 sell
"qty": "float64", # parent size, base units
"limit_px": "float64", # NaN for unconstrained
"decision_ts": "datetime64[ns, UTC]",
"decision_mid": "float64", # mid your feed showed at decision_ts
"arrival_ts": "datetime64[ns, UTC]", # first exchange ack
"arrival_mid": "float64",
"end_ts": "datetime64[ns, UTC]", # filled / cancelled / expired
"terminal_mid": "float64",
"mkt_volume": "float64", # market volume over [arrival_ts, end_ts]
"sigma_bps": "float64", # realized vol estimate at decision time
}
FILLS = {
"parent_id": "str",
"ts": "datetime64[ns, UTC]", # exchange timestamp
"price": "float64",
"qty": "float64",
"fee": "float64", # quote ccy, post-discount, signed
"liquidity": "str", # maker | taker
"venue": "str",
}
La fonction d'attribution est une transcription directe de la décomposition :
import numpy as np
def is_decomposition(p: pd.Series, fills: pd.DataFrame) -> dict:
"""Expanded implementation shortfall for one parent, bps of paper
notional. Sign convention: positive = cost."""
s, X = p["side"], p["qty"]
paper = X * p["decision_mid"]
x = fills["qty"].sum()
delay = s * X * (p["arrival_mid"] - p["decision_mid"])
trade = s * ((fills["price"] - p["arrival_mid"]) * fills["qty"]).sum()
oppty = s * (X - x) * (p["terminal_mid"] - p["arrival_mid"])
fees = fills["fee"].sum()
bps = lambda v: 1e4 * v / paper
return {
"parent_id": p["parent_id"],
"delay_bps": bps(delay), "trade_bps": bps(trade),
"oppty_bps": bps(oppty), "fees_bps": bps(fees),
"is_bps": bps(delay + trade + oppty + fees),
"fill_ratio": x / X,
"participation": x / max(p["mkt_volume"], x),
}
def tca_table(parents: pd.DataFrame, fills: pd.DataFrame) -> pd.DataFrame:
fg = dict(tuple(fills.groupby("parent_id")))
empty = fills.iloc[0:0]
rows = [is_decomposition(p, fg.get(p["parent_id"], empty))
for _, p in parents.iterrows()]
return parents.merge(pd.DataFrame(rows), on="parent_id")
Et les requêtes d'attribution, là où la TCA cesse d'être de la comptabilité et devient de la recherche :
tca = tca_table(parents, fills)
def wavg(g: pd.DataFrame, col: str) -> float:
w = g["qty"] * g["decision_mid"] # notional weights
return (g[col] * w).sum() / w.sum()
comp = ["delay_bps", "trade_bps", "oppty_bps", "fees_bps", "is_bps"]
by_strat = tca.groupby("strategy").apply(
lambda g: pd.Series({c: wavg(g, c) for c in comp}
| {"is_std": g["is_bps"].std(), "n": len(g)}))
done = tca[tca["fill_ratio"] > 0.99]
done["pov_bin"] = pd.qcut(done["participation"], 6)
impact_curve = done.groupby("pov_bin").apply(lambda g: wavg(g, "trade_bps"))
tca["hour"] = tca["arrival_ts"].dt.hour
by_hour = tca.groupby("hour").apply(lambda g: wavg(g, "is_bps"))
by_venue = tca.groupby("venue").apply(
lambda g: pd.Series({"is_bps": wavg(g, "is_bps"),
"is_std": g["is_bps"].std(),
"fill_ratio": g["fill_ratio"].mean(), "n": len(g)}))
La requête 4 est la table d'entrée qu'optimise un smart order router — router sans TCA par venue, c'est router selon la grille tarifaire, c'est-à-dire selon la plus petite composante de coût (le smart order routing en crypto prend cette table comme point de départ). Avec le module de markout, cela représente les ~200 lignes promises.
Boucler la boucle : calibrer le modèle de coût du backtest
C'est ici que le pipeline se rentabilise. Votre backtest affirme des chiffres : slippage_bps = 5, une courbe de probabilité d'exécution, un coefficient d'impact. Chacun d'eux est une affirmation sur l'exécution en réel, et la table TCA est l'exécution en réel. La boucle : mesurer avec la TCA, ajuster le modèle de coût, lancer le backtest avec le modèle ajusté, trader, re-mesurer.

Pour le coût taker, empruntez la forme fonctionnelle à Almgren et al. (2005) plutôt que d'en inventer une. Leur résultat — coût proportionnel à la volatilité quotidienne fois une puissance du taux de participation — donne un modèle à deux paramètres :
avec l'estimation actions comme prior. Ajustez-le sur des moyennes par bins, pas sur des parents bruts — les coûts individuels des parents sont dominés par le bruit (c'est le terme de risque de timing), et une régression log-log sur données brutes ajustera volontiers le bruit :
done = tca[(tca["fill_ratio"] > 0.99) & (tca["participation"] > 0)]
done["norm_cost"] = done["trade_bps"] / done["sigma_bps"]
bins = done.groupby(pd.qcut(done["participation"], 8)).agg(
pov=("participation", "mean"), cost=("norm_cost", "mean"))
bins = bins[bins["cost"] > 0] # can't log a negative bin
b, log_a = np.polyfit(np.log(bins["pov"]), np.log(bins["cost"]), 1)
def taker_cost_bps(participation, sigma_bps, a=np.exp(log_a), b=b):
return a * sigma_bps * participation ** b
Ensuite, soyez honnête sur les barres d'erreur. L'équipe d'Almgren avait 700 000 ordres et reportait tout de même l'exposant d'impact temporaire à ; un bot avec 2 000 parents ne peut pas se permettre d'estimer librement par symbole. Le régime pratique : mutualiser entre symboles après normalisation par , contracter fortement vers 0,6 (ou simplement le fixer et n'ajuster que ), et réajuster mensuellement. Si votre ajusté dérive vers le haut, votre empreinte a grandi ou le marché s'est aminci — dans les deux cas le backtest avait besoin de le savoir.
Pour les stratégies maker, les cibles de calibrage sont différentes et proviennent des deux autres modules :
- Probabilité d'exécution : le modèle de file d'attente du simulateur d'exécution prédit par cellule (distance-de-spread, position-dans-la-file) ; votre log de parents fournit les ratios d'exécution réalisés par cellule. Un désaccord est un bug de simulateur avec une référence de grille attachée.
- Sélection adverse : remplacez l'hypothèse implicite du simulateur « les exécutions sont interchangeables » par la table de markout mesurée — une exécution maker simulée à la distance dans l'heure porte le mesuré comme décote mark-to-market immédiate. Ce seul changement est la différence entre un backtest maker qui hallucine et un qui colle à la réalité ; c'était l'entrée de calibrage manquante signalée dans l'article de simulation d'exécution.
- Dispersion du coût : injectez la distribution de l'IS, pas sa moyenne, dans le backtest. Un modèle de coût qui ne déplace que la moyenne ne peut pas reproduire les drawdowns que crée le risque de timing ; même une loi log-normale ajustée à l'IS par parent bat une constante.
Le traitement complet de la courbe de slippage — formes fonctionnelles, conditionnement par régime, quand la loi de puissance se casse — fait l'objet de son propre article : Courbes de slippage et modèles de coût pour backtests. Le point ici est architectural : les modèles de cet article sont inajustables sans les tables du présent article.
Pièges
La littérature TCA a des décennies, et les façons de la truquer aussi. Trois modes d'échec représentent l'essentiel de l'auto-illusion.
Manipulation du benchmark. Tout benchmark autre que le prix d'arrivée peut être caressé. Le classique est le VWAP : un algorithme noté contre le VWAP d'intervalle peut le suivre à un point de base près pendant que la position perd vingt contre l'arrivée, parce que le benchmark dérive avec le prix que vous poussez — et à une participation significative vos propres prints sont le VWAP, donc le suivre est un devoir noté par soi-même. Nous avons disséqué la politique des benchmarks dans TWAP vs VWAP vs POV ; la règle côté TCA est plus simple : les benchmarks sont choisis avant de trader, et l'IS-contre-arrivée est toujours calculé même quand un scheduler est noté contre son benchmark de planning. La variante plus subtile est la manipulation de l'arrivée : si la composante qui fixe decision_ts peut voir le momentum à court terme, elle peut chronométrer les « décisions » pour flatter le terme de délai. Les timestamps de décision appartiennent à la couche de signal, loggés avant que toute logique d'exécution ne tourne.
Biais du survivant dans l'analyse limitée aux exécutions. Conditionnez votre analyse de coût sur les exécutions et l'exécution passive semble gratuite. Concrètement : 100 parents d'achat passifs, un tick sous le mid. Soixante s'exécutent et — étant passifs — s'exécutent à des prix en moyenne 3 bps meilleurs que l'arrivée : « coût » mesuré bps, un rapport dont être fier. Les quarante qui ne se sont jamais exécutés étaient exactement ceux où le prix s'est envolé ; valorisez-les à 25 bps adverses à l'annulation et le chiffre honnête est bps. Le rapport limité aux exécutions et le vrai rapport diffèrent de 11 bps et de signe. Ce n'est pas un cas marginal — c'est le mécanisme du trading passif : être exécuté est corrélé au fait que le prix passe à travers vous, ce qui est le même conditionnement qui rend les backtests à exécution-au-contact fantaisistes. Le coût d'opportunité n'est pas un raffinement optionnel de l'IS ; c'est le terme qui défend toute la mesure contre la sélection. Le même biais a une variante taker : les ordres IOC qui ont raté, les ordres rejetés par les rate-limits ou les contrôles de risque — si les ratés ne sont pas loggés, le coût de rater n'est pas mesuré, et il est le plus grand précisément dans les marchés rapides où votre stratégie voulait le plus le trade.
Divers pièges, brièvement : mids d'arrivée reconstruits à partir de bougies d'exchange (votre flux et la bougie divergent exactement quand ça compte) ; moyenner les bps par parent sans pondérations notionnelles (mille exécutions de poussière l'emportant sur un vrai ordre) ; frais loggés avant remise ; mids de markout pris sur une venue différente de l'exécution (base cross-venue se faisant passer pour de la sélection adverse) ; et sur les perps, laisser l'accumulation de funding déborder dans la fenêtre d'exécution — le funding est un coût, mais pas un coût d'exécution, et les mélanger empoisonne les deux analyses.
Ce qu'il faut faire cette semaine
Ajoutez les deux tables de log à votre bot — le schéma ci-dessus est une liste de colonnes, pas un projet. Ne rétro-remplissez rien ; deux semaines de logs honnêtes valent mieux qu'une année de reconstructions. Lancez la décomposition et les trois horizons de markout. Vous apprendrez quelle composante domine (presque jamais les frais), si vos exécutions passives sont adversement sélectionnées au-delà de leur capture de spread, et à quelle distance la constante de coût de votre backtest se situe de la courbe mesurée. Puis recâblez la courbe ajustée dans le simulateur et relancez le backtest qui vous a dit de lancer cette stratégie en premier lieu. Le portefeuille papier de Perold, trente-huit ans plus tard, reste le seul adversaire honnête que le vôtre ait.
Authors
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.