Ver no GitHub

Andrew's Pitchfork Strategy

Port of the MetaTrader expert advisor "Andrew's Pitchfork". The original script expected a manually drawn Andrews' Pitchfork object and combined it with momentum, multi-timeframe moving averages and MACD filters. The StockSharp version keeps the indicator stack, replaces the manual drawing with automatic trend detection and recreates the protective logic (multi-entry limits, stop-loss, take-profit, break-even and trailing management).

Strategy logic

  1. Indicators
    • Two Linear Weighted Moving Averages (LWMA) calculated on the typical price of the selected candle series.
    • A Momentum oscillator on the same timeframe, evaluated by the absolute deviation from the equilibrium level 100.
    • A classic MACD (12, 26, 9) signal line pair.
  2. Entry rules
    • Long trades require the fast LWMA to be above the slow LWMA, at least one of the last three momentum deviations to exceed the MomentumBuyThreshold, and the MACD line to be above its signal line.
    • Short trades invert these conditions.
    • The strategy pyramids by repeatedly adding the base Volume while the absolute position is below Volume * MaxPyramids. Opposite signals close the current exposure before opening the new direction.
  3. Risk management
    • Initial stop-loss and take-profit levels are placed in price steps around the entry. Both are updated whenever the position size changes.
    • Break-even logic moves the stop after the price has travelled a configurable number of steps in favour of the position.
    • Trailing stop logic keeps following the most profitable price with an additional padding distance.

Compared with the MQL version, the StockSharp port automatically infers the trend using LWMA slope instead of checking the orientation of a user-drawn Pitchfork object. All other filters (momentum, MACD, multi-order limit) and money management tools were reproduced with StockSharp's high-level API.

Parameters

Name Type Default Description
CandleType DataType 15-minute time frame Primary candle series used by all indicators.
FastMaPeriod int 6 Length of the fast LWMA on typical price.
SlowMaPeriod int 85 Length of the slow LWMA on typical price.
MomentumPeriod int 14 Momentum indicator lookback.
MomentumBuyThreshold decimal 0.3 Minimum |Momentum - 100| for long entries.
MomentumSellThreshold decimal 0.3 Minimum |Momentum - 100| for short entries.
MaxPyramids int 1 Maximum number of base lots allowed in the same direction.
StopLossSteps int 20 Stop-loss distance expressed in price steps.
TakeProfitSteps int 50 Take-profit distance expressed in price steps.
EnableTrailing bool true Enables dynamic trailing stop.
TrailingTriggerSteps int 40 Profit in steps required before the trailing stop activates.
TrailingDistanceSteps int 40 Distance in steps maintained between the price extreme and trailing stop.
TrailingPadSteps int 10 Extra padding applied to the trailing stop.
EnableBreakEven bool true Enables break-even stop adjustment.
BreakEvenTriggerSteps int 30 Profit in steps needed before moving the stop to break-even.
BreakEvenOffsetSteps int 30 Offset in steps beyond entry when break-even is applied.

Notes

  • The strategy requires a valid PriceStep from the selected security to convert step-based distances into prices. If the step is missing the trailing and break-even logic remain dormant.
  • Protective orders (stop and take-profit) are recreated whenever the position size changes, ensuring that scaling-in or reversing aligns the orders with the new exposure.
  • The default parameters match the original EA configuration but can be optimised via the built-in StrategyParam ranges.
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;
	}
}