GitHub で見る
OzFx シンプル戦略
概要
- MetaTrader 4 エキスパート アドバイザ OzFx (フォルダ
MQL/7994) を StockSharp の高レベル API に変換します。
- 加速/減速オシレーター (AC) を確率オシレーターの %K ラインと併用して、ゼロ ライン付近の運動量の反転を検出します。
- 最初のターゲットに到達した後、時間差でテイクプロフィットし、損益分岐点保護を行って 5 つの成行注文を積み重ねるエキスパートの行動を再現します。
取引ロジック
- Awesome Oscillator (5/34) を構築し、その 5 期間 SMA を減算して、以前および現在完了したローソク足の Accelerator Oscillator の値を取得します。
- 確率オシレーター (%K 長さ =
StochasticLength、平滑化 3/3) をサブスクライブし、ローソク足の終値でメインラインを読み取ります。
- 長いセットアップには次のものが必要です。
- 構成された中間レベル (デフォルトは 50) より上の
%K。
- 現在の AC 値は正で、前の値よりも高くなります。
- 以前の AC 値は依然としてゼロを下回っています (勢いがベースラインを超えています)。
- 短いセットアップ では、ルールが逆方向に反映されます。
- 新しいバーにシグナルが表示されると、戦略は 5 つの均等な成行注文を開きます。
- レイヤ 1 ~ 4 は、
TakeProfitPips 倍の間隔でテイクプロフィットを受け取ります。
- レイヤー 5 には利益目標がなく、引き続きこの動きを後追いしています。
- スタックがオープンしている間に反対のセットアップが表示された場合、残りの注文は市場でクローズされ、新しいエントリーの前に戦略をフラットに保ちます。
ポジション管理
- すべてのレイヤーは、
StopLossPips で定義された同じストップロス距離を共有します。
- 最初のテイクプロフィットが約定した後、残りの注文はストップを損益分岐点(エントリー)価格まで絞り込み、元の「modok」ロジックと一致します。
- 保護的出口は、ローソク足の極値が保存されているストップレベルまたはターゲットレベルを突き抜けたときに実行されます。ブローカー側の未決注文は使用されません。
- この戦略では、一度に 1 方向のみが許可され、すべての注文が終了するまで待ってからエントリー ブロック フラグがリセットされます。
パラメーター
| 名前 |
説明 |
デフォルト |
OrderVolume |
5 つの成行注文それぞれのロット サイズ。 |
0.1 |
StopLossPips |
エントリーとストップロスの間の距離 (pips で表されます)。 |
100 |
TakeProfitPips |
連続するテイクプロフィットレベル間の増分(レイヤー1~4)。 |
50 |
StochasticLevel |
確率的 %K 値に適用されるしきい値。 |
50 |
StochasticLength |
確率的 %K 計算のルックバック期間。 |
5 |
CandleType |
ストラテジーで使用されるソース ローソク足シリーズ (デフォルトは 4 時間足ローソク足)。 |
4h time-frame |
実装メモ
- 新しいバーで作業する MT4 エキスパートとの一貫性を保つために、シグナルは完成したローソク足でのみ評価されます。
- Pip 変換は、必要に応じて最小価格ステップを 10 倍することで、3/5 桁の外国為替シンボルに自動的に適応します。
- スタッガードエントリーとエグジットは、レイヤードオブジェクトを介してメモリー内で処理されるため、ストラテジーはポジションの一部を適切にクローズできます。
- リポジトリ ガイドラインの要求に従って、C# コード内のすべてのコメントは英語で書かれています。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class OzFxSimpleStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
private int _cooldownRemaining;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public OzFxSimpleStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 20).SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 80).SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
_cooldownCandles = Param(nameof(CooldownCandles), 100).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = default;
_prevSlow = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var fast = new WeightedMovingAverage { Length = FastPeriod };
var slow = new WeightedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevFast = fast;
_prevSlow = slow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class oz_fx_simple_strategy(Strategy):
def __init__(self):
super(oz_fx_simple_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20).SetDisplay("Fast WMA", "Fast WMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 80).SetDisplay("Slow WMA", "Slow WMA period", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 100).SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(oz_fx_simple_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_slow = 0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(oz_fx_simple_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_slow = 0
self._has_prev = False
self._cooldown_remaining = 0
fast = WeightedMovingAverage()
fast.Length = self._fast_period.Value
slow = WeightedMovingAverage()
slow.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast, slow, self.OnProcess).Start()
def OnProcess(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
if not self._has_prev:
self._prev_fast = fast
self._prev_slow = slow
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_fast = fast
self._prev_slow = slow
return
if self._prev_fast <= self._prev_slow and fast > slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self._cooldown_candles.Value
elif self._prev_fast >= self._prev_slow and fast < slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self._cooldown_candles.Value
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return oz_fx_simple_strategy()