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>
/// Strategy based on LeManSignal indicator.
/// Opens long on buy signal and short on sell signal.
/// Signals are derived from high and low breakouts over two consecutive periods.
/// </summary>
public class LeManSignalStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
/// <summary>
/// Indicator period length.
/// </summary>
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// Offset to confirm signal.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public LeManSignalStrategy()
{
_period = Param(nameof(Period), 12)
.SetDisplay("Period", "LeManSignal lookback period", "Indicator")
.SetGreaterThanZero()
.SetOptimize(5, 30, 5);
_signalBar = Param(nameof(SignalBar), 1)
.SetDisplay("Signal Bar", "Offset for confirmed signal", "Indicator")
.SetNotNegative()
.SetOptimize(0, 2, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).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();
_highs.Clear();
_lows.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var warmup = new ExponentialMovingAverage { Length = 2 * Period + 3 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(warmup, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal _warmupVal)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
var maxLen = 2 * Period + 3;
if (_highs.Count > maxLen)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count < maxLen)
return;
var signal = GetSignal(SignalBar);
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (signal > 0 && Position <= 0)
BuyMarket();
else if (signal < 0 && Position >= 0)
SellMarket();
}
private int GetSignal(int bar)
{
var size = _highs.Count;
var bar1 = bar + 1;
var bar2 = bar + 2;
var bar1p = bar1 + Period;
var bar2p = bar2 + Period;
var h1 = Highest(size - bar1 - Period, Period);
var h2 = Highest(size - bar1p - Period, Period);
var h3 = Highest(size - bar2 - Period, Period);
var h4 = Highest(size - bar2p - Period, Period);
var l1 = Lowest(size - bar1 - Period, Period);
var l2 = Lowest(size - bar1p - Period, Period);
var l3 = Lowest(size - bar2 - Period, Period);
var l4 = Lowest(size - bar2p - Period, Period);
var buy = h3 <= h4 && h1 > h2;
var sell = l3 >= l4 && l1 < l2;
if (buy)
return 1;
if (sell)
return -1;
return 0;
}
private decimal Highest(int start, int length)
{
var max = decimal.MinValue;
for (var i = start; i < start + length; i++)
{
var v = _highs[i];
if (v > max)
max = v;
}
return max;
}
private decimal Lowest(int start, int length)
{
var min = decimal.MaxValue;
for (var i = start; i < start + length; i++)
{
var v = _lows[i];
if (v < min)
min = v;
}
return min;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class le_man_signal_strategy(Strategy):
def __init__(self):
super(le_man_signal_strategy, self).__init__()
self._period = self.Param("Period", 12) \
.SetDisplay("Period", "LeManSignal lookback period", "Indicator")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Bar", "Offset for confirmed signal", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._highs = []
self._lows = []
@property
def period(self):
return self._period.Value
@property
def signal_bar(self):
return self._signal_bar.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(le_man_signal_strategy, self).OnReseted()
self._highs = []
self._lows = []
def OnStarted2(self, time):
super(le_man_signal_strategy, self).OnStarted2(time)
warmup = ExponentialMovingAverage()
warmup.Length = 2 * self.period + 3
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(warmup, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle, _warmup_val):
if candle.State != CandleStates.Finished:
return
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
max_len = 2 * self.period + 3
if len(self._highs) > max_len:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < max_len:
return
signal = self._get_signal(self.signal_bar)
if signal > 0 and self.Position <= 0:
self.BuyMarket()
elif signal < 0 and self.Position >= 0:
self.SellMarket()
def _get_signal(self, bar):
size = len(self._highs)
p = self.period
bar1 = bar + 1
bar2 = bar + 2
bar1p = bar1 + p
bar2p = bar2 + p
h1 = self._highest_range(size - bar1 - p, p)
h2 = self._highest_range(size - bar1p - p, p)
h3 = self._highest_range(size - bar2 - p, p)
h4 = self._highest_range(size - bar2p - p, p)
l1 = self._lowest_range(size - bar1 - p, p)
l2 = self._lowest_range(size - bar1p - p, p)
l3 = self._lowest_range(size - bar2 - p, p)
l4 = self._lowest_range(size - bar2p - p, p)
buy = h3 <= h4 and h1 > h2
sell = l3 >= l4 and l1 < l2
if buy:
return 1
if sell:
return -1
return 0
def _highest_range(self, start, length):
mx = float('-inf')
for i in range(start, start + length):
if self._highs[i] > mx:
mx = self._highs[i]
return mx
def _lowest_range(self, start, length):
mn = float('inf')
for i in range(start, start + length):
if self._lows[i] < mn:
mn = self._lows[i]
return mn
def CreateClone(self):
return le_man_signal_strategy()