Nevalyashka Martingale戦略
概要
Nevalyashka Martingale戦略は、MetaTrader 5エキスパートアドバイザー「Nevalyashka3_1」の直接移植版です。損失取引後に売買を交互に切り替えるシングルシンボルのマーチンゲールを実行します。戦略は常に売りから始まり、前回の取引サイクルが利益か損失かを決定するためにアカウントのエクイティを測定します。利益はボリュームをベースロットサイズにリセットして方向を変えずに維持し、損失はロットサイズを乗算してドローダウンを回復しようと方向を切り替えます。
動作の仕組み
- 最初の取引 – ベースロットサイズを使用して最初の完成したローソク足でショートポジションを開きます。
- エクイティ追跡 – 戦略は最高観測エクイティ値を保存します。ポジションが開いていないとき、現在のエクイティを保存されたピークと比較します。
- エクイティが新高値を付けた場合、次の取引はベースロットサイズを使用して最後の方向を繰り返します。
- エクイティが新高値を付けなかった場合、次の取引は乗数でロットを増やして方向を切り替えます。
- ストップロス / テイクプロフィット – すべての注文は「ポイント」(インストゥルメントステップ)で定義された固定距離を使用します。テイクプロフィットは元のエキスパートを反映:ストップはエントリーから
StopLossPoints離れ、ターゲットはTakeProfitPoints離れています。 - トレーリング – 価格が
MoveProfitPoints動くと、ストップが締まります。各動きには追加のMoveStepPointsバッファが必要で、市場がさらに押し進めたときのみストップが前進します。ストップがエントリー価格を超えて引かれると、計画ボリュームが乗数で割られ、次の取引をベースロットに向けてスケールダウンします。 - ポジション終了 – ローソク足の高値/安値がストップまたはテイクレベルに達すると、ポジションは即座に閉じます。終了後、戦略はエクイティを評価して次のシグナルを準備します。
パラメーター
BaseVolume– 最初の取引と利益の出たサイクルのロットサイズ(デフォルト0.1)。VolumeMultiplier– 損失後に次のロットを増やすために適用される係数(デフォルト1.1)。TakeProfitPoints– 価格ポイントで測定したテイクプロフィット距離(デフォルト94)。MoveProfitPoints– トレーリングストップが有効になる前の最小有利エクスカーション(デフォルト25)。MoveStepPoints– 連続するトレーリング調整の間に必要な追加バッファ(デフォルト11)。StopLossPoints– 価格ポイントで測定した初期ストップロス距離(デフォルト70)。CandleType– 取引管理に使用する時間軸。デフォルトは5分足。
ポジション管理の詳細
- 戦略は元の「Lot」変数を反映するために
_plannedVolumeを保持します。取引を閉じた後またはストップがブレークイーブンを超えたときのみ変更されます。 AdjustVolumeはVolumeStepにロットサイズを合わせ、MinVolume/MaxVolumeを適用することでexchangeのルールを尊重します。GetPointValueはMT5の「調整ポイント」ロジックを再現します:3桁または5桁の小数で見積もられるインストゥルメントの場合、ポイントサイズに10を乗算して整数pipsで作業します。HandleLongPositionとHandleShortPositionは、インジケーター履歴に頼らずにローソク足の高値と安値を使用してMT5のストップ修正と終了動作をエミュレートします。
使用上の注意
- 戦略は単一の証券を取引することを前提としています。戦略に追加して、開始前に
Security/Portfolioを設定してください。 - マーチンゲールなので、一連の損失後にリスクが急速に増大します。
BaseVolumeとVolumeMultiplierを注意深く調整し、現実的な証拠金要件でテストしてください。 - ストップとテイクプロフィットの距離はインストゥルメントポイントで定義されています。オフセットとロット計算がブローカーと一致するように、証券のメタデータ(
PriceStep、VolumeStep、MinVolume)が入力されていることを確認してください。 - トレーリングロジックは完成したローソク足に作用します。価格パスによっては、ライブ取引でバー内のストップヒットが早く発生する可能性があります。
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>
/// Equity-based martingale strategy that alternates trade direction after losses.
/// Opens a short position on startup, resets volume after profitable cycles,
/// and increases exposure following drawdowns while managing fixed stops and targets.
/// </summary>
public class NevalyashkaMartingaleStrategy : Strategy
{
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _moveProfitPoints;
private readonly StrategyParam<decimal> _moveStepPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal _plannedVolume;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _equityPeak;
private bool _nextDirectionIsSell = true;
private bool _initialOrderPlaced;
/// <summary>
/// Initializes <see cref="NevalyashkaMartingaleStrategy"/>.
/// </summary>
public NevalyashkaMartingaleStrategy()
{
_baseVolume = Param(nameof(BaseVolume), 0.1m)
.SetDisplay("Base Volume", "Initial trade volume", "Risk")
.SetGreaterThanZero()
;
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.1m)
.SetDisplay("Volume Multiplier", "Multiplier applied after losses", "Risk")
.SetGreaterThanZero()
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetDisplay("Take Profit Points", "Profit target in points", "Orders")
.SetGreaterThanZero()
;
_moveProfitPoints = Param(nameof(MoveProfitPoints), 100m)
.SetDisplay("Move Profit Points", "Profit buffer before trailing activates", "Orders")
.SetGreaterThanZero()
;
_moveStepPoints = Param(nameof(MoveStepPoints), 50m)
.SetDisplay("Move Step Points", "Extra buffer for trailing stop updates", "Orders")
.SetGreaterThanZero()
;
_stopLossPoints = Param(nameof(StopLossPoints), 400m)
.SetDisplay("Stop Loss Points", "Initial protective distance", "Orders")
.SetGreaterThanZero()
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for trade management", "General");
}
/// <summary>
/// Base order volume.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Multiplier applied when recovering from a loss.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// Take profit distance in points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Minimum profit in points before the stop is tightened.
/// </summary>
public decimal MoveProfitPoints
{
get => _moveProfitPoints.Value;
set => _moveProfitPoints.Value = value;
}
/// <summary>
/// Additional margin in points required between stop adjustments.
/// </summary>
public decimal MoveStepPoints
{
get => _moveStepPoints.Value;
set => _moveStepPoints.Value = value;
}
/// <summary>
/// Initial stop loss distance in points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Candle type used to drive the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_plannedVolume = 0m;
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_equityPeak = 0m;
_nextDirectionIsSell = true;
_initialOrderPlaced = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_equityPeak = Portfolio?.CurrentValue ?? 0m;
_plannedVolume = AdjustVolume(BaseVolume);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var point = GetPointValue();
HandleOpenPosition(candle, point);
if (Position != 0)
return;
var equity = Portfolio?.CurrentValue ?? 0m;
if (!_initialOrderPlaced)
{
if (_plannedVolume == 0m)
{
_plannedVolume = AdjustVolume(BaseVolume);
if (_plannedVolume == 0m)
return;
}
if (OpenPosition(true, candle.ClosePrice, point))
{
_initialOrderPlaced = true;
_nextDirectionIsSell = true;
}
return;
}
if (equity > _equityPeak)
{
_equityPeak = equity;
_plannedVolume = AdjustVolume(BaseVolume);
if (_plannedVolume == 0m)
return;
if (_nextDirectionIsSell)
{
if (OpenPosition(true, candle.ClosePrice, point))
return;
}
else
{
if (OpenPosition(false, candle.ClosePrice, point))
return;
}
}
else
{
var increased = VolumeMultiplier > 0m ? AdjustVolume(_plannedVolume * VolumeMultiplier) : 0m;
if (increased == 0m)
return;
_plannedVolume = increased;
if (_nextDirectionIsSell)
{
if (OpenPosition(false, candle.ClosePrice, point))
_nextDirectionIsSell = false;
}
else
{
if (OpenPosition(true, candle.ClosePrice, point))
_nextDirectionIsSell = true;
}
}
}
private void HandleOpenPosition(ICandleMessage candle, decimal point)
{
if (Position > 0)
{
HandleLongPosition(candle, point);
}
else if (Position < 0)
{
HandleShortPosition(candle, point);
}
}
private void HandleLongPosition(ICandleMessage candle, decimal point)
{
if (_stopPrice is not decimal currentStop || _takePrice is not decimal currentTake)
return;
var price = candle.ClosePrice;
var moveThreshold = MoveProfitPoints * point;
if (price - _entryPrice > moveThreshold)
{
var candidate = price - (StopLossPoints + MoveStepPoints) * point;
if (candidate > currentStop)
{
var newStop = price - StopLossPoints * point;
_stopPrice = newStop;
if (_plannedVolume > AdjustVolume(BaseVolume) && newStop > _entryPrice)
ReduceVolume();
}
}
if (candle.LowPrice <= _stopPrice)
{
SellMarket();
ResetProtection();
return;
}
if (candle.HighPrice >= currentTake)
{
SellMarket();
ResetProtection();
}
}
private void HandleShortPosition(ICandleMessage candle, decimal point)
{
if (_stopPrice is not decimal currentStop || _takePrice is not decimal currentTake)
return;
var price = candle.ClosePrice;
var moveThreshold = MoveProfitPoints * point;
if (_entryPrice - price > moveThreshold)
{
var candidate = price + (StopLossPoints + MoveStepPoints) * point;
if (candidate < currentStop)
{
var newStop = price + StopLossPoints * point;
_stopPrice = newStop;
if (_plannedVolume > AdjustVolume(BaseVolume) && newStop < _entryPrice)
ReduceVolume();
}
}
if (candle.HighPrice >= _stopPrice)
{
BuyMarket();
ResetProtection();
return;
}
if (candle.LowPrice <= currentTake)
{
BuyMarket();
ResetProtection();
}
}
private bool OpenPosition(bool isSell, decimal price, decimal point)
{
if (_plannedVolume <= 0m)
return false;
if (point <= 0m)
return false;
var stopOffset = StopLossPoints * point;
var takeOffset = TakeProfitPoints * point;
if (stopOffset <= 0m || takeOffset <= 0m)
return false;
if (isSell)
{
SellMarket();
_stopPrice = price + stopOffset;
_takePrice = price - takeOffset;
}
else
{
BuyMarket();
_stopPrice = price - stopOffset;
_takePrice = price + takeOffset;
}
_entryPrice = price;
return true;
}
private void ReduceVolume()
{
if (VolumeMultiplier <= 0m)
return;
var baseVolume = AdjustVolume(BaseVolume);
if (baseVolume == 0m)
return;
var reduced = AdjustVolume(_plannedVolume / VolumeMultiplier);
if (reduced < baseVolume)
reduced = baseVolume;
_plannedVolume = reduced;
}
private void ResetProtection()
{
_stopPrice = null;
_takePrice = null;
_entryPrice = 0m;
}
private decimal AdjustVolume(decimal volume)
{
if (Security is null)
return volume;
if (volume <= 0m)
return 0m;
var step = Security.VolumeStep ?? 0m;
if (step > 0m)
volume = Math.Floor(volume / step) * step;
var min = Security.MinVolume ?? 0m;
if (min > 0m && volume < min)
return 0m;
var max = Security.MaxVolume ?? 0m;
if (max > 0m && volume > max)
volume = max;
return volume;
}
private decimal GetPointValue()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
step = 1m;
if (step <= 0m)
return 1m;
var digits = 0;
var value = step;
while (value < 1m && digits < 10)
{
value *= 10m;
digits++;
}
if (digits == 3 || digits == 5)
step *= 10m;
return step;
}
}
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 nevalyashka_martingale_strategy(Strategy):
def __init__(self):
super(nevalyashka_martingale_strategy, self).__init__()
self._base_volume = self.Param("BaseVolume", 0.1)
self._volume_multiplier = self.Param("VolumeMultiplier", 1.1)
self._take_profit_points = self.Param("TakeProfitPoints", 500.0)
self._move_profit_points = self.Param("MoveProfitPoints", 100.0)
self._move_step_points = self.Param("MoveStepPoints", 50.0)
self._stop_loss_points = self.Param("StopLossPoints", 400.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._planned_volume = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._equity_peak = 0.0
self._next_direction_is_sell = True
self._initial_order_placed = False
@property
def BaseVolume(self):
return self._base_volume.Value
@BaseVolume.setter
def BaseVolume(self, value):
self._base_volume.Value = value
@property
def VolumeMultiplier(self):
return self._volume_multiplier.Value
@VolumeMultiplier.setter
def VolumeMultiplier(self, value):
self._volume_multiplier.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def MoveProfitPoints(self):
return self._move_profit_points.Value
@MoveProfitPoints.setter
def MoveProfitPoints(self, value):
self._move_profit_points.Value = value
@property
def MoveStepPoints(self):
return self._move_step_points.Value
@MoveStepPoints.setter
def MoveStepPoints(self, value):
self._move_step_points.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _get_point_value(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
digits = 0
value = step
while value < 1.0 and digits < 10:
value *= 10.0
digits += 1
if digits == 3 or digits == 5:
step *= 10.0
return step
def OnStarted2(self, time):
super(nevalyashka_martingale_strategy, self).OnStarted2(time)
self._equity_peak = float(self.Portfolio.CurrentValue) if self.Portfolio is not None and self.Portfolio.CurrentValue is not None else 0.0
self._planned_volume = float(self.BaseVolume)
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._next_direction_is_sell = True
self._initial_order_placed = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
point = self._get_point_value()
self._handle_open_position(candle, point)
if self.Position != 0:
return
equity = float(self.Portfolio.CurrentValue) if self.Portfolio is not None and self.Portfolio.CurrentValue is not None else 0.0
if not self._initial_order_placed:
if self._planned_volume <= 0.0:
self._planned_volume = float(self.BaseVolume)
if self._planned_volume <= 0.0:
return
if self._open_position(True, float(candle.ClosePrice), point):
self._initial_order_placed = True
self._next_direction_is_sell = True
return
if equity > self._equity_peak:
self._equity_peak = equity
self._planned_volume = float(self.BaseVolume)
if self._planned_volume <= 0.0:
return
if self._next_direction_is_sell:
self._open_position(True, float(candle.ClosePrice), point)
else:
self._open_position(False, float(candle.ClosePrice), point)
else:
mult = float(self.VolumeMultiplier)
if mult > 0.0:
increased = self._planned_volume * mult
else:
increased = 0.0
if increased <= 0.0:
return
self._planned_volume = increased
if self._next_direction_is_sell:
if self._open_position(False, float(candle.ClosePrice), point):
self._next_direction_is_sell = False
else:
if self._open_position(True, float(candle.ClosePrice), point):
self._next_direction_is_sell = True
def _handle_open_position(self, candle, point):
if self.Position > 0:
self._handle_long_position(candle, point)
elif self.Position < 0:
self._handle_short_position(candle, point)
def _handle_long_position(self, candle, point):
if self._stop_price is None or self._take_price is None:
return
current_stop = self._stop_price
current_take = self._take_price
price = float(candle.ClosePrice)
move_threshold = float(self.MoveProfitPoints) * point
if price - self._entry_price > move_threshold:
candidate = price - (float(self.StopLossPoints) + float(self.MoveStepPoints)) * point
if candidate > current_stop:
new_stop = price - float(self.StopLossPoints) * point
self._stop_price = new_stop
if self._planned_volume > float(self.BaseVolume) and new_stop > self._entry_price:
self._reduce_volume()
if float(candle.LowPrice) <= self._stop_price:
self.SellMarket()
self._reset_protection()
return
if float(candle.HighPrice) >= current_take:
self.SellMarket()
self._reset_protection()
def _handle_short_position(self, candle, point):
if self._stop_price is None or self._take_price is None:
return
current_stop = self._stop_price
current_take = self._take_price
price = float(candle.ClosePrice)
move_threshold = float(self.MoveProfitPoints) * point
if self._entry_price - price > move_threshold:
candidate = price + (float(self.StopLossPoints) + float(self.MoveStepPoints)) * point
if candidate < current_stop:
new_stop = price + float(self.StopLossPoints) * point
self._stop_price = new_stop
if self._planned_volume > float(self.BaseVolume) and new_stop < self._entry_price:
self._reduce_volume()
if float(candle.HighPrice) >= self._stop_price:
self.BuyMarket()
self._reset_protection()
return
if float(candle.LowPrice) <= current_take:
self.BuyMarket()
self._reset_protection()
def _open_position(self, is_sell, price, point):
if self._planned_volume <= 0.0:
return False
if point <= 0.0:
return False
stop_offset = float(self.StopLossPoints) * point
take_offset = float(self.TakeProfitPoints) * point
if stop_offset <= 0.0 or take_offset <= 0.0:
return False
if is_sell:
self.SellMarket()
self._stop_price = price + stop_offset
self._take_price = price - take_offset
else:
self.BuyMarket()
self._stop_price = price - stop_offset
self._take_price = price + take_offset
self._entry_price = price
return True
def _reduce_volume(self):
mult = float(self.VolumeMultiplier)
if mult <= 0.0:
return
base_vol = float(self.BaseVolume)
if base_vol <= 0.0:
return
reduced = self._planned_volume / mult
if reduced < base_vol:
reduced = base_vol
self._planned_volume = reduced
def _reset_protection(self):
self._stop_price = None
self._take_price = None
self._entry_price = 0.0
def OnReseted(self):
super(nevalyashka_martingale_strategy, self).OnReseted()
self._planned_volume = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._equity_peak = 0.0
self._next_direction_is_sell = True
self._initial_order_placed = False
def CreateClone(self):
return nevalyashka_martingale_strategy()