「月利37,000円を達成するには、9通貨ペアのロットサイズをどう配分すべきか?」——この問いに対して、勝率・RR比・トレード頻度が異なる9ペアの最適ロット配分を、scipyの2段階最適化で自動計算するシステムを構築した。39テスト全PASS。
単純に「全ペア均等配分」ではなく、各ペアの期待リターンとリスクを考慮した配分を行うことで、同じリスク量でも月利目標の達成確率を最大化する。
なぜ均等配分ではダメなのか
9通貨ペアの特性の違い
FXエンジンで運用中の9ペアは、勝率・RR比・トレード頻度がそれぞれ異なる。
EUR_JPY: 勝率 65%, RR比 1.2, 月平均 8トレード → 高頻度・安定型
GBP_JPY: 勝率 55%, RR比 1.8, 月平均 5トレード → 中頻度・高RR型
AUD_JPY: 勝率 60%, RR比 1.0, 月平均 6トレード → 中頻度・安定型
CHF_JPY: 勝率 45%, RR比 0.8, 月平均 4トレード → 低頻度・低期待値
CHF_JPYのようにバックテストで期待値がマイナスのペアに、EUR_JPYと同じロットを割り当てるのは非合理的だ。
均等配分の問題
均等配分: 全ペア 10,000通貨
→ EUR_JPY(高期待値): 10,000通貨で利益を出す
→ CHF_JPY(低期待値): 10,000通貨で損失を出す
→ 結果: 高期待値ペアの利益を低期待値ペアが食い潰す
最適配分では、期待値の高いペアにロットを集中させ、低いペアは縮小(または除外)する。
2段階最適化の設計
なぜ2段階か
単一の最適化問題として定式化すると、変数が多すぎて解が不安定になる(9ペア × ロットサイズ = 9変数の非線形最適化)。
そこで問題を2段階に分解した。
Stage 1: 各ペアの「最適リスク配分比率」を決定
→ 入力: 各ペアの勝率・RR比・相関行列
→ 出力: リスク配分比率(例: EUR_JPY 25%, GBP_JPY 20%, ...)
Stage 2: 月利目標から「必要なロットサイズ」を逆算
→ 入力: リスク配分比率 + 月利目標 + 口座残高
→ 出力: 各ペアの具体的なロットサイズ
Stage 1: リスク配分比率の最適化
各ペアに割り当てるリスク比率を、シャープ比の最大化を目的関数として最適化する。
from scipy.optimize import minimize
def neg_sharpe_ratio(weights, expected_returns, cov_matrix):
"""ポートフォリオのシャープ比を最大化(負の値を最小化)"""
portfolio_return = weights @ expected_returns
portfolio_vol = (weights @ cov_matrix @ weights) ** 0.5
if portfolio_vol == 0:
return 0
return -portfolio_return / portfolio_vol
# 制約条件
constraints = [
{"type": "eq", "fun": lambda w: w.sum() - 1.0}, # 合計100%
]
bounds = [(0.0, 0.4)] * n_pairs # 各ペア最大40%まで(集中リスク回避)
result = minimize(
neg_sharpe_ratio,
x0=np.ones(n_pairs) / n_pairs, # 初期値: 均等配分
args=(expected_returns, cov_matrix),
method="SLSQP",
bounds=bounds,
constraints=constraints,
)
optimal_weights = result.x
SLSQP(Sequential Least Squares Programming)は、等式/不等式制約付きの非線形最適化に適したアルゴリズムだ。
上限制約(最大40%)の意味
1ペアに最大40%までという制約を設けることで、「EUR_JPYに80%集中」のような極端な配分を防ぐ。集中投資は期待リターンが高いが、そのペアが不調になったときのダメージも大きい。
Stage 2: ロットサイズの逆算
Stage 1で得た比率を、月利目標と口座残高から具体的なロットサイズに変換する。
def calculate_lot_sizes(
weights: np.ndarray, # Stage 1の最適配分比率
monthly_target: float, # 月利目標(円)
equity: float, # 口座残高(円)
expected_monthly_pips: dict, # 各ペアの月間期待pips
pip_values: dict, # 各ペアの1pip価値
) -> dict:
lot_sizes = {}
total_risk_budget = equity * 0.02 # 口座の2%をリスク予算
for i, pair in enumerate(pairs):
pair_risk = total_risk_budget * weights[i]
# ロットサイズ = リスク予算 / (SL幅 × pip価値)
lot = pair_risk / (sl_pips[pair] * pip_values[pair])
# GMO Coin制約: 1,000通貨単位
lot_sizes[pair] = max(1000, round(lot / 1000) * 1000)
return lot_sizes
相関行列の活用
なぜ相関を考慮するか
EUR_JPYとGBP_JPYは相関が高い(ともにクロス円で、米ドルの動きに影響を受ける)。この2ペアに大きなロットを配分すると、実質的に「同じ方向に2倍のポジションを持っている」のと変わらない。
相関行列を最適化に組み込むことで、相関の低いペアに分散投資する配分が自然に得られる。
# 相関行列の例(バックテストの日次リターンから計算)
correlation_matrix = {
"EUR_JPY-GBP_JPY": 0.78, # 高相関
"EUR_JPY-AUD_JPY": 0.55, # 中相関
"EUR_JPY-EUR_USD": 0.42, # 低相関
}
高相関のペア同士は「片方にロットを集中し、もう片方を縮小する」方が、ポートフォリオ全体のリスク調整後リターンが改善する。
月利目標の逆算
具体例
月利目標37,000円、口座残高545,888円の場合:
月利目標率: 37,000 / 545,888 = 6.78%
Stage 1の最適配分:
EUR_JPY: 28%
GBP_JPY: 22%
AUD_JPY: 15%
CAD_JPY: 12%
NZD_JPY: 8%
EUR_USD: 8%
GBP_USD: 5%
AUD_USD: 2%
CHF_JPY: 0%(期待値マイナスのため除外)
Stage 2の逆算結果:
EUR_JPY: 14,000通貨
GBP_JPY: 11,000通貨
AUD_JPY: 8,000通貨
...
CHF_JPY: 0通貨(配分なし)
CHF_JPYはバックテストで期待値マイナスのため、最適化の結果として配分0%になる。これは数学的に正しい判断だ。
データ蓄積の重要性
このシステムの精度は「入力データの質」に依存する。バックテストの勝率・RR比は過去のデータに基づくため、データ蓄積期間が短いと推定誤差が大きい。
最低2ヶ月のDry-Runデータ蓄積後に最適化を実行するルールを設けた。2ヶ月は統計的に有意な結論を出すには短いが、「全くデータなしで均等配分するよりはマシ」という実用的な判断だ。
学んだこと
1. 均等配分は「分からないときの最善策」であり「最適」ではない
各ペアの特性が分かっているなら、それを反映した配分にすべきだ。均等配分は「情報がないとき」のベースラインとしては正しいが、2ヶ月以上の実績データがあるなら最適化の余地がある。
2. 2段階分解で最適化が安定する
9変数の一括最適化は局所解に陥りやすい。「比率の決定」と「ロットへの変換」を分離することで、各ステージの問題がシンプルになり、解の安定性が向上した。
3. 上限制約は数学よりも実務のための安全装置
数学的には「EUR_JPYに100%集中」が最適解になりうるが、実務的にはそのペアが不調になったときに壊滅的なダメージを受ける。最大40%の上限制約は、過学習(バックテストデータへの過剰適合)に対する安全装置だ。
まとめ
月次目標逆算型ロット最適化の設計で重要なのは以下の3点だ。
- 2段階最適化: Stage 1でリスク配分比率を決定、Stage 2で月利目標からロットを逆算。問題を分解して安定化
- 相関行列の考慮: 高相関ペアへの重複投資を回避し、分散効果を最大化
- 上限制約(最大40%): 1ペアへの過剰集中を防ぐ安全装置。過学習対策
「月利X万円を達成するために、各ペアに何ロット配分すべきか」という問いに対して、感覚ではなく数学的に回答するシステムだ。