取引は無料ではない
無料で売買するバックテストはどんな戦略も過大評価し、売買が多いほど嘘が大きくなります。実際のコストは、ブローカーの手数料、取引所や規制の手数料、そして何より毎回またぐビッド・アスク・スプレッドです。通常は売買代金のベーシスポイントで表し、1bp は0.01%です。片道5bpの往復は10bpかかり、1日3bpしか稼げない戦略は、毎日ポジションを反転させるとこれに耐えられません。
def trade_cost(notional, bps):
return abs(notional) * bps / 10000
print(trade_cost(25000, 5)) # 12.5 buying 25,000 at 5 bps
print(trade_cost(-25000, 5)) # 12.5 selling costs the same
print(trade_cost(1_000_000, 1)) # 100.0 one basis point on a million回転率:どれだけ売買するか
コストはポジションの大きさではなく、その変化量で決まります。ポジションを口座に対する割合で表すと、1日の回転率は前日のポジションからの変化の絶対値 abs(position[t] - position[t - 1]) で、0日目は無ポジションから始めます。ロングを持ち続けるのは最初の購入以外コストがかからず、ロングからショートへ反転すると口座を2回分売買します。1年で合計した回転率は、どの戦略でも最初に見る数です。
import numpy as np
positions = np.array([1.0, 1.0, -1.0, -1.0, 0.0]) # long, long, flip to short, short, flat
turnover = np.abs(np.diff(positions, prepend=0.0)) # day 0 starts from a flat position
print(turnover) # [1. 0. 2. 0. 1.]
print(turnover.sum()) # 4.0 accounts traded over the five daysネットリターンと損益分岐コスト
その日のコストは口座に対する割合で turnover * bps / 10000 となり、ネットリターンはグロスのリターンからそれを引いたものです。便利な1つの数が損益分岐コストで、総利益がちょうどゼロになる売買単位あたりのコスト sum(gross) / sum(turnover) * 10000 です。実際に払うコストと比べてください。損益分岐が2bpの戦略は実質的に死んでいて、40bpの戦略には余裕があります。
# Gross and net returns of a rule that flips often, at 5 bps per unit traded
import numpy as np
rng = np.random.default_rng(11)
positions = np.where(rng.random(250) > 0.5, 1.0, -1.0) # a coin-flip position, flipping often
gross = rng.normal(0.0015, 0.005, size=250) # a strategy with a real daily edge before costs
turnover = np.abs(np.diff(positions, prepend=0.0))
net = gross - turnover * 5 / 10000
print(round(float(gross.sum()), 4), round(float(net.sum()), 4))
print(round(float(turnover.sum()), 1), "accounts traded;", round(float(gross.sum() / turnover.sum() * 10000), 2), "bps break-even")# The break-even cost of a small example
import numpy as np
gross = np.array([0.004, 0.002, 0.003, 0.001, 0.002])
positions = np.array([1.0, 1.0, -1.0, -1.0, 0.0])
turnover = np.abs(np.diff(positions, prepend=0.0))
breakeven = gross.sum() / turnover.sum() * 10000
print(round(float(breakeven), 2)) # 30.0 bps per unit traded
print(abs(round(float((gross - turnover * breakeven / 10000).sum()), 10))) # 0.0: the whole profit is goneコストは手数料だけではない一律のbp課金は最も単純なモデルです。実際のコストは銘柄、時間帯、注文サイズにも左右されます。小型株や寄り付きではスプレッドが広がり、大きな注文は価格を不利に動かします。これは次のトピックの主題です。モデルが何であれ、コストを無視したバックテストは楽観的で、低く見積もったバックテストは危険です。
- コストは売買代金のベーシスポイントで表し、
abs(notional) * bps / 10000。買いも売りも同じ - 回転率は
abs(ポジション変化)(反転は2)。ネット=グロス − 回転率 × bps / 10000 - 損益分岐コスト
sum(gross) / sum(turnover) * 10000は戦略が耐えられるコストの大きさを示す。実際のコストと比べる
次はスリッページです。期待した価格と実際に得た価格の差です。
ラボ:採点される内容
trade_costで1回の取引のコストを求める25点turnoverで売買した量を数える25点net_returnsで請求額を差し引く25点breakeven_bpsで利益を消すコストを求める25点