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?

FX自動売買の実装で詰まった5つの罠(OANDA API編)

0
Posted at

Alpaca で株・暗号資産の自動売買を動かした後、OANDA で FX に対応したときに想定外のハマりどころが連発しました。FX 特有の罠を5つ紹介します。


罠1:JPY建て口座の残高を USD に換算しないとポジションサイズがおかしくなる

OANDA 口座が JPY 建ての場合、/accounts/{id}/summary が返す balance や NAV は日本円です。そのまま USD 計算のサイジング式に入れると、為替レートが1ドル=150円の場合に 150倍の発注量 になります。

// account-tools.ts
const summary = await oanda.getAccountSummary();
let navUsd = summary.nav;

// JPY 建て口座の場合は USD に換算してからサイザーに渡す
if (summary.currency === "JPY") {
  const tick = await oanda.getLatestPrice("USD_JPY");
  if (tick && tick.price > 0) {
    navUsd = summary.nav / tick.price;  // 例: 1,500,000 / 150 = 10,000 USD
  }
}

さらに JPY 建てペア(USD_JPY, GBP_JPY)は SL の「1 pip 価値」が USD 換算で異なります。厳密には1 unit あたりの pip 価値は 1 pip / entry_rate USD になります。そのため JPY ペアのサイジングは保守的(小さめ)になります。


罠2:USD_base ペアの SL 距離は外貨建てになる

USD_JPY や USD_CAD のように USD が base(左)の場合、|entry - SL| の単位が quote 通貨(JPY/CAD) になります。

// position-sizer.ts
const quoteConv = isUsdBase ? entry : 1;
// USD_JPY: entry ≈ 150, SL距離 0.5円 → USD換算 = 0.5 / 150 = 0.00333 USD/unit
const riskPerUnitUSD = riskPerShare / quoteConv;
quantity = Math.floor(riskTarget / riskPerUnitUSD);

EUR_USD のような quote-USD ペアならこの換算は不要(quoteConv = 1)ですが、USD_JPY 系は必ず換算が必要です。これを忘れると発注量が正しくなりません。


罠3:OANDA の SL/TP はネイティブ管理で、ソフトウェア監視が不要な代わりに決済検知が難しい

Alpaca の暗号資産では SL/TP をソフトウェアで監視してクローズします。OANDA は stopLossOnFill / takeProfitOnFill を注文時に渡せばブローカー側でネイティブ管理してくれます。

// OANDA の注文(SL/TP はネイティブ)
{
  "order": {
    "type": "MARKET",
    "instrument": "GBP_USD",
    "units": "10000",
    "stopLossOnFill": { "price": "1.2450" },
    "takeProfitOnFill": { "price": "1.2650" }
  }
}

ただし問題があります。ブローカーが bar close 前のティックで SL/TP を執行した場合、バーが閉じたときには価格が SL/TP を越えていないように見える ことがあります。

SL = 1.2450
15:00 バーの動き: 1.2460 → 1.2449(SL ヒット)→ 1.2455(バー終値)
→ バー終値 1.2455 > SL 1.2450 → ソフトウェア判定: まだオープン!
→ でも実際はブローカー側でクローズ済み

解決策:OANDA の closed trades API で同期する

// hermes.ts
private async syncOandaFxOutcomes(): Promise<void> {
  const pendingFx = this.memory.getPendingTrades()
    .filter(e => e.type === "real" && e.symbol.includes("_"));

  for (const entry of pendingFx) {
    // OANDA の決済済みトレードと照合
    const closed = await oanda.getClosedTrades(entry.symbol, entry.timestamp);
    if (closed.length) {
      const outcome = closed[0].realizedPL >= 0 ? "WIN" : "LOSS";
      this.memory.updateOutcome(entry.id, outcome, pnlPct);
    }
  }
}

毎サイクルの冒頭で OANDA の closed trades API を叩いて、メモリ側の記録を同期します。


罠4:OANDA の REST API ページネーション

/trades エンドポイントで from + to + count を同時に指定すると API がエラーを返します。

// ❌ これはエラーになる
const params = { from: startTime, to: endTime, count: 50 };

// ✅ from + count のみ(to を除く)
const params = { from: startTime, count: 50 };

OANDA の API ドキュメントには書いてありますが見落としやすいです。from+to+count の組み合わせは拒否されます。


罠5:GBP ペアのレバレッジ上限が他ペアと異なる

OANDA のペーパー口座では GBP_USD / GBP_JPY は最大レバレッジが 20x に制限されていますが、EUR_USD は 25x、USD_JPY は 30x など異なります。

一律で最大レバレッジを設定すると GBP ペアで上限を超えた発注をしてしまいます。

// ペア別レバレッジキャップを strategy-params.json で管理
{
  "GBP_USD": {
    "leverageCap": 20
  },
  "EUR_USD": {
    "leverageCap": 25
  }
}

// position-sizer.ts
const effectiveLeverage = leverageOverride ?? this.maxLeverage;
const maxByLeverage = Math.floor(equity * effectiveLeverage / notionalPerUnit);
quantity = Math.min(quantity, maxByLeverage);

FX と株・暗号資産の主な違いまとめ

項目 株(Alpaca) 暗号資産(Alpaca) FX(OANDA)
SL/TP 管理 ブローカー(bracket) ソフトウェア ブローカー(ネイティブ)
決済検知 bracket が自動クローズ SL/TP チェックして close_position closed trades API で同期
発注単位 株数(整数) 小数可(0.0001 まで) units(1,000〜100,000)
口座通貨換算 不要(USD 建て) 不要 JPY 建て口座は要換算
セッション 平日 9:30-16:00 ET 24/7 平日 24h(月〜金 UTC)

FX は「執行はブローカーに任せられる」という利点がある一方、口座通貨・leverage・pip 換算など FX 固有の複雑さがあります。特に JPY 建て口座 × JPY ペアの組み合わせは最も注意が必要でした。

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?