GitHub で見る
ジグアンドザグメス戦略
概要
ZigAndZagScalpelStrategy は、MetaTrader 4 "ZigAndZag" ツールキット (フォルダー 8304) の StockSharp ポートです。
オリジナルのパッケージは、カスタムインジケーターとエキスパートアドバイザーを組み合わせています。 2 つの ZigZag ウィンドウが使用されます。
- KeelOver – 支配的なトレンドをマークするロング ルックバック スイング検出器。
- スラローム – 実行可能なブレイクアウトを定義する短いルックバック スイング検出器。
長期ジグザグが上向きに反転すると、戦略は次のスラローム安値を探し、価格を待ちます。
そのピボットの上に構成可能な数のポイントを上昇させます。買い成行注文は、
ブレークアウト距離が満たされています。対称ルールは、KeelOver トレンドが転換したときにショートポジションをオープンします
下がると、スラロームは最高値を更新し、価格はそれを下回ります。オプションでポジションをクローズすることができます
反対のスラロームピボットが確認されるとすぐに、インジケーターのリミットアローの削除を模倣します。
この実装により、エキスパートアドバイザーによる毎日の取引リミッターが維持されます。設定可能な数値のみ
取引日ごとに許可される取引の数は、午前 0 時 (為替時間) に自動的にリセットされます。これ
元のコードから「新しい日」フラグを再現します。
仕組み
CandleType によって定義されたプライマリ キャンドル ストリームを購読します。
- 2 つの
ZigZagIndicator インスタンスをフィードします。
- トレンド検出器の深さ =
KeelOverLength。
- 深さ =
SlalomLength (エントリーシグナルの場合)。
- 最新の KeelOver ピボットを追跡して、トレンドが上昇しているかどうかを判断します (最後のピボットは安値です)。
またはダウン(最後のピボットはハイ)。
- スラローム インジケーターが新しいピボットを発行したら、その方向にブレイクアウトを準備します。
- 加重価格
(5×Close + 2×Open + High + Low) / 9 を計算します。価格が以上に変動した場合
トレンドがサポートしている間、ピボットから離れた BreakoutDistancePoints (価格単位に変換)
動き、成行注文を実行します。
- グローバルトレンドが反転するか、反対のスラロームピボットが現れたら、既存のポジションを閉じます。
CloseOnOppositePivot が有効になっています。
- 暦日が変わるたびに、日次取引カウンターをリセットします。
パラメータ DeviationPoints と Backstep は両方の ZigZag インスタンス間で共有されるため、
スイング構造は MetaTrader インジケーター バッファーと一致します。
パラメーター
| 名前 |
デフォルト |
説明 |
CandleType |
15m |
両方の ZigZag ラダーを構築するために使用される主な時間枠。 |
KeelOverLength |
55 |
トレンドを定義する長期的なジグザグ ルックバック (オリジナルの KeelOver)。 |
SlalomLength |
17 |
エントリに使用される短期 ZigZag ルックバック (オリジナル Slalom)。 |
DeviationPoints |
5 |
新しい ZigZag ピボットが確認されるまでの最小スイング サイズ (ポイント単位)。 |
Backstep |
3 |
連続するピボット間に必要なバーの距離。 |
BreakoutDistancePoints |
2 |
注文を発行する前のピボットからの距離 (ポイント単位)。 |
MaxTradesPerDay |
1 |
暦日あたりの最大エントリ数。元の newday フラグをミラーリングします。 |
CloseOnOppositePivot |
true |
スラローム ジグザグが逆のスイングを生成したら、オープン ポジションを閉じます。 |
すべてのポイントベースのパラメータは、Security.PriceStep を使用して価格単位に変換されます。楽器の場合
価格ステップが設定されていない場合、テスト中に戦略を機能し続けるために値 1 が使用されます。
使用上の注意
- この戦略は成行注文 (
BuyMarket / SellMarket) で動作します。独自のリスク ルールを追加する
または、より厳格なリスク管理が必要な場合はストップロスヘルパーが必要です。
- 両方の ZigZag インジケーターが同じローソク足ストリームを共有しているため、選択された
CandleType が
データアダプターでサポートされています。
MaxTradesPerDay = 1 は「1 日あたり 1 回の取引」の動作を再現します。必要に応じて値を増やします
同じセッション中の複数のエントリ。
CloseOnOppositePivot = false を設定すると、世界的なトレンドが反転するまでポジションを保持し続けるようになります。
あらゆる短期的なスイングに反応します。
MT4 エキスパートアドバイザーとの違い
- MetaTrader バージョンには保留中の制限矢印が配置されました。 StockSharp ポートはブレークアウトを実行します
即時成行注文は高レベル API 内に収まるようにします。
- リスク管理、ロットサイジング、部分的なクローズは意図的に省略されています。 StockSharp の位置を使用する
高度な資本管理が必要な場合は、サイジングヘルパーを使用します。
- インジケーター バッファー 4/5/6 は、直接戦略ロジックとチャートの注釈に置き換えられます。
DrawIndicator と DrawOwnTrades。
推奨される拡張機能
- ATR または最近の ZigZag スイングに関連付けられたストップロスとテイクプロフィットのパラメーターを追加します。
- 元のインジケーターを
BreakoutDistancePoints = 0 でオーバーレイして、生のピボット ラダーを視覚化します。
- セッションフィルター (
IsFormedAndOnlineAndAllowTrading) と組み合わせて、取引時間を制限します。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// ZigAndZagScalpel translation that trades on breakouts from short-term pivots confirmed by a long-term ZigZag trend.
/// </summary>
public class ZigAndZagScalpelStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maxTradesPerDay;
private readonly StrategyParam<bool> _closeOnOppositePivot;
private decimal _previousMajorPivot;
private decimal _lastMajorPivot;
private decimal _previousMinorPivot;
private decimal _lastMinorPivot;
private DateTime _currentDay = DateTime.MinValue;
private int _tradesToday;
private bool _trendUp;
private PivotTypes _lastMinorPivotType = PivotTypes.None;
private bool _minorPivotUsed;
/// <summary>
/// Initializes a new instance of the <see cref="ZigAndZagScalpelStrategy"/> class.
/// </summary>
public ZigAndZagScalpelStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for all calculations", "General");
_maxTradesPerDay = Param(nameof(MaxTradesPerDay), 1)
.SetDisplay("Max Trades Per Day", "Daily limit matching the original expert advisor", "Trading");
_closeOnOppositePivot = Param(nameof(CloseOnOppositePivot), true)
.SetDisplay("Close On Opposite Pivot", "Exit when the entry ZigZag prints the opposite swing", "Risk");
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Maximum number of trades allowed per trading day.
/// </summary>
public int MaxTradesPerDay
{
get => _maxTradesPerDay.Value;
set => _maxTradesPerDay.Value = value;
}
/// <summary>
/// Determines whether open positions should be closed on the opposite entry pivot.
/// </summary>
public bool CloseOnOppositePivot
{
get => _closeOnOppositePivot.Value;
set => _closeOnOppositePivot.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousMajorPivot = 0m;
_lastMajorPivot = 0m;
_previousMinorPivot = 0m;
_lastMinorPivot = 0m;
_currentDay = DateTime.MinValue;
_tradesToday = 0;
_trendUp = false;
_lastMinorPivotType = PivotTypes.None;
_minorPivotUsed = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var majorZigZag = new ZigZag { Deviation = 0.02m };
var minorZigZag = new ZigZag { Deviation = 0.005m };
var subscription = SubscribeCandles(CandleType);
subscription
.BindWithEmpty(majorZigZag, minorZigZag, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, majorZigZag);
DrawIndicator(area, minorZigZag);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal? majorValue, decimal? minorValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateDailyCounter(candle.OpenTime);
if (majorValue is not null)
UpdateMajorTrend(majorValue.Value);
if (minorValue is not null)
UpdateMinorPivot(minorValue.Value);
if (!IsFormedAndOnlineAndAllowTrading())
return;
ManageExistingPosition();
if (Position != 0)
return;
if (_minorPivotUsed)
return;
if (_lastMinorPivotType == PivotTypes.None)
return;
if (_tradesToday >= MaxTradesPerDay)
return;
var navel = CalculateNavel(candle);
if (_lastMinorPivotType == PivotTypes.Low && _trendUp)
{
if (navel > _lastMinorPivot)
{
BuyMarket();
_minorPivotUsed = true;
_tradesToday++;
}
}
else if (_lastMinorPivotType == PivotTypes.High && !_trendUp)
{
if (navel < _lastMinorPivot)
{
SellMarket();
_minorPivotUsed = true;
_tradesToday++;
}
}
}
private void UpdateDailyCounter(DateTime time)
{
var date = time.Date;
if (date == _currentDay)
return;
_currentDay = date;
_tradesToday = 0;
}
private void UpdateMajorTrend(decimal majorValue)
{
if (_lastMajorPivot == 0m)
{
_lastMajorPivot = majorValue;
_previousMajorPivot = majorValue;
return;
}
if (majorValue == _lastMajorPivot)
return;
_previousMajorPivot = _lastMajorPivot;
_lastMajorPivot = majorValue;
_trendUp = _lastMajorPivot < _previousMajorPivot;
}
private void UpdateMinorPivot(decimal minorValue)
{
if (_lastMinorPivot == 0m)
{
_lastMinorPivot = minorValue;
_previousMinorPivot = minorValue;
_lastMinorPivotType = PivotTypes.Low;
_minorPivotUsed = false;
return;
}
if (minorValue == _lastMinorPivot)
return;
_previousMinorPivot = _lastMinorPivot;
_lastMinorPivot = minorValue;
_lastMinorPivotType = _lastMinorPivot < _previousMinorPivot ? PivotTypes.Low : PivotTypes.High;
_minorPivotUsed = false;
}
private void ManageExistingPosition()
{
if (Position > 0)
{
if (!_trendUp || (CloseOnOppositePivot && _lastMinorPivotType == PivotTypes.High))
SellMarket(Position);
}
else if (Position < 0)
{
if (_trendUp || (CloseOnOppositePivot && _lastMinorPivotType == PivotTypes.Low))
BuyMarket(Position.Abs());
}
}
private static decimal CalculateNavel(ICandleMessage candle)
{
return (5m * candle.ClosePrice + 2m * candle.OpenPrice + candle.HighPrice + candle.LowPrice) / 9m;
}
private enum PivotTypes
{
None,
Low,
High
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, DateTime, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ZigZag
# Pivot type constants
PIVOT_NONE = 0
PIVOT_LOW = 1
PIVOT_HIGH = 2
class zig_and_zag_scalpel_strategy(Strategy):
def __init__(self):
super(zig_and_zag_scalpel_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Primary timeframe for all calculations", "General")
self._max_trades_per_day = self.Param("MaxTradesPerDay", 1) \
.SetDisplay("Max Trades Per Day", "Daily limit matching the original expert advisor", "Trading")
self._close_on_opposite_pivot = self.Param("CloseOnOppositePivot", True) \
.SetDisplay("Close On Opposite Pivot", "Exit when the entry ZigZag prints the opposite swing", "Risk")
self._previous_major_pivot = Decimal(0)
self._last_major_pivot = Decimal(0)
self._previous_minor_pivot = Decimal(0)
self._last_minor_pivot = Decimal(0)
self._current_day = DateTime.MinValue
self._trades_today = 0
self._trend_up = False
self._last_minor_pivot_type = PIVOT_NONE
self._minor_pivot_used = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def MaxTradesPerDay(self):
return self._max_trades_per_day.Value
@property
def CloseOnOppositePivot(self):
return self._close_on_opposite_pivot.Value
def OnStarted2(self, time):
super(zig_and_zag_scalpel_strategy, self).OnStarted2(time)
major_zigzag = ZigZag()
major_zigzag.Deviation = Decimal(0.02)
minor_zigzag = ZigZag()
minor_zigzag.Deviation = Decimal(0.005)
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindWithEmpty(major_zigzag, minor_zigzag, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, major_zigzag)
self.DrawIndicator(area, minor_zigzag)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, major_value, minor_value):
if candle.State != CandleStates.Finished:
return
self._update_daily_counter(candle.OpenTime)
if major_value is not None:
self._update_major_trend(major_value)
if minor_value is not None:
self._update_minor_pivot(minor_value)
if not self.IsFormedAndOnlineAndAllowTrading():
return
self._manage_existing_position()
if self.Position != 0:
return
if self._minor_pivot_used:
return
if self._last_minor_pivot_type == PIVOT_NONE:
return
if self._trades_today >= self.MaxTradesPerDay:
return
navel = self._calculate_navel(candle)
if self._last_minor_pivot_type == PIVOT_LOW and self._trend_up:
if navel > self._last_minor_pivot:
self.BuyMarket()
self._minor_pivot_used = True
self._trades_today += 1
elif self._last_minor_pivot_type == PIVOT_HIGH and not self._trend_up:
if navel < self._last_minor_pivot:
self.SellMarket()
self._minor_pivot_used = True
self._trades_today += 1
def _update_daily_counter(self, time):
date = time.Date
if date == self._current_day:
return
self._current_day = date
self._trades_today = 0
def _update_major_trend(self, major_value):
if self._last_major_pivot == Decimal(0):
self._last_major_pivot = major_value
self._previous_major_pivot = major_value
return
if major_value == self._last_major_pivot:
return
self._previous_major_pivot = self._last_major_pivot
self._last_major_pivot = major_value
self._trend_up = self._last_major_pivot < self._previous_major_pivot
def _update_minor_pivot(self, minor_value):
if self._last_minor_pivot == Decimal(0):
self._last_minor_pivot = minor_value
self._previous_minor_pivot = minor_value
self._last_minor_pivot_type = PIVOT_LOW
self._minor_pivot_used = False
return
if minor_value == self._last_minor_pivot:
return
self._previous_minor_pivot = self._last_minor_pivot
self._last_minor_pivot = minor_value
self._last_minor_pivot_type = PIVOT_LOW if self._last_minor_pivot < self._previous_minor_pivot else PIVOT_HIGH
self._minor_pivot_used = False
def _manage_existing_position(self):
if self.Position > 0:
if not self._trend_up or (self.CloseOnOppositePivot and self._last_minor_pivot_type == PIVOT_HIGH):
self.SellMarket(self.Position)
elif self.Position < 0:
if self._trend_up or (self.CloseOnOppositePivot and self._last_minor_pivot_type == PIVOT_LOW):
self.BuyMarket(abs(self.Position))
def _calculate_navel(self, candle):
return (Decimal(5) * candle.ClosePrice + Decimal(2) * candle.OpenPrice +
candle.HighPrice + candle.LowPrice) / Decimal(9)
def OnReseted(self):
super(zig_and_zag_scalpel_strategy, self).OnReseted()
self._previous_major_pivot = Decimal(0)
self._last_major_pivot = Decimal(0)
self._previous_minor_pivot = Decimal(0)
self._last_minor_pivot = Decimal(0)
self._current_day = DateTime.MinValue
self._trades_today = 0
self._trend_up = False
self._last_minor_pivot_type = PIVOT_NONE
self._minor_pivot_used = False
def CreateClone(self):
return zig_and_zag_scalpel_strategy()