Code wird viel öfter gelesen als geschrieben
Eine Trading-Codebasis lebt Jahre und wird von Leuten geändert, die sie nicht geschrieben haben, oft unter Druck und oft von Ihnen selbst, sechs Monate später. Sauberer Code lässt sich schnell verstehen und sicher ändern. Es geht nicht um Stilregeln, sondern darum, eine falsche Änderung sichtbar zu machen. Eine schlecht benannte Funktion, die drei Dinge tut und ein Limit in einer Formel versteckt, ist der Ort der teuren Bugs, etwa ein Risikolimit, von dem niemand wusste, dass es fest einprogrammiert war.
# 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?Namen, kleine Funktionen, keine versteckten Zahlen
Drei Gewohnheiten leisten das Meiste. Benennen Sie Dinge nach dem, was sie sind: gross_exposure statt f, max_leverage statt m. Halten Sie Funktionen klein und einzweckig: Jede erledigt eine Aufgabe, die sich in wenigen Worten benennen lässt, ist so einzeln testbar und wiederverwendbar, und eine größere Funktion entsteht durch ihren Aufruf. Vergraben Sie keine Zahlen: Ein Limit wie 1.5 mitten in einer Formel ist eine magische Zahl, die niemand sicher finden oder ändern kann; übergeben Sie es als Argument (oder benennen Sie es als Konstante). Die Brutto-Exposure einmal in einer eigenen Funktion zu berechnen, beseitigt zudem den wiederholten Ausdruck.
# 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 limitsRänder absichern und das Warum sagen
Behandeln Sie die schlechte Eingabe zuerst und geben Sie sofort zurück oder lösen Sie aus (eine Guard Clause), damit sich der Hauptpfad ohne Verschachtelung geradeaus liest. Ein Hebel gross / equity bei Eigenkapital null sollte ein klarer ValueError sein, kein ZeroDivisionError aus der Tiefe, und ein leeres Buch sollte 0.0 ergeben statt abzustürzen. Kommentare sollten das Warum erklären, das der Code nicht sagen kann (# die Börse rundet auf den Cent), nicht das Was wiederholen, das der Code schon zeigt. Docstrings sagen, was eine Funktion nimmt und liefert. Bevorzugen Sie schließlich reine Funktionen, die nur von ihren Argumenten abhängen und keine Nebenwirkungen haben: Sie lassen sich mit wenigen Eingaben testen.
# 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- Benennen Sie Dinge nach dem, was sie sind, halten Sie Funktionen klein und einzweckig und bauen Sie größere durch deren Aufruf.
- Keine magischen Zahlen: Limits kommen als Argumente oder benannte Konstanten; berechnen Sie einen wiederholten Wert nur einmal.
- Sichern Sie schlechte Eingaben zuerst mit einem klaren Fehler ab, kommentieren Sie das Warum statt des Was und bevorzugen Sie testbare reine Funktionen.
Als Nächstes: Typannotationen und statische Prüfung, Werkzeuge Fehler finden lassen, bevor der Code läuft.
Lab: Was bewertet wird
- Mit
net_exposuredas Buch saldieren25 Pkt. - Mit
gross_exposurejede Position addieren25 Pkt. - Den Nenner in
leverageabsichern25 Pkt. - Die Prüfungen in
risk_flagszusammensetzen25 Pkt.