Стратегия Nevalyashka Martingale
Обзор
Nevalyashka Martingale — это перенос эксперта MetaTrader 5 «Nevalyashka3_1». Стратегия торгует одним инструментом и реализует мартингейл, чередуя покупки и продажи после убыточных сделок. Запуск начинается с короткой позиции. После закрытия сделок стратегия анализирует капитал счёта: если капитал вырос, объём сбрасывается на базовый и направление сохраняется; если капитал не обновил максимум, объём умножается на коэффициент и направление меняется, чтобы попытаться отыграть просадку.
Логика работы
- Стартовая сделка — на первой завершённой свече открывается шорт базовым объёмом.
- Контроль equity — стратегия хранит максимум капитала. Пока позиции нет, текущий капитал сравнивается с пиком.
- Новый максимум → следующая сделка идёт прежним направлением и базовым объёмом.
- Без обновления максимума → объём умножается на коэффициент, направление переключается (лонг ↔ шорт).
- Стоп и тейк — для каждой сделки выставляется фиксированный стоп и тейк, задаваемый в «пунктах» (шага цены).
- Трейлинг-стоп — когда цена проходит
MoveProfitPoints, стоп подтягивается. Дополнительный буферMoveStepPointsне позволяет переносить стоп слишком часто. Если стоп выходит за точку входа, планируемый объём делится на коэффициент, возвращая его к базовому значению. - Выход из позиции — при достижении стопа или тейка по максимуму/минимуму свечи позиция закрывается рыночным приказом. Далее происходит повторная оценка капитала и подготовка следующего входа.
Параметры
BaseVolume— базовый объём первой и прибыльных сделок (по умолчанию 0.1).VolumeMultiplier— множитель для увеличения объёма после убытка (по умолчанию 1.1).TakeProfitPoints— расстояние до тейк-профита в пунктах (по умолчанию 94).MoveProfitPoints— минимальное движение в прибыль до включения трейлинг-стопа (по умолчанию 25).MoveStepPoints— дополнительный буфер между переносами стопа (по умолчанию 11).StopLossPoints— исходное расстояние стоп-лосса в пунктах (по умолчанию 70).CandleType— тип свечей, по умолчанию пятиминутный таймфрейм.
Особенности управления позицией
- Поле
_plannedVolumeповторяет MT5-переменнуюLot: оно меняется только после закрытия сделки или переноса стопа за точку входа. AdjustVolumeподгоняет объём под биржевые ограничения (VolumeStep,MinVolume,MaxVolume).GetPointValueповторяет MT5-логику «adjusted point»: для котировок с 3 или 5 знаками величина пункта умножается на 10, чтобы работать с пипсами.- Методы
HandleLongPositionиHandleShortPositionиспользуют high/low свечи для имитации переноса стопов и закрытия, как в оригинале.
Рекомендации по применению
- Стратегия рассчитана на один инструмент: перед стартом укажите
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()