Стратегия Exp Martin V2
Стратегия Exp Martin V2 реализует экспоненциальную мартингейловую систему. В любой момент открыта только одна позиция, а направление и объём следующей сделки выбираются на основе результата предыдущей.
Старт происходит с заданного типа ордера (покупка или продажа) и начального объёма. Для каждой позиции устанавливаются фиксированные уровни тейк-профита и стоп-лосса. Если сделка закрылась с прибылью, снова открывается позиция того же типа и с исходным объёмом. При убыточной сделке направление разворачивается, а объём умножается на указанный коэффициент. Увеличение объёма продолжается после каждого убытка до достижения максимального числа умножений, после чего объём возвращается к исходному значению.
Таким образом формируется чередующаяся серия сделок, которая стремится компенсировать предыдущие потери при появлении прибыльного движения.
Детали
- Логика входа:
- Открыть первую позицию согласно параметру Start Type (0 - покупка, 1 - продажа) с объёмом Start Volume.
- После прибыльной сделки повторить то же направление с исходным объёмом.
- После убыточной сделки развернуть направление и умножить объём на Factor до достижения лимита Limit.
- Длинные/короткие позиции: В зависимости от текущей последовательности.
- Логика выхода:
- Позиции закрываются при достижении уровней Take Profit или Stop Loss.
- Стопы: Фиксированные уровни тейк-профита и стоп-лосса в пунктах.
- Фильтры: Отсутствуют.
- Управление позицией: Одновременно открыта только одна позиция.
Стратегия предназначена для экспериментов с мартингейлом в StockSharp без использования дополнительных индикаторов.
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>
/// Exponential martingale strategy.
/// </summary>
public class ExpMartinV2Strategy : Strategy
{
private readonly StrategyParam<decimal> _startVolume;
private readonly StrategyParam<decimal> _factor;
private readonly StrategyParam<int> _limit;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _startType;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cycleCooldownBars;
private decimal _currentVolume;
private decimal _maxVolume;
private bool _needOpenPosition = true;
private int _direction;
private decimal _entryPrice;
private decimal _longTake;
private decimal _longStop;
private decimal _shortTake;
private decimal _shortStop;
private int _cooldownRemaining;
/// <summary>
/// Initial order volume.
/// </summary>
public decimal StartVolume { get => _startVolume.Value; set => _startVolume.Value = value; }
/// <summary>
/// Volume multiplier after a loss.
/// </summary>
public decimal Factor { get => _factor.Value; set => _factor.Value = value; }
/// <summary>
/// Maximum number of volume multiplications.
/// </summary>
public int Limit { get => _limit.Value; set => _limit.Value = value; }
/// <summary>
/// Stop loss in price steps.
/// </summary>
public int StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
/// <summary>
/// Take profit in price steps.
/// </summary>
public int TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
/// <summary>
/// Starting order type: 0 - buy, 1 - sell.
/// </summary>
public int StartType { get => _startType.Value; set => _startType.Value = value; }
/// <summary>
/// Candle type used for processing.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Closed candles to wait before starting the next martingale cycle.
/// </summary>
public int CycleCooldownBars { get => _cycleCooldownBars.Value; set => _cycleCooldownBars.Value = value; }
public ExpMartinV2Strategy()
{
_startVolume = Param(nameof(StartVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Start Volume", "Initial order volume", "General");
_factor = Param(nameof(Factor), 2m)
.SetGreaterThanZero()
.SetDisplay("Factor", "Volume multiplier", "General");
_limit = Param(nameof(Limit), 5)
.SetGreaterThanZero()
.SetDisplay("Limit", "Max multiplication count", "General");
_stopLoss = Param(nameof(StopLoss), 100)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Loss limit in points", "Risk");
_takeProfit = Param(nameof(TakeProfit), 100)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Profit target in points", "Risk");
_startType = Param(nameof(StartType), 0)
.SetDisplay("Start Type", "0-Buy, 1-Sell", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_cycleCooldownBars = Param(nameof(CycleCooldownBars), 2)
.SetNotNegative()
.SetDisplay("Cycle Cooldown Bars", "Closed candles to wait before the next entry cycle", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentVolume = 0m;
_maxVolume = 0m;
_needOpenPosition = true;
_direction = 0;
_entryPrice = 0m;
_longTake = 0m;
_longStop = 0m;
_shortTake = 0m;
_shortStop = 0m;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_currentVolume = StartVolume;
_direction = StartType == 0 ? 1 : -1;
_maxVolume = StartVolume;
for (var i = 0; i < Limit; i++)
_maxVolume = RoundVolume(_maxVolume * Factor);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var step = Security.PriceStep ?? 1m;
if (Position > 0)
{
if (candle.HighPrice >= _longTake)
{
SellMarket(Position);
PrepareNext(true);
}
else if (candle.LowPrice <= _longStop)
{
SellMarket(Position);
PrepareNext(false);
}
}
else if (Position < 0)
{
if (candle.LowPrice <= _shortTake)
{
BuyMarket(-Position);
PrepareNext(true);
}
else if (candle.HighPrice >= _shortStop)
{
BuyMarket(-Position);
PrepareNext(false);
}
}
if (_needOpenPosition && Position == 0 && _cooldownRemaining == 0)
{
_entryPrice = candle.ClosePrice;
if (_direction == 1)
{
BuyMarket(_currentVolume);
_longTake = _entryPrice + TakeProfit * step;
_longStop = _entryPrice - StopLoss * step;
}
else
{
SellMarket(_currentVolume);
_shortTake = _entryPrice - TakeProfit * step;
_shortStop = _entryPrice + StopLoss * step;
}
_needOpenPosition = false;
}
}
private void PrepareNext(bool wasProfit)
{
if (wasProfit)
{
_currentVolume = StartVolume;
}
else
{
_direction = -_direction;
_currentVolume = RoundVolume(_currentVolume * Factor);
if (_currentVolume > _maxVolume)
_currentVolume = StartVolume;
}
_needOpenPosition = true;
_cooldownRemaining = CycleCooldownBars;
}
private decimal RoundVolume(decimal volume)
{
var step = Security.VolumeStep ?? 1m;
return Math.Ceiling(volume / step) * step;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class exp_martin_v2_strategy(Strategy):
"""
Exponential martingale strategy.
Opens positions and doubles volume on loss, resets on profit.
"""
def __init__(self):
super(exp_martin_v2_strategy, self).__init__()
self._start_volume = self.Param("StartVolume", 1.0) \
.SetDisplay("Start Volume", "Initial order volume", "General")
self._factor = self.Param("Factor", 2.0) \
.SetDisplay("Factor", "Volume multiplier", "General")
self._limit = self.Param("Limit", 5) \
.SetDisplay("Limit", "Max multiplication count", "General")
self._stop_loss = self.Param("StopLoss", 100) \
.SetDisplay("Stop Loss", "Loss limit in points", "Risk")
self._take_profit = self.Param("TakeProfit", 100) \
.SetDisplay("Take Profit", "Profit target in points", "Risk")
self._start_type = self.Param("StartType", 0) \
.SetDisplay("Start Type", "0-Buy, 1-Sell", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._cooldown_bars = self.Param("CycleCooldownBars", 2) \
.SetDisplay("Cycle Cooldown Bars", "Bars to wait before next cycle", "General")
self._current_volume = 0.0
self._max_volume = 0.0
self._need_open = True
self._direction = 0
self._entry_price = 0.0
self._long_take = 0.0
self._long_stop = 0.0
self._short_take = 0.0
self._short_stop = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_martin_v2_strategy, self).OnReseted()
self._current_volume = 0.0
self._max_volume = 0.0
self._need_open = True
self._direction = 0
self._entry_price = 0.0
self._long_take = 0.0
self._long_stop = 0.0
self._short_take = 0.0
self._short_stop = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(exp_martin_v2_strategy, self).OnStarted2(time)
sv = float(self._start_volume.Value)
self._current_volume = sv
self._direction = 1 if self._start_type.Value == 0 else -1
self._max_volume = sv
f = float(self._factor.Value)
for i in range(self._limit.Value):
self._max_volume = self._round_volume(self._max_volume * f)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self.Position > 0:
if high >= self._long_take:
self.SellMarket(self.Position)
self._prepare_next(True)
elif low <= self._long_stop:
self.SellMarket(self.Position)
self._prepare_next(False)
elif self.Position < 0:
if low <= self._short_take:
self.BuyMarket(-self.Position)
self._prepare_next(True)
elif high >= self._short_stop:
self.BuyMarket(-self.Position)
self._prepare_next(False)
if self._need_open and self.Position == 0 and self._cooldown_remaining == 0:
self._entry_price = close
tp = self._take_profit.Value
sl = self._stop_loss.Value
if self._direction == 1:
self.BuyMarket(self._current_volume)
self._long_take = self._entry_price + tp * step
self._long_stop = self._entry_price - sl * step
else:
self.SellMarket(self._current_volume)
self._short_take = self._entry_price - tp * step
self._short_stop = self._entry_price + sl * step
self._need_open = False
def _prepare_next(self, was_profit):
if was_profit:
self._current_volume = float(self._start_volume.Value)
else:
self._direction = -self._direction
self._current_volume = self._round_volume(self._current_volume * float(self._factor.Value))
if self._current_volume > self._max_volume:
self._current_volume = float(self._start_volume.Value)
self._need_open = True
self._cooldown_remaining = self._cooldown_bars.Value
def _round_volume(self, volume):
step = 1.0
if self.Security is not None and self.Security.VolumeStep is not None:
step = float(self.Security.VolumeStep)
if step <= 0:
step = 1.0
return math.ceil(volume / step) * step
def CreateClone(self):
return exp_martin_v2_strategy()