O código é lido muito mais do que escrito
Uma base de código de trading vive anos e é alterada por pessoas que não a escreveram, muitas vezes sob pressão e muitas vezes por você mesmo, seis meses depois. Código limpo é o que se entende rápido e se altera com segurança. Não se trata de regras de estilo, mas de tornar visível uma mudança errada. Uma função mal nomeada, que faz três coisas e esconde um limite dentro de uma fórmula, é onde moram os bugs caros, como um limite de risco que ninguém lembrava estar fixo no código.
# 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?Nomes, funções pequenas, sem números escondidos
Três hábitos fazem a maior parte. Dê nomes às coisas pelo que são: gross_exposure em vez de f, max_leverage em vez de m. Mantenha funções pequenas e de um só propósito: cada uma faz um trabalho que se nomeia em poucas palavras, então se testa sozinha e se reutiliza, e uma função maior se constrói chamando-as. Não enterre números: um limite como 1.5 digitado dentro de uma fórmula é um número mágico que ninguém consegue achar ou mudar com segurança; passe-o como argumento (ou dê-lhe nome de constante). Calcular a exposição bruta uma vez em sua própria função também elimina a expressão repetida.
# 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 limitsProteger as bordas e dizer por quê
Trate primeiro a entrada ruim e retorne ou levante de imediato (uma cláusula de guarda), para que o caminho principal se leia reto, sem aninhamento. Uma alavancagem gross / equity com patrimônio zero deve ser um ValueError claro, não um ZeroDivisionError vindo do fundo, e uma carteira vazia deve dar 0.0 e não uma falha. Comentários devem explicar o porquê, que o código não diz (# a bolsa arredonda ao centavo), não repetir o quê que o código já mostra. Docstrings dizem o que a função recebe e devolve. Por fim, prefira funções puras, que dependem só dos argumentos e não têm efeitos colaterais: são as que se testam com poucas entradas.
# 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- Dê nomes às coisas pelo que são, mantenha funções pequenas e de um só propósito, e construa as maiores chamando-as.
- Sem números mágicos: os limites chegam como argumentos ou constantes nomeadas; calcule uma vez um valor repetido.
- Proteja primeiro contra a entrada ruim com um erro claro, comente o porquê e não o quê, e prefira funções puras que possa testar.
A seguir: anotações de tipo e verificação estática, deixar as ferramentas pegarem erros antes de o código rodar.
Lab: o que é avaliado
- Netear a carteira com
net_exposure25 pts - Somar cada posição com
gross_exposure25 pts - Proteger o denominador em
leverage25 pts - Compor as verificações em
risk_flags25 pts