0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Python] XAUUSD のロットサイズ計算を関数化する——pip 定義の違いを吸収する実装と6つのテスト

0
Posted at

結論

XAUUSD のロットサイズ計算は、pip を入力に取らない設計にすると壊れません。価格幅とオンス数だけで計算し、pip は最後に表示用へ変換します。この記事では 3 つの関数と 6 つのテストで、ブローカーごとに 10 倍違う pip 定義を吸収する実装を書きます。

用語の前提

  • ロット(lot): 取引量の単位。XAUUSD の標準ロットは 100 トロイオンス(= 100 oz)。
  • pip: 値動きの最小単位を表す慣習的な単位。ただし XAUUSD では業界統一の定義がなく、0.10 ドルと 0.01 ドルの 2 系統が併存します。
  • 契約サイズ(contract size): 1 ロットが何オンスかを表す数値。XAUUSD は 100。
  • 損切り幅(stop distance): エントリー価格と損切り価格の差。価格(ドル)で表します。

この 4 つのうち、ブローカー間でぶれるのは pip の定義だけです。ロットと契約サイズは共通、損切り幅は自分で決める値です。したがって、pip を計算の入力から外せば、ぶれる要素は消えます。

3 つの関数

CONTRACT_SIZE = 100.0  # XAUUSD: 1 lot = 100 oz

def pip_size_of(definition: str) -> float:
    """'0.10' or '0.01' -> pip の価格幅"""
    table = {"0.10": 0.10, "0.01": 0.01}
    if definition not in table:
        raise ValueError(f"unknown pip definition: {definition}")
    return table[definition]

def pip_value(lots: float, pip_size: float) -> float:
    """1 pip 動いたときの損益(USD)"""
    if lots <= 0:
        raise ValueError("lots must be positive")
    return lots * CONTRACT_SIZE * pip_size

def position_size(equity: float, risk_pct: float,
                  stop_distance: float) -> float:
    """risk_pct を % で受け取り、ロットを返す"""
    if equity <= 0 or stop_distance <= 0:
        raise ValueError("equity and stop_distance must be positive")
    if not 0 < risk_pct <= 5:
        raise ValueError("risk_pct out of sane range")
    risk_money = equity * risk_pct / 100.0
    return risk_money / (stop_distance * CONTRACT_SIZE)

position_size() に pip が登場しない点が設計の核心です。損切り幅は価格で受け取るので、ブローカーの pip 定義が変わっても同じ結果を返します。

def break_even_distance(lots: float, spread: float,
                        commission_round_trip: float) -> float:
    """往復コストを回収するのに必要な価格移動(USD)"""
    ounces = lots * CONTRACT_SIZE
    if ounces <= 0:
        raise ValueError("lots must be positive")
    return (ounces * spread + commission_round_trip) / ounces

break_even_distance() はロットに比例する項と比例しない項が混ざるので、オンスで割って価格に戻します。0.1 ロット・スプレッド 0.20・往復手数料 0.60 なら、(10*0.20 + 0.60)/10 = 0.26 ドル。

テストで仕様を固定する

import pytest

def test_pip_value_differs_by_definition():
    assert pip_value(1.0, 0.10) == pytest.approx(10.0)
    assert pip_value(1.0, 0.01) == pytest.approx(1.0)

def test_pip_value_scales_with_lots():
    assert pip_value(0.1, 0.10) == pytest.approx(1.0)

def test_sizing_is_definition_independent():
    """pip 定義を変えてもロットは同じ —— これが設計の目的"""
    size = position_size(1000, 1.0, 5.0)
    assert size == pytest.approx(0.02)

def test_sizing_rejects_zero_stop():
    with pytest.raises(ValueError):
        position_size(1000, 1.0, 0.0)

def test_break_even_matches_hand_calculation():
    assert break_even_distance(0.1, 0.20, 0.60) == pytest.approx(0.26)

def test_break_even_is_lot_independent():
    """同じスプレッドと手数料率なら、必要値幅はロットに依存しない"""
    assert break_even_distance(0.1, 0.20, 0.60) == pytest.approx(0.26)
    assert break_even_distance(1.0, 0.20, 6.00) == pytest.approx(0.26)

test_sizing_is_definition_independent が要点です。このテストが通る限り、pip 定義の違いはサイジングに影響しません。逆にここが落ちる実装を書いてしまったら、どこかで pip を計算に混ぜています。

つまずいたところ

1. break_even_distance() を pip で返そうとした。 最初は「26 pips」と返す実装にしましたが、pip 定義が 0.01 のブローカーでは 2600 pips になり、表示が破綻します。価格(ドル)で返し、表示時にだけ pip へ変換する形に直しました。

2. risk_pct を小数で受けてしまった。 0.01 を 1% のつもりで渡すと 0.01% として計算され、ロットが 100 分の 1 になります。関数の docstring に「% で受け取る」と明記し、0 < risk_pct <= 5 の範囲チェックを入れて弾くようにしました。単位の取り違えは例外で落とす方が、静かに間違うより安全です。

3. 最小ロットの丸めを関数に入れなかった。 0.014 が必要でも 0.01 しか出せないブローカーがあります。丸めは表示層の責務にして、計算層は丸めないまま返す設計にしました。計算層で丸めると、テストが実際のリスクと一致しなくなります。

FAQ

Q. pip 定義が 0.01 のブローカーで「50 pips」と言われたら? 価格に直すと 0.50 ドルです。0.10 定義なら 5.00 ドル。受け渡しは必ず価格で行い、pip は表示だけに使うのが安全です。

Q. レバレッジは計算に入れないのですか? 入りません。レバレッジは必要証拠金を決めるだけで、損切り幅とリスク額から算出されるロットには影響しません。強平距離の計算には必要なので、別関数に分けています。

Q. 契約サイズが 100 でないブローカーは? CONTRACT_SIZE を定数ではなく引数にしてください。ただし XAUUSD はほぼ全ブローカーで 100 oz なので、定数のままでも実害は小さいです。

まとめ

  • 計算層に pip を入れない。価格とオンス数だけで完結させる。
  • pip は表示層でだけ使う。定義(0.10 / 0.01)を必ず併記する。
  • 単位の取り違えは例外で落とす。静かに間違う実装を作らない。
  • 丸めは表示層。計算層は生の値を返す。

この 4 点を守ると、ブローカーを跨いだ検証が壊れなくなります。

本記事は教育目的であり、投資助言ではありません。契約サイズ・pip 定義・スプレッド・手数料はブローカーごとに異なるため、必ずご自身の契約仕様をご確認ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?