Ver no GitHub

Estratégia de AeroSpine

Visão geral

A Estratégia de AeroSpine é uma conversão do especialista MetaTrader AEROSPINE.mq4. Ela opera um único símbolo e tenta capturar rompimentos afastados do preço de abertura diária. O robô original foi projetado para gráficos diários enquanto monitorava ticks; o port mantém a ideia de rompimento da abertura diária, mas depende de velas concluídas fornecidas pelo StockSharp.

Lógica de trading

  • No início de cada dia de trading, a estratégia armazena o preço de abertura diária derivado da primeira vela do dia.
  • Novas posições são avaliadas apenas após a hora de entrada configurada. As velas concluídas devem satisfazer um filtro de volume mínimo e o spread atual deve estar abaixo do limite configurado.
  • Se nenhuma posição estiver aberta e nenhum trade de recuperação estiver pendente:
    • Um trade comprado é aberto quando a máxima da vela cruza a abertura diária em EntryOffsetPips.
    • Um trade vendido é aberto quando a mínima da vela rompe abaixo da abertura diária em EntryOffsetPips.
  • Após qualquer trade perdedor, a estratégia prepara uma entrada de recuperação na direção oposta. Trades de recuperação usam RecoveryOffsetPips e aumentam o volume adicionando o volume base ao tamanho do trade perdedor, replicando o dimensionamento estilo martingale do especialista MQL.
  • Posições abertas são gerenciadas com três mecanismos:
    • Um take-profit fixo em TakeProfitPips a partir do preço de entrada.
    • Um gatilho opcional de break-even que fecha o trade quando o preço recua para a distância de break-even após ter se movido a favor da posição.
    • Uma saída protetora se o preço retornar à abertura diária e cruzá-la em ExitOffsetPips contra a posição.

Parâmetros

Nome Descrição
Candle Type Período das velas de trabalho usadas para avaliação de sinais.
Volume Tamanho base da ordem para primeiras entradas e para construir o volume de recuperação.
Entry Hour Hora mínima (horário da bolsa) em que novas entradas podem ser realizadas.
Entry Offset Distância em pips da abertura diária que deve ser cruzada para abrir o primeiro trade do dia.
Exit Offset Distância em pips além da abertura diária usada para fechar posições que revertam além da abertura.
Recovery Offset Distância em pips da abertura diária necessária para acionar um trade de recuperação após uma perda.
Take Profit Distância fixa de take-profit medida em pips a partir do preço de entrada.
Break Even Distância em pips necessária para armar a saída de break-even.
Use Break Even Ativa ou desativa o bloco de gerenciamento de break-even.
Volume Filter Volume mínimo da vela necessário para novas entradas, espelhando a verificação original Volume[0] > 10000.
Max Spread Rejeita novas entradas se o spread atual for mais amplo que o valor permitido (convertido de pips).
Enable Recovery Ativa a lógica de recuperação na direção oposta após um trade perdedor.

Notas sobre a conversão

  • O EA original colocava ordens diretamente em ticks enquanto aplicava um gráfico diário. O port emula isso com velas intradíarias: a abertura diária é atualizada na primeira vela de cada dia e as verificações de rompimento usam as máximas/mínimas das velas.
  • Todos os elementos de interface do MetaTrader (rótulos, cálculos de capital em múltiplos símbolos, etc.) foram removidos. Apenas a lógica de trading relevante para o símbolo atual foi preservada.
  • Break-even e modificações de stop (OrderModify) são simulados via chamadas explícitas a ClosePosition() quando os limites calculados são atingidos.
  • Filtros de spread e volume mapeiam diretamente às verificações originais MODE_SPREAD e Volume[0].
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class AeroSpineStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public AeroSpineStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}