GitHub で見る
通貨強度 v1.1戦略
概要
通貨強度 v1.1戦略はMetaTraderのエキスパートアドバイザーCurrency Strength v1.1を再現します。26の流動性の高いFXペアの日次パーセンテージ変化を使用して、8つの主要通貨(USD、EUR、JPY、CAD、AUD、NZD、GBP、CHF)の相対的な強さを測定します。2つの通貨の強さが設定可能な閾値を超えて乖離するたびに、戦略は対応する通貨ペアに強い通貨の方向でポジションを開きます。
市場とデータ
- インストゥルメントユニバース: 26の主要およびクロスFXペア(USDJPY、USDCAD、AUDUSD、USDCHF、GBPUSD、EURUSD、NZDUSD、EURJPY、EURCAD、EURGBP、EURCHF、EURAUD、EURNZD、AUDNZD、AUDCAD、AUDCHF、AUDJPY、CHFJPY、GBPCHF、GBPAUD、GBPCAD、GBPJPY、CADJPY、NZDJPY、GBPNZD、CADCHF)。
- データ頻度: 日足ローソク足(D1)。一貫した計算を維持するために、完成したローソク足のみが処理されます。
- 必須フィールド: 各ローソク足の始値、高値、安値、終値。
通貨強度の計算
各ペアの日次パーセンテージ変化は以下のように計算されます:
(change) = (Close − Open) / Open × 100
これらのペア固有の変化はその後、通貨強度インデックスに結合されます:
- EUR強度 = EURJPY、EURCAD、EURGBP、EURCHF、EURAUD、EURUSD、EURNZDの平均
- USD強度 = USDJPY、USDCAD、–AUDUSD、USDCHF、–GBPUSD、–EURUSD、–NZDUSDの平均
- JPY強度 = USDJPY、EURJPY、AUDJPY、CHFJPY、GBPJPY、CADJPY、NZDJPYの負の平均
- CAD強度 = CADCHF、CADJPY、–GBPCAD、–AUDCAD、–EURCAD、–USDCADの平均
- AUD強度 = AUDUSD、AUDNZD、AUDCAD、AUDCHF、AUDJPY、–EURAUD、–GBPAUDの平均
- NZD強度 = NZDUSD、NZDJPY、–EURNZD、–AUDNZD、–GBPNZDの平均
- GBP強度 = GBPUSD、–EURGBP、GBPCHF、GBPAUD、GBPCAD、GBPJPY、GBPNZDの平均
- CHF強度 = CHFJPY、–USDCHF、–EURCHF、–AUDCHF、–GBPCHF、–CADCHFの平均
各平均は元のエキスパートアドバイザーと同じ数のコンポーネントを使用して、重み付けスキームを保持します。
トレードロジック
- 26のすべてのペアが新しい完成した日次ローソク足を報告した後、強度が再計算されます。
- 各ペアに対して、戦略は2つの関連する通貨強度を比較します。絶対差が
DifferenceThresholdパラメーターを超えると、トレードシグナルが生成されます。
- シグナルの方向は強い通貨に従います:
- 基準通貨強度 > 決済通貨強度 → ペアを買う。
- 基準通貨強度 < 決済通貨強度 → ペアを売る。
- 取引はペアの日足ローソク足がシグナルと一致する場合(買いでは終値が始値より上、売りでは終値が始値より下)にのみ許可され、元のEAのトレンドフィルターを反映します。
- 既存のネットポジションが尊重されます。反対のポジションがオープンな状態で反転シグナルが現れると、戦略は現在のポジションを閉じ、1つの成行注文で新しい方向に切り替えます。
TradeOncePerDayが有効な場合、各ペアは取引日ごとに最大1回のロングエントリーと最大1回のショートエントリーが可能です。
リスク管理と決済
- オプションの
UseSlTpフラグは各ペアの日次ローソク足で実行されるストップロスとテイクプロフィットロジックを有効にします。距離はpipで定義されます(StopLossPips、TakeProfitPips)。
- 保護ロジックは最新のローソク足の日次高値/安値を評価します。これらの極値がそれぞれの目標に達すると、ポジションは次の評価ステップで市場価格で閉じられます。
- SL/TPなしでは、反対のシグナルが反転を強制するか戦略が手動で停止されるまでポジションはオープンのままで、ソースEAの動作を反映します。
戦略パラメーター
| パラメーター |
説明 |
CandleType |
ローソク足の時間軸(デフォルト:日足)。 |
DifferenceThreshold |
トレードをトリガーするために必要な最小強度差(パーセンテージポイント)。 |
TradeOncePerDay |
trueの場合、各ペアを1日1回のロングエントリーと1回のショートエントリーに制限します。 |
UseSlTp |
ストップロスとテイクプロフィットレベルの日次評価を有効にします。 |
TakeProfitPips |
pipで測定したテイクプロフィット距離。 |
StopLossPips |
pipで測定したストップロス距離。 |
| ペアパラメーター |
26のFXペアの個別Security入力。戦略を開始する前に各々を割り当てる必要があります。 |
Volume |
トレードサイズを定義する基底クラスプロパティ(デフォルト0.01ロット)。 |
実装メモ
- 戦略は高レベルのローソク足サブスクリプションAPI(
SubscribeCandles)を使用して各ペアに個別にサブスクライブします。
- ローソク足の処理は不完全なローソク足を厳密に無視し、StockSharpの変換ガイドラインを満たします。
- 強度計算とシグナル生成は、すべてのペアが同じ取引日を報告したときにのみ実行され、同期された通貨バスケットを保証します。
- 内部辞書は方向ごとの最後の取引日を追跡し、保護的な決済のためにエントリー情報を保存します。
使用のヒント
- 戦略を開始する前にすべての26のインストゥルメントを割り当ててください。入力が欠けると部分的な計算を防ぐために例外がスローされます。
- データプロバイダーが設定されたすべてのペアに日次ローソク足を供給することを確認し、通貨強度を同期させてください。
DifferenceThresholdを調整してシグナル頻度を制御します。小さい閾値は頻繁なトレードにつながりますが、反転も多くなります。
- pipベースのストップをブローカーの見積もり精度に合わせて調整してください。デフォルトはフラクショナルpip価格設定を想定しています。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades based on percentage change momentum of candles.
/// Simplified from the original multi-pair currency strength approach to single-security.
/// </summary>
public class CurrencyStrengthV11Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _differenceThreshold;
private decimal? _prevChange;
private decimal? _prevMomentum;
private decimal _entryPrice;
private DateTimeOffset? _lastTradeTime;
/// <summary>
/// Candle type for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Minimum percentage change to trigger trades.
/// </summary>
public decimal DifferenceThreshold
{
get => _differenceThreshold.Value;
set => _differenceThreshold.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public CurrencyStrengthV11Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strength calculation", "General");
_differenceThreshold = Param(nameof(DifferenceThreshold), 0.2m)
.SetDisplay("Threshold", "Minimum percentage change to trigger trade", "Parameters")
.SetOptimize(0.05m, 1m, 0.05m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevChange = null;
_prevMomentum = null;
_entryPrice = 0m;
_lastTradeTime = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
var change = candle.OpenPrice != 0m
? (candle.ClosePrice - candle.OpenPrice) / candle.OpenPrice * 100m
: 0m;
if (_prevChange == null)
{
_prevChange = change;
return;
}
var momentum = change - _prevChange.Value;
var cooldownPassed = _lastTradeTime is null || candle.CloseTime - _lastTradeTime >= TimeSpan.FromHours(24);
var longSignal = _prevMomentum is decimal prevMomentum && prevMomentum <= DifferenceThreshold && momentum > DifferenceThreshold;
var shortSignal = _prevMomentum is decimal prevMomentum2 && prevMomentum2 >= -DifferenceThreshold && momentum < -DifferenceThreshold;
if (cooldownPassed && longSignal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume > 0m ? Volume : 1m);
_entryPrice = candle.ClosePrice;
_lastTradeTime = candle.CloseTime;
}
else if (cooldownPassed && shortSignal && Position >= 0)
{
if (Position > 0)
SellMarket(Position);
SellMarket(Volume > 0m ? Volume : 1m);
_entryPrice = candle.ClosePrice;
_lastTradeTime = candle.CloseTime;
}
_prevChange = change;
_prevMomentum = momentum;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class currency_strength_v11_strategy(Strategy):
def __init__(self):
super(currency_strength_v11_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._difference_threshold = self.Param("DifferenceThreshold", 0.2)
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def DifferenceThreshold(self):
return self._difference_threshold.Value
@DifferenceThreshold.setter
def DifferenceThreshold(self, value):
self._difference_threshold.Value = value
def OnStarted2(self, time):
super(currency_strength_v11_strategy, self).OnStarted2(time)
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
open_price = float(candle.OpenPrice)
close = float(candle.ClosePrice)
change = (close - open_price) / open_price * 100.0 if open_price != 0.0 else 0.0
if self._prev_change is None:
self._prev_change = change
return
momentum = change - self._prev_change
threshold = float(self.DifferenceThreshold)
long_signal = (self._prev_momentum is not None and
self._prev_momentum <= threshold and
momentum > threshold)
short_signal = (self._prev_momentum is not None and
self._prev_momentum >= -threshold and
momentum < -threshold)
cooldown_passed = (self._last_trade_time is None or
candle.CloseTime - self._last_trade_time >= TimeSpan.FromHours(24))
if cooldown_passed and long_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
vol = float(self.Volume) if float(self.Volume) > 0 else 1.0
self.BuyMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._last_trade_time = candle.CloseTime
elif cooldown_passed and short_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
vol = float(self.Volume) if float(self.Volume) > 0 else 1.0
self.SellMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._last_trade_time = candle.CloseTime
self._prev_change = change
self._prev_momentum = momentum
def OnReseted(self):
super(currency_strength_v11_strategy, self).OnReseted()
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
def CreateClone(self):
return currency_strength_v11_strategy()