Ver no GitHub

FuturePatternMemoryEstratégia

Visão geral

FuturePatternMemoryStrategy é uma versão StockSharp dos clássicos MetaTrader especialistas FutureMA e FutureMACD. Os robôs originais registraram sequências de diferenças de indicadores em arquivos CSV, reutilizaram as estatísticas armazenadas e decidiram se as condições atuais favoreciam rompimentos de alta ou de baixa. Esta versão C# mantém a mesma ideia, mas substitui o sistema de arquivos por um armazém de padrões na memória e expõe cada botão como um parâmetro de estratégia. A estratégia pode operar no spread da média móvel suavizada (a lógica FutureMA) ou no histograma MACD (a lógica FutureMACD).

A estratégia avalia cada vela finalizada em cinco etapas:

  1. Projeção do indicador – calcule o oscilador selecionado (spread MA ou histograma MACD) e normalize-o com um fator de escala configurável. Os valores são discretizados para números inteiros para criar assinaturas de padrões compactos.
  2. Hashing de padrão – mantém uma janela deslizante dos valores normalizados AnalysisBars mais recentes. Cada vez que uma nova barra fecha, a janela é convertida em uma string hash exclusiva que identifica o contexto atual do mercado.
  3. Análise de oscilação histórica – inspecione as velas FractalDepth anteriores, meça a distância entre a abertura mais antiga e os extremos mais altos/mais baixos e converta esses intervalos em pontos. Essas distâncias são as expectativas de recompensa que os robôs originais acumularam em seus arquivos CSV.
  4. Atualização de memória ponderada – a chave hash é usada para recuperar ou criar uma entrada no dicionário de padrões. As expectativas de alta e baixa de take-profit são atualizadas com uma média móvel ponderada controlada por ForgettingFactor, que reproduz o coeficiente de “esquecimento” (zabyvaemost) do código MQL.
  5. Avaliação e execução do sinal – se a expectativa de alta dominar, o padrão foi visto mais de MinimumMatches vezes e o ganho projetado estiver acima de MinimumTakeProfit, a estratégia entra ou aumenta para uma posição longa. O ramo de baixa funciona simetricamente. Os níveis de proteção são derivados das estatísticas armazenadas e opcionalmente acompanhados à medida que a negociação se move a favor.

Notas de conversão

  • Ambos os especialistas MetaTrader são mesclados em uma estratégia configurável por meio do parâmetro Source, permitindo alternar entre o mecanismo baseado em MA e o mecanismo baseado em MACD sem recompilação.
  • A persistência baseada em arquivo foi substituída por um Dictionary<string, PatternStats> que mantém todas as estatísticas na memória durante a execução. Isso evita E/S de arquivo e permanece dentro do modelo sandbox StockSharp.
  • O gerenciamento de posição replica o posicionamento stop/alvo original: o stop usa o swing médio completo, enquanto o take-profit usa StatisticalTakeRatio (o Stat_Take_Profit original). Quando EnableTrailingStop é verdadeiro, o stop é movido em um quarto de passo da distância do lucro, exatamente como o especialista MQL modificou suas ordens.
  • O modo manual (ManualMode) desativa a colocação automatizada de pedidos, mas continua a coletar estatísticas, correspondendo ao comportamento original da sinalização Ruchnik.
  • A ampliação (AllowAddOn) imita o sinalizador dokupka e permite que a estratégia adicione volume sempre que o padrão se repete em uma nova barra.

Lógica de negociação em detalhes

  • Fonte do indicador
    • MA spread: calcula duas médias móveis suavizadas (SMMA 6 e SMMA 24) sobre preços medianos e utiliza sua diferença.
    • MACD histograma: calcula a diferença entre a linha principal MACD e a linha de sinal (configuração 26/12/9 por padrão).
  • Normalização: NormalizationFactor reproduz tocnost; ele dimensiona a diferença bruta antes de convertê-la em uma assinatura inteira. A conversão divide por 100 * MinPriceStep para manter unidades baseadas em pip.
  • Memória de padrões: o dicionário armazena, para cada assinatura, o número de correspondências de alta, a distância média de alta, o número de correspondências de baixa e a distância média de baixa. Os valores são atualizados com a fórmula ponderada (current + input * ForgettingFactor) / (1 + ForgettingFactor).
  • Regras de entrada:
    • Longo: expectativa de alta ≥ expectativa de baixa, correspondências de alta > MinimumMatches, distância de alta esperada > MinimumTakeProfit.
    • Curto: expectativa de baixa ≥ expectativa de alta, correspondências de baixa > MinimumMatches, distância de baixa esperada > MinimumTakeProfit.
  • Gerenciamento de risco: o stop loss é definido como uma oscilação média completa em relação à posição; o take-profit usa StatisticalTakeRatio dessa oscilação. Os trailing stops se movem depois que o preço percorre um quarto da distância, assim como a rotina de trailing original.

Parâmetros

Parâmetro Descrição Padrão
CandleType Prazo principal utilizado para cálculos. Velas de 30 minutos
Source Escolha entre spread MA (FutureMA) e MACD histograma (FutureMACD). MaSpread
FastMaLength / SlowMaLength Comprimentos de SMMA quando Source = MaSpread. 24/06
MacdFastLength / MacdSlowLength / MacdSignalLength MACD períodos em que Source = MacdHistogram. 26/12/9
AnalysisBars Número de barras que formam a assinatura do padrão. 8
FractalDepth Número de velas anteriores usadas para medir distâncias de rompimento. 4
MinimumMatches Número necessário de ocorrências armazenadas antes de realizar uma negociação. 5
MinimumTakeProfit Distância mínima esperada (em pontos) para aceitar o sinal. 30
NormalizationFactor Fator de escala aplicado à diferença do indicador. 10
ForgettingFactor Peso aplicado a novas medições na memória padrão. 1,5
StatisticalTakeRatio Taxa de lucro em relação à oscilação medida. 0,5
EnableTrailingStop Ativa a lógica de trailing stop de um quarto de passo. false
ManualMode Colete estatísticas, mas ignore a colocação de pedidos. false
AllowAddOn Permitir a ampliação quando um padrão se repete. true
Volume Tamanho do pedido usado para entradas. 0,1

Conselhos práticos

  • A estratégia depende de hashes discretizados, então escolha NormalizationFactor e AnalysisBars com cuidado: valores muito grandes produzem assinaturas esparsas, enquanto valores muito pequenos misturam estados distintos.
  • Ao executar negociações ao vivo, considere exportar o dicionário de padrões após a sessão se precisar de persistência entre as execuções.
  • Como a versão MQL armazenou dados por símbolo/período, é recomendado manter uma instância de estratégia dedicada por instrumento/período para evitar contaminação cruzada de estatísticas.
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();
	}
}