Port des MetaTrader-Expertenberaters "Andrew's Pitchfork". Das Originalskript erwartete ein manuell gezeichnetes Andrews Pitchfork-Objekt und kombinierte es mit Momentum-, Multi-Timeframe-Moving-Average- und MACD-Filtern. Die StockSharp-Version behält den Indikator-Stack, ersetzt das manuelle Zeichnen durch automatische Trenderkennung und recreiert die Schutzlogik (Mehrfach-Einstiegslimits, Stop-Loss, Take-Profit, Break-even und Trailing-Management).
Strategielogik
Indikatoren
Zwei Linear Gewichtete Gleitende Durchschnitte (LWMA), berechnet auf dem typischen Preis der ausgewählten Kerzenserie.
Ein Momentum-Oszillator auf demselben Zeitrahmen, bewertet durch die absolute Abweichung vom Gleichgewichtsniveau 100.
Ein klassisches MACD (12, 26, 9)-Signallinienpaar.
Einstiegsregeln
Long-Trades erfordern, dass die schnelle LWMA über der langsamen LWMA liegt, mindestens eine der letzten drei Momentum-Abweichungen den MomentumBuyThreshold überschreitet und die MACD-Linie über ihrer Signallinie liegt.
Short-Trades kehren diese Bedingungen um.
Die Strategie pyramidiert durch wiederholtes Hinzufügen des Basisvolumens Volume, solange die absolute Position unter Volume * MaxPyramids liegt. Entgegengesetzte Signale schließen die aktuelle Exposition, bevor die neue Richtung geöffnet wird.
Risikomanagement
Anfängliche Stop-Loss- und Take-Profit-Niveaus werden in Preisschritten um den Einstieg platziert. Beide werden aktualisiert, wenn sich die Positionsgröße ändert.
Break-even-Logik verschiebt den Stop, nachdem der Preis eine konfigurierbare Anzahl von Schritten zugunsten der Position zurückgelegt hat.
Trailing-Stop-Logik folgt weiterhin dem rentabelsten Preis mit einem zusätzlichen Padding-Abstand.
Im Vergleich zur MQL-Version schlussfolgert der StockSharp-Port den Trend automatisch anhand der LWMA-Steigung, anstatt die Ausrichtung eines benutzergezeichneten Pitchfork-Objekts zu prüfen. Alle anderen Filter (Momentum, MACD, Multi-Order-Limit) und Geldmanagement-Tools wurden mit der High-Level-API von StockSharp reproduziert.
Parameter
Name
Typ
Standard
Beschreibung
CandleType
DataType
15-Minuten-Zeitrahmen
Primäre Kerzenserie für alle Indikatoren.
FastMaPeriod
int
6
Länge der schnellen LWMA auf dem typischen Preis.
SlowMaPeriod
int
85
Länge der langsamen LWMA auf dem typischen Preis.
MomentumPeriod
int
14
Momentum-Indikator-Rückblick.
MomentumBuyThreshold
decimal
0.3
Minimum |Momentum - 100| für Long-Einstiege.
MomentumSellThreshold
decimal
0.3
Minimum |Momentum - 100| für Short-Einstiege.
MaxPyramids
int
1
Maximale Anzahl von Basislots in dieselbe Richtung.
StopLossSteps
int
20
Stop-Loss-Abstand in Preisschritten.
TakeProfitSteps
int
50
Take-Profit-Abstand in Preisschritten.
EnableTrailing
bool
true
Aktiviert den dynamischen Trailing Stop.
TrailingTriggerSteps
int
40
Gewinn in Schritten vor Aktivierung des Trailing Stops.
TrailingDistanceSteps
int
40
Abstand in Schritten zwischen Preisextrem und Trailing Stop.
TrailingPadSteps
int
10
Zusätzliches Padding für den Trailing Stop.
EnableBreakEven
bool
true
Aktiviert die Break-even-Stop-Anpassung.
BreakEvenTriggerSteps
int
30
Gewinn in Schritten vor dem Verschieben des Stops auf Break-even.
BreakEvenOffsetSteps
int
30
Offset in Schritten über dem Einstieg bei Break-even-Anwendung.
Hinweise
Die Strategie benötigt einen gültigen PriceStep des ausgewählten Wertpapiers, um schrittbasierte Abstände in Preise umzuwandeln. Fehlt der Schritt, bleibt die Trailing- und Break-even-Logik inaktiv.
Schutzorders (Stop und Take-Profit) werden neu erstellt, wenn sich die Positionsgröße ändert, um sicherzustellen, dass Skalierung oder Umkehrung die Orders an der neuen Exposition ausrichtet.
Die Standardparameter entsprechen der ursprünglichen EA-Konfiguration, können aber über die integrierten StrategyParam-Bereiche optimiert werden.
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()