using System;
using System.Collections.Generic;
using System.Text;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Pattern memory strategy that records normalized MA spread sequences,
/// tracks fractal outcomes, and trades when a recognized pattern has favorable statistics.
/// </summary>
public class FuturePatternMemoryStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastMaLength;
private readonly StrategyParam<int> _slowMaLength;
private readonly StrategyParam<int> _patternLength;
private readonly StrategyParam<int> _minMatches;
private readonly Queue<int> _patternWindow = new();
private readonly Dictionary<string, (int buyCount, int sellCount)> _patterns = new();
private decimal _entryPrice;
private int _barIndex;
private int _lastEntryBar;
public FuturePatternMemoryStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_fastMaLength = Param(nameof(FastMaLength), 6)
.SetDisplay("Fast MA", "Fast EMA period.", "Indicators");
_slowMaLength = Param(nameof(SlowMaLength), 24)
.SetDisplay("Slow MA", "Slow EMA period.", "Indicators");
_patternLength = Param(nameof(PatternLength), 5)
.SetDisplay("Pattern Length", "Number of bars in pattern signature.", "Pattern");
_minMatches = Param(nameof(MinMatches), 3)
.SetDisplay("Min Matches", "Minimum pattern occurrences before trading.", "Pattern");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastMaLength
{
get => _fastMaLength.Value;
set => _fastMaLength.Value = value;
}
public int SlowMaLength
{
get => _slowMaLength.Value;
set => _slowMaLength.Value = value;
}
public int PatternLength
{
get => _patternLength.Value;
set => _patternLength.Value = value;
}
public int MinMatches
{
get => _minMatches.Value;
set => _minMatches.Value = value;
}
protected override void OnReseted()
{
base.OnReseted();
_patternWindow.Clear();
_patterns.Clear();
_entryPrice = 0;
_barIndex = 0;
_lastEntryBar = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_patternWindow.Clear();
_patterns.Clear();
_entryPrice = 0;
_barIndex = 0;
_lastEntryBar = 0;
var fastEma = new ExponentialMovingAverage { Length = FastMaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowMaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
_barIndex++;
var close = candle.ClosePrice;
// Normalize the MA spread into a discrete value
var spread = fastValue - slowValue;
var normalized = spread > 0 ? 1 : (spread < 0 ? -1 : 0);
_patternWindow.Enqueue(normalized);
while (_patternWindow.Count > PatternLength)
_patternWindow.Dequeue();
if (_patternWindow.Count < PatternLength)
return;
var key = BuildPatternKey(_patternWindow);
// Record outcome: if price went up, it's a buy match, otherwise sell
if (!_patterns.TryGetValue(key, out var stats))
stats = (0, 0);
if (close > fastValue)
stats = (stats.buyCount + 1, stats.sellCount);
else if (close < fastValue)
stats = (stats.buyCount, stats.sellCount + 1);
_patterns[key] = stats;
// Cooldown: minimum SlowMaLength*4 bars between any trade action
var cooldownBars = SlowMaLength * 4;
var cooledDown = _barIndex - _lastEntryBar >= cooldownBars;
// Position management
var exitedThisBar = false;
if (Position > 0)
{
if (cooledDown && (spread < 0 || (_entryPrice > 0 && close < _entryPrice * 0.985m)))
{
SellMarket();
_lastEntryBar = _barIndex;
exitedThisBar = true;
}
}
else if (Position < 0)
{
if (cooledDown && (spread > 0 || (_entryPrice > 0 && close > _entryPrice * 1.015m)))
{
BuyMarket();
_lastEntryBar = _barIndex;
exitedThisBar = true;
}
}
// Entry based on pattern statistics (cooldown between entries)
if (!exitedThisBar && Position == 0 && cooledDown)
{
var total = stats.buyCount + stats.sellCount;
if (total >= MinMatches)
{
if (stats.buyCount > stats.sellCount && spread > 0)
{
_entryPrice = close;
_lastEntryBar = _barIndex;
BuyMarket();
}
else if (stats.sellCount > stats.buyCount && spread < 0)
{
_entryPrice = close;
_lastEntryBar = _barIndex;
SellMarket();
}
}
}
}
private static string BuildPatternKey(IEnumerable<int> values)
{
var sb = new StringBuilder();
var first = true;
foreach (var v in values)
{
if (!first) sb.Append('_');
sb.Append(v);
first = false;
}
return sb.ToString();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from collections import deque
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class future_pattern_memory_strategy(Strategy):
"""
Future Pattern Memory: records normalized MA spread sequences,
tracks outcomes, and trades when a recognized pattern has favorable statistics.
"""
def __init__(self):
super(future_pattern_memory_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._fast_ma_length = self.Param("FastMaLength", 6) \
.SetDisplay("Fast MA", "Fast EMA period", "Indicators")
self._slow_ma_length = self.Param("SlowMaLength", 24) \
.SetDisplay("Slow MA", "Slow EMA period", "Indicators")
self._pattern_length = self.Param("PatternLength", 5) \
.SetDisplay("Pattern Length", "Number of bars in pattern signature", "Pattern")
self._min_matches = self.Param("MinMatches", 3) \
.SetDisplay("Min Matches", "Minimum pattern occurrences before trading", "Pattern")
self._pattern_window = deque()
self._patterns = {}
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(future_pattern_memory_strategy, self).OnReseted()
self._pattern_window = deque()
self._patterns = {}
self._entry_price = 0.0
def OnStarted2(self, time):
super(future_pattern_memory_strategy, self).OnStarted2(time)
self._pattern_window = deque()
self._patterns = {}
self._entry_price = 0.0
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ma_length.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_ma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ema, slow_ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
fast = float(fast_value)
slow = float(slow_value)
spread = fast - slow
normalized = 1 if spread > 0 else (-1 if spread < 0 else 0)
self._pattern_window.append(normalized)
pat_len = self._pattern_length.Value
while len(self._pattern_window) > pat_len:
self._pattern_window.popleft()
if len(self._pattern_window) < pat_len:
return
key = "_".join(str(v) for v in self._pattern_window)
stats = self._patterns.get(key, (0, 0))
buy_count, sell_count = stats
if close > fast:
buy_count += 1
elif close < fast:
sell_count += 1
self._patterns[key] = (buy_count, sell_count)
# Position management
if self.Position > 0:
if spread < 0 or (self._entry_price > 0 and close < self._entry_price * 0.985):
self.SellMarket()
elif self.Position < 0:
if spread > 0 or (self._entry_price > 0 and close > self._entry_price * 1.015):
self.BuyMarket()
# Entry based on pattern statistics
if self.Position == 0:
total = buy_count + sell_count
if total >= self._min_matches.Value:
if buy_count > sell_count and spread > 0:
self._entry_price = close
self.BuyMarket()
elif sell_count > buy_count and spread < 0:
self._entry_price = close
self.SellMarket()
def CreateClone(self):
return future_pattern_memory_strategy()