Стратегия Stochastic Martingale
Эта стратегия сочетает классический вход по стохастическому осциллятору с усреднением по принципу мартингейла. Позиция открывается, когда линия %K пересекает %D и осциллятор находится выше/ниже заданных зон. Если цена движется против позиции на заданный шаг, объём увеличивается с коэффициентом. Позиции закрываются при достижении суммарной прибыли в заданном количестве пунктов.
Детали
- Вход
- Лонг: %K > %D и %D > ZoneBuy
- Шорт: %K < %D и %D < ZoneSell
- Усреднение
- Дополнительные ордера ставятся каждые
Stepпунктов (илиStep * число ордеровв режиме 1). - Объём каждого нового ордера умножается на
Mult.
- Дополнительные ордера ставятся каждые
- Выход
- Лонг: цена ≥ последняя цена покупки +
ProfitFactor * число ордеровпунктов. - Шорт: цена ≤ последняя цена продажи −
ProfitFactor * число ордеровпунктов.
- Лонг: цена ≥ последняя цена покупки +
- Параметры включают шаг, режим шага, коэффициент прибыли, множитель, стартовые объёмы и периоды стохастика.
- Фильтры
- Категория: Трендовая
- Направление: Обе стороны
- Индикаторы: Stochastic
- Стопы: Нет
- Сложность: Средняя
- Таймфрейм: Краткосрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенции: Нет
- Уровень риска: Высокий
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>
/// Stochastic Martingale strategy.
/// Uses Stochastic oscillator for signals and martingale averaging.
/// </summary>
public class StochasticMartingaleStrategy : Strategy
{
private readonly StrategyParam<int> _step;
private readonly StrategyParam<int> _stepMode;
private readonly StrategyParam<int> _profitFactor;
private readonly StrategyParam<decimal> _mult;
private readonly StrategyParam<decimal> _buyVolume;
private readonly StrategyParam<decimal> _sellVolume;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<decimal> _zoneBuy;
private readonly StrategyParam<decimal> _zoneSell;
private readonly StrategyParam<bool> _reverse;
private readonly StrategyParam<DataType> _candleType;
private decimal _lastBuyPrice;
private decimal _lastBuyVolume;
private decimal _lastSellPrice;
private decimal _lastSellVolume;
private int _buyCount;
private int _sellCount;
/// <summary>Price step in points for averaging.</summary>
public int Step { get => _step.Value; set => _step.Value = value; }
/// <summary>Step mode: 0 - fixed, 1 - multiplied by orders count.</summary>
public int StepMode { get => _stepMode.Value; set => _stepMode.Value = value; }
/// <summary>Points for take profit per order.</summary>
public int ProfitFactor { get => _profitFactor.Value; set => _profitFactor.Value = value; }
/// <summary>Volume multiplier for averaging.</summary>
public decimal Mult { get => _mult.Value; set => _mult.Value = value; }
/// <summary>Initial buy volume.</summary>
public decimal BuyVolume { get => _buyVolume.Value; set => _buyVolume.Value = value; }
/// <summary>Initial sell volume.</summary>
public decimal SellVolume { get => _sellVolume.Value; set => _sellVolume.Value = value; }
/// <summary>Stochastic %K period.</summary>
public int KPeriod { get => _kPeriod.Value; set => _kPeriod.Value = value; }
/// <summary>Stochastic %D period.</summary>
public int DPeriod { get => _dPeriod.Value; set => _dPeriod.Value = value; }
/// <summary>Oversold level.</summary>
public decimal ZoneBuy { get => _zoneBuy.Value; set => _zoneBuy.Value = value; }
/// <summary>Overbought level.</summary>
public decimal ZoneSell { get => _zoneSell.Value; set => _zoneSell.Value = value; }
/// <summary>Reverse entry direction.</summary>
public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
/// <summary>Candle type.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>Initialize <see cref="StochasticMartingaleStrategy"/>.</summary>
public StochasticMartingaleStrategy()
{
_step = Param(nameof(Step), 25)
.SetGreaterThanZero()
.SetDisplay("Step", "Price step in points for averaging", "Martingale");
_stepMode = Param(nameof(StepMode), 0)
.SetDisplay("Step Mode", "0 - fixed step, 1 - step multiplied by orders count", "Martingale");
_profitFactor = Param(nameof(ProfitFactor), 20)
.SetGreaterThanZero()
.SetDisplay("Profit Factor", "Points for take profit per order", "Martingale");
_mult = Param(nameof(Mult), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "Volume multiplier for averaging", "Martingale");
_buyVolume = Param(nameof(BuyVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Buy Volume", "Initial buy volume", "General");
_sellVolume = Param(nameof(SellVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Sell Volume", "Initial sell volume", "General");
_kPeriod = Param(nameof(KPeriod), 200)
.SetGreaterThanZero()
.SetDisplay("%K Period", "Stochastic %K period", "Indicators");
_dPeriod = Param(nameof(DPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("%D Period", "Stochastic %D period", "Indicators");
_zoneBuy = Param(nameof(ZoneBuy), 65m)
.SetDisplay("Zone Buy", "Oversold level", "Indicators");
_zoneSell = Param(nameof(ZoneSell), 70m)
.SetDisplay("Zone Sell", "Overbought level", "Indicators");
_reverse = Param(nameof(Reverse), false)
.SetDisplay("Reverse", "Reverses entry direction", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastBuyPrice = 0;
_lastBuyVolume = 0;
_lastSellPrice = 0;
_lastSellVolume = 0;
_buyCount = 0;
_sellCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var stochastic = new StochasticOscillator
{
K = { Length = KPeriod },
D = { Length = DPeriod },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(stochastic, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, stochastic);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal kValue || stoch.D is not decimal dValue)
return;
var step = Step * (Security.PriceStep ?? 0m);
var profit = ProfitFactor * (Security.PriceStep ?? 0m);
var price = candle.ClosePrice;
if (_buyCount > 0 && Position > 0)
{
if ((StepMode == 0 && price <= _lastBuyPrice - step) ||
(StepMode == 1 && price <= _lastBuyPrice - step * _buyCount))
{
var volume = CheckVolume(_lastBuyVolume * Mult);
if (volume > 0)
{
BuyMarket(volume);
_lastBuyPrice = price;
_lastBuyVolume = volume;
_buyCount++;
}
}
if (price >= _lastBuyPrice + profit * _buyCount)
{
SellMarket(Math.Abs(Position));
_buyCount = 0;
}
}
else if (_sellCount > 0 && Position < 0)
{
if ((StepMode == 0 && price >= _lastSellPrice + step) ||
(StepMode == 1 && price >= _lastSellPrice + step * _sellCount))
{
var volume = CheckVolume(_lastSellVolume * Mult);
if (volume > 0)
{
SellMarket(volume);
_lastSellPrice = price;
_lastSellVolume = volume;
_sellCount++;
}
}
if (price <= _lastSellPrice - profit * _sellCount)
{
BuyMarket(Math.Abs(Position));
_sellCount = 0;
}
}
else if (Position == 0)
{
if (kValue > dValue && dValue > ZoneBuy)
{
if (!Reverse)
{
var volume = CheckVolume(BuyVolume);
if (volume > 0)
{
BuyMarket(volume);
_lastBuyPrice = price;
_lastBuyVolume = volume;
_buyCount = 1;
}
}
else
{
var volume = CheckVolume(SellVolume);
if (volume > 0)
{
SellMarket(volume);
_lastSellPrice = price;
_lastSellVolume = volume;
_sellCount = 1;
}
}
}
else if (kValue < dValue && dValue < ZoneSell)
{
if (!Reverse)
{
var volume = CheckVolume(SellVolume);
if (volume > 0)
{
SellMarket(volume);
_lastSellPrice = price;
_lastSellVolume = volume;
_sellCount = 1;
}
}
else
{
var volume = CheckVolume(BuyVolume);
if (volume > 0)
{
BuyMarket(volume);
_lastBuyPrice = price;
_lastBuyVolume = volume;
_buyCount = 1;
}
}
}
}
}
private decimal CheckVolume(decimal volume)
{
var step = Security.VolumeStep ?? 0m;
if (step > 0)
volume = step * Math.Floor(volume / step);
if (volume <= 0)
volume = 0m;
return volume;
}
}
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
from StockSharp.Algo.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class stochastic_martingale_strategy(Strategy):
def __init__(self):
super(stochastic_martingale_strategy, self).__init__()
self._step = self.Param("Step", 25)
self._step_mode = self.Param("StepMode", 0)
self._profit_factor = self.Param("ProfitFactor", 20)
self._mult = self.Param("Mult", 1.5)
self._buy_volume_param = self.Param("BuyVolume", 0.01)
self._sell_volume_param = self.Param("SellVolume", 0.01)
self._k_period = self.Param("KPeriod", 200)
self._d_period = self.Param("DPeriod", 20)
self._zone_buy = self.Param("ZoneBuy", 65.0)
self._zone_sell = self.Param("ZoneSell", 70.0)
self._reverse = self.Param("Reverse", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._last_buy_price = 0.0
self._last_buy_volume = 0.0
self._last_sell_price = 0.0
self._last_sell_volume = 0.0
self._buy_count = 0
self._sell_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(stochastic_martingale_strategy, self).OnStarted2(time)
self._last_buy_price = 0.0
self._last_buy_volume = 0.0
self._last_sell_price = 0.0
self._last_sell_volume = 0.0
self._buy_count = 0
self._sell_count = 0
stochastic = StochasticOscillator()
stochastic.K.Length = int(self._k_period.Value)
stochastic.D.Length = int(self._d_period.Value)
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(stochastic, self.ProcessCandle).Start()
def _check_volume(self, volume):
sec = self.Security
vol_step = float(sec.VolumeStep) if sec is not None and sec.VolumeStep is not None else 0.0
if vol_step > 0:
volume = vol_step * Math.Floor(volume / vol_step)
if volume <= 0:
volume = 0.0
return volume
def ProcessCandle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
k_val = stoch_value.K
d_val = stoch_value.D
if k_val is None or d_val is None:
return
k_val = float(k_val)
d_val = float(d_val)
price_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0
step = float(self._step.Value) * price_step
profit = float(self._profit_factor.Value) * price_step
price = float(candle.ClosePrice)
mult = float(self._mult.Value)
zone_buy = float(self._zone_buy.Value)
zone_sell = float(self._zone_sell.Value)
reverse = bool(self._reverse.Value)
buy_vol = float(self._buy_volume_param.Value)
sell_vol = float(self._sell_volume_param.Value)
pos = float(self.Position)
step_mode = int(self._step_mode.Value)
if self._buy_count > 0 and pos > 0:
if (step_mode == 0 and price <= self._last_buy_price - step) or \
(step_mode == 1 and price <= self._last_buy_price - step * self._buy_count):
volume = self._check_volume(self._last_buy_volume * mult)
if volume > 0:
self.BuyMarket(volume)
self._last_buy_price = price
self._last_buy_volume = volume
self._buy_count += 1
if price >= self._last_buy_price + profit * self._buy_count:
self.SellMarket(abs(float(self.Position)))
self._buy_count = 0
elif self._sell_count > 0 and pos < 0:
if (step_mode == 0 and price >= self._last_sell_price + step) or \
(step_mode == 1 and price >= self._last_sell_price + step * self._sell_count):
volume = self._check_volume(self._last_sell_volume * mult)
if volume > 0:
self.SellMarket(volume)
self._last_sell_price = price
self._last_sell_volume = volume
self._sell_count += 1
if price <= self._last_sell_price - profit * self._sell_count:
self.BuyMarket(abs(float(self.Position)))
self._sell_count = 0
elif float(self.Position) == 0:
if k_val > d_val and d_val > zone_buy:
if not reverse:
volume = self._check_volume(buy_vol)
if volume > 0:
self.BuyMarket(volume)
self._last_buy_price = price
self._last_buy_volume = volume
self._buy_count = 1
else:
volume = self._check_volume(sell_vol)
if volume > 0:
self.SellMarket(volume)
self._last_sell_price = price
self._last_sell_volume = volume
self._sell_count = 1
elif k_val < d_val and d_val < zone_sell:
if not reverse:
volume = self._check_volume(sell_vol)
if volume > 0:
self.SellMarket(volume)
self._last_sell_price = price
self._last_sell_volume = volume
self._sell_count = 1
else:
volume = self._check_volume(buy_vol)
if volume > 0:
self.BuyMarket(volume)
self._last_buy_price = price
self._last_buy_volume = volume
self._buy_count = 1
def OnReseted(self):
super(stochastic_martingale_strategy, self).OnReseted()
self._last_buy_price = 0.0
self._last_buy_volume = 0.0
self._last_sell_price = 0.0
self._last_sell_volume = 0.0
self._buy_count = 0
self._sell_count = 0
def CreateClone(self):
return stochastic_martingale_strategy()