Le code est lu bien plus souvent qu'il n'est écrit
Une base de code de trading vit des années et est modifiée par des gens qui ne l'ont pas écrite, souvent sous pression et souvent par vous-même, six mois plus tard. Le code propre est un code qu'on comprend vite et qu'on peut modifier sans danger. Il ne s'agit pas de règles de style, mais de rendre visible une mauvaise modification. Une fonction mal nommée, qui fait trois choses et cache une limite dans une formule, est l'endroit où se logent les bugs coûteux, comme une limite de risque que personne ne se rappelait codée en dur.
# The same calculation, written two ways
def f(d, e, m, w):
r = []
if sum([abs(x) for x in d.values()]) / e > m:
r.append("LEVERAGE")
for k in sorted(d):
if abs(d[k]) / sum([abs(x) for x in d.values()]) > w:
r.append("CONCENTRATION:" + k)
return r
book = {"AAPL": 80_000.0, "MSFT": -10_000.0, "NVDA": 10_000.0}
print(f(book, 50_000.0, 1.5, 0.5)) # works, but what is d? e? m? w?Des noms, de petites fonctions, pas de nombres cachés
Trois habitudes font l'essentiel. Nommez les choses pour ce qu'elles sont : gross_exposure plutôt que f, max_leverage plutôt que m. Gardez des fonctions petites et à but unique : chacune fait une tâche qu'on peut nommer en quelques mots, donc se teste seule et se réutilise, et une fonction plus grande se construit en les appelant. N'enterrez pas les nombres : une limite comme 1.5 tapée dans une formule est un nombre magique que personne ne peut trouver ou changer sans risque ; passez-la en argument (ou nommez-la comme constante). Calculer l'exposition brute une fois dans sa propre fonction supprime aussi l'expression répétée.
# Small functions with names that say what they return; the limits come in as arguments
def gross_exposure(positions):
return sum(abs(value) for value in positions.values())
def leverage(positions, equity):
return gross_exposure(positions) / equity
def risk_flags(positions, equity, max_leverage, max_weight):
flags = []
if leverage(positions, equity) > max_leverage:
flags.append("LEVERAGE")
gross = gross_exposure(positions)
for symbol in sorted(positions):
if abs(positions[symbol]) / gross > max_weight:
flags.append(f"CONCENTRATION:{symbol}")
return flags
book = {"AAPL": 80_000.0, "MSFT": -10_000.0, "NVDA": 10_000.0}
print(risk_flags(book, 50_000.0, 1.5, 0.5)) # ['LEVERAGE', 'CONCENTRATION:AAPL']
print(risk_flags(book, 50_000.0, 3.0, 0.9)) # [] the same code, looser limitsProtéger les bords et dire pourquoi
Traitez d'abord l'entrée invalide et retournez ou levez tout de suite (une clause de garde), pour que le chemin principal se lise droit, sans imbrication. Un levier gross / equity avec un capital nul doit être une ValueError claire, pas une ZeroDivisionError venue du fond, et un portefeuille vide doit donner 0.0 plutôt qu'un plantage. Les commentaires doivent expliquer le pourquoi, que le code ne peut pas dire (# la bourse arrondit au centime), pas répéter le quoi que le code montre déjà. Les docstrings disent ce qu'une fonction prend et rend. Enfin, préférez les fonctions pures, qui ne dépendent que de leurs arguments et n'ont pas d'effet de bord : ce sont celles qu'on teste avec quelques entrées.
# A guard clause stops bad input at the door and says exactly what is wrong
def leverage(positions, equity):
if equity <= 0:
raise ValueError("equity must be positive")
return sum(abs(value) for value in positions.values()) / equity
print(leverage({"AAPL": 60_000.0, "MSFT": -30_000.0}, 45_000.0)) # 2.0
print(leverage({}, 45_000.0)) # 0.0 an empty book is fine
try:
leverage({"AAPL": 60_000.0}, 0.0)
except ValueError as error:
print("rejected:", error) # rejected: equity must be positive# A pure function answers the same way every time; hidden state does not
history = []
def running_notional(price, quantity): # remembers earlier calls: the answer depends on the past
history.append(price * quantity)
return sum(history)
def notional(price, quantity): # depends only on its arguments
return price * quantity
print(running_notional(10.0, 5), running_notional(10.0, 5)) # 50.0 100.0 same call, different answers
print(notional(10.0, 5), notional(10.0, 5)) # 50.0 50.0 always the same- Nommez les choses pour ce qu'elles sont, gardez des fonctions petites et à but unique, et construisez les plus grandes en les appelant.
- Pas de nombres magiques : les limites arrivent en arguments ou en constantes nommées ; calculez une valeur répétée une seule fois.
- Protégez d'abord contre l'entrée invalide avec une erreur claire, commentez le pourquoi et non le quoi, et préférez les fonctions pures testables.
À suivre : les annotations de type et la vérification statique, laisser des outils attraper les erreurs avant l'exécution.
Lab : ce qui est noté
- Netter le portefeuille avec
net_exposure25 pts - Additionner chaque position avec
gross_exposure25 pts - Protéger le dénominateur dans
leverage25 pts - Composer les contrôles dans
risk_flags25 pts