Porte do consultor especialista MetaTrader "Andrew's Pitchfork". O script original esperava um objeto Andrews Pitchfork desenhado manualmente e o combinava com filtros de Momentum, médias móveis de múltiplos períodos e MACD. A versão StockSharp mantém o conjunto de indicadores, substitui o desenho manual por detecção automática de tendência e recria a lógica de proteção (limites de múltiplas entradas, stop-loss, take-profit, ponto de equilíbrio e gerenciamento de trailing).
Lógica da estratégia
Indicadores
Duas Médias Móveis Ponderadas Linearmente (LWMA) calculadas sobre o preço típico da série de candles selecionada.
Um oscilador Momentum no mesmo período, avaliado pelo desvio absoluto do nível de equilíbrio 100.
Um par de linhas de sinal MACD (12, 26, 9) clássico.
Regras de entrada
Trades longos requerem que a LWMA rápida esteja acima da LWMA lenta, pelo menos um dos últimos três desvios de Momentum exceda o MomentumBuyThreshold, e a linha MACD esteja acima de sua linha de sinal.
Trades curtos invertem essas condições.
A estratégia faz pirâmide adicionando repetidamente o Volume base enquanto a posição absoluta estiver abaixo de Volume * MaxPyramids. Sinais opostos fecham a exposição atual antes de abrir a nova direção.
Gestão de risco
Os níveis iniciais de stop-loss e take-profit são colocados em passos de preço ao redor da entrada. Ambos são atualizados quando o tamanho da posição muda.
A lógica de ponto de equilíbrio move o stop após o preço percorrer um número configurável de passos a favor da posição.
A lógica do trailing stop continua seguindo o preço mais lucrativo com uma distância de preenchimento adicional.
Em comparação com a versão MQL, o porte StockSharp infere automaticamente a tendência usando a inclinação da LWMA em vez de verificar a orientação de um objeto Pitchfork desenhado pelo usuário. Todos os outros filtros (Momentum, MACD, limite de múltiplas ordens) e ferramentas de gestão de dinheiro foram reproduzidos com a API de alto nível do StockSharp.
Parâmetros
Nome
Tipo
Padrão
Descrição
CandleType
DataType
Período de 15 minutos
Série de candles principal usada por todos os indicadores.
FastMaPeriod
int
6
Comprimento da LWMA rápida sobre o preço típico.
SlowMaPeriod
int
85
Comprimento da LWMA lenta sobre o preço típico.
MomentumPeriod
int
14
Retrospectiva do indicador Momentum.
MomentumBuyThreshold
decimal
0.3
Mínimo |Momentum - 100| para entradas longas.
MomentumSellThreshold
decimal
0.3
Mínimo |Momentum - 100| para entradas curtas.
MaxPyramids
int
1
Número máximo de lotes base permitidos na mesma direção.
StopLossSteps
int
20
Distância do stop-loss em passos de preço.
TakeProfitSteps
int
50
Distância do take-profit em passos de preço.
EnableTrailing
bool
true
Habilita o trailing stop dinâmico.
TrailingTriggerSteps
int
40
Lucro em passos necessário antes de o trailing stop ser ativado.
TrailingDistanceSteps
int
40
Distância em passos mantida entre o extremo de preço e o trailing stop.
TrailingPadSteps
int
10
Preenchimento extra aplicado ao trailing stop.
EnableBreakEven
bool
true
Habilita o ajuste do stop ao ponto de equilíbrio.
BreakEvenTriggerSteps
int
30
Lucro em passos necessário antes de mover o stop para o ponto de equilíbrio.
BreakEvenOffsetSteps
int
30
Offset em passos além da entrada quando o ponto de equilíbrio é aplicado.
Notas
A estratégia requer um PriceStep válido do ativo selecionado para converter distâncias baseadas em passos em preços. Se o passo estiver faltando, a lógica de trailing e ponto de equilíbrio permanece inativa.
Ordens protetoras (stop e take-profit) são recriadas quando o tamanho da posição muda, garantindo que o escalonamento ou reversão alinhe as ordens com a nova exposição.
Os parâmetros padrão correspondem à configuração original do EA, mas podem ser otimizados por meio dos intervalos StrategyParam integrados.
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 AndrewsPitchforkStrategy : 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 AndrewsPitchforkStrategy()
{
_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;
}
}
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 andrews_pitchfork_strategy(Strategy):
def __init__(self):
super(andrews_pitchfork_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(andrews_pitchfork_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(andrews_pitchfork_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return andrews_pitchfork_strategy()