Die Zwei-Paar-Korrelationsstrategie portiert den MetaTrader-Expertenberater "2-Paar-Korrelation EA" (Paket MQL/52043) auf die StockSharp-Hochebene API. Es überwacht die Geldkurse zweier stark korrelierender Kryptosymbole (BTCUSD als Primärzweig und ETHUSD als Absicherungszweig) und führt einen marktneutralen Handel durch, wenn deren Spread von einem konfigurierbaren Schwellenwert abweicht.
Kernworkflow
Risk Gating – das Portfolioeigenkapital wird kontinuierlich überwacht. Wenn der Rückgang vom historischen Höchstwert MaxDrawdownPercent überschreitet, werden neue Geschäfte ausgesetzt, bis sich das Eigenkapital wieder über RecoveryPercent des Höchstwerts erholt.
Volatilitätsfilter – beide Instrumente speisen einen 5-minütigen Kerzenstrom in einen AverageTrueRange-Indikator der Länge AtrPeriod ein. Der Handel wird übersprungen, wenn einer von ATR den Wert PriceDifferenceThreshold * 0.01 überschreitet, was die „Hohe Volatilitätspause“ aus dem Code MQL nachahmt.
Spread-Erkennung – Die Strategie abonniert Level-1-Daten für beide Instrumente und bewertet die Geld-Preis-Spanne bei jeder Aktualisierung. Bei Bid(BTCUSD) - Bid(ETHUSD) > PriceDifferenceThreshold wird BTCUSD gekauft und ETHUSD verkauft. Wenn der Spread unter -PriceDifferenceThreshold fällt, werden die Positionen umgekehrt (Short BTCUSD, Long ETHUSD).
Dynamische Lot-Größe – das Volumen pro Bein wird aus RiskPercent des aktuellen Portfolio-Eigenkapitals dividiert durch die synthetische Stop-Distanz StopLossPips * PriceStep abgeleitet. Das Ergebnis wird mit den Börsenvolumenbeschränkungen normalisiert, bevor Orders gesendet werden.
Basket-Exit – der gesamte variable Gewinn beider Zweige wird in der Kontowährung erfasst. Sobald es MinimumTotalProfit erreicht, schließt die Strategie das gesamte Paar, unabhängig von der Einstiegsrichtung.
Erforderliche Marktdaten
Level1 (bester Geld-/Briefkurs) sowohl für das primäre Wertpapier (Security) als auch für das Hedge-Wertpapier (SecondSecurity).
Kerzen vom Typ AtrCandleType (standardmäßig 5-Minuten-Zeitrahmen) für dieselben zwei Instrumente zur Versorgung des ATR-Filters.
Stellen Sie sicher, dass die Wertpapiere aussagekräftige Werte für PriceStep, StepPrice, VolumeStep und minimale/maximale Volumenwerte aufweisen, damit die Losgröße und die Gewinnumrechnung das MetaTrader-Verhalten widerspiegeln.
Parameter
Name
Typ
Standard
Beschreibung
SecondSecurity
Security
—
Absicherungsinstrument (ETHUSD im Original EA).
MaxDrawdownPercent
decimal
20
Drawdown-Schwellenwert, der neue Trades pausiert.
RiskPercent
decimal
2
Portfolioanteil, der pro Trade für die Positionsgröße riskiert wird.
PriceDifferenceThreshold
decimal
100
Zur Eröffnung des Paares ist eine Divergenz zwischen Geldkurs und Kurs erforderlich.
MinimumTotalProfit
decimal
0.30
Gewinnziel in Kontowährung für den Abschluss beider Legs.
AtrPeriod
int
14
ATR Länge für den Volatilitätsfilter.
RecoveryPercent
decimal
95
Prozentsatz des Spitzenkapitals, der erforderlich ist, um den Handel nach einem Drawdown wieder aufzunehmen.
StopLossPips
int
50
Synthetischer Stopp, der zur Umwandlung von RiskPercent in Lose verwendet wird.
AtrCandleType
DataType
TimeSpan.FromMinutes(5).TimeFrame()
Für die ATR-Berechnung verwendete Kerzenserie.
Dateien
CS/TwoPairCorrelationStrategy.cs – Strategieumsetzung basierend auf dem High-Level API.
README.md – diese Dokumentation (Englisch).
README_zh.md – Dokumentation auf Chinesisch.
README_ru.md – Dokumentation auf Russisch.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Mean-reversion strategy with ATR volatility filter and drawdown control.
/// Simplified from the two-pair correlation EA to single security.
/// </summary>
public class TwoPairCorrelationStrategy : Strategy
{
private readonly StrategyParam<decimal> _maxDrawdownPercent;
private readonly StrategyParam<decimal> _priceDifferenceThreshold;
private readonly StrategyParam<decimal> _minimumTotalProfit;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private AverageTrueRange _atr;
private SimpleMovingAverage _sma;
private decimal _atrValue;
private decimal _entryPrice;
private decimal _peakEquity;
private bool _tradingPaused;
/// <summary>
/// Maximum drawdown percentage that pauses new entries.
/// </summary>
public decimal MaxDrawdownPercent
{
get => _maxDrawdownPercent.Value;
set => _maxDrawdownPercent.Value = value;
}
/// <summary>
/// Price deviation threshold from SMA for entry.
/// </summary>
public decimal PriceDifferenceThreshold
{
get => _priceDifferenceThreshold.Value;
set => _priceDifferenceThreshold.Value = value;
}
/// <summary>
/// Floating profit target for closing.
/// </summary>
public decimal MinimumTotalProfit
{
get => _minimumTotalProfit.Value;
set => _minimumTotalProfit.Value = value;
}
/// <summary>
/// ATR period for volatility filter.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Candle type for signals and ATR.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public TwoPairCorrelationStrategy()
{
_maxDrawdownPercent = Param(nameof(MaxDrawdownPercent), 20m)
.SetGreaterThanZero()
.SetDisplay("Max Drawdown %", "Maximum drawdown before trading is paused", "Risk")
.SetOptimize(5m, 50m, 5m);
_priceDifferenceThreshold = Param(nameof(PriceDifferenceThreshold), 5m)
.SetGreaterThanZero()
.SetDisplay("Price Deviation", "Distance from SMA required to enter", "Signals")
.SetOptimize(1m, 20m, 1m);
_minimumTotalProfit = Param(nameof(MinimumTotalProfit), 3m)
.SetGreaterThanZero()
.SetDisplay("Profit Target", "Floating profit required to close position", "Risk")
.SetOptimize(1m, 10m, 1m);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Number of candles for volatility filter", "Indicators")
.SetOptimize(5, 40, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle series for signals", "Indicators");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_atr = null;
_sma = null;
_atrValue = 0m;
_entryPrice = 0m;
_peakEquity = 0m;
_tradingPaused = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_peakEquity = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
_atr = new AverageTrueRange { Length = AtrPeriod };
_sma = new SimpleMovingAverage { Length = 20 };
SubscribeCandles(CandleType)
.Bind(_atr, _sma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_atr == null || _sma == null || !_atr.IsFormed || !_sma.IsFormed)
return;
_atrValue = atrValue;
var price = candle.ClosePrice;
// Drawdown control
UpdateDrawdownState();
// Check profit target
if (Position != 0 && _entryPrice > 0m)
{
var pnl = Position > 0
? price - _entryPrice
: _entryPrice - price;
var profitTarget = Math.Max(MinimumTotalProfit, _atrValue * 0.5m);
if (profitTarget > 0m && pnl >= profitTarget)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
_entryPrice = 0m;
return;
}
}
if (_tradingPaused)
return;
if (Position != 0)
return;
var deviation = price - smaValue;
var entryThreshold = Math.Max(PriceDifferenceThreshold, _atrValue);
if (deviation > entryThreshold)
{
SellMarket();
_entryPrice = price;
}
else if (deviation < -entryThreshold)
{
BuyMarket();
_entryPrice = price;
}
}
private void UpdateDrawdownState()
{
if (Portfolio == null)
return;
var equity = Portfolio.CurrentValue ?? Portfolio.BeginValue ?? 0m;
if (equity <= 0m)
return;
if (equity > _peakEquity)
_peakEquity = equity;
if (MaxDrawdownPercent <= 0m || _peakEquity <= 0m)
{
_tradingPaused = false;
return;
}
var drawdown = (_peakEquity - equity) / _peakEquity * 100m;
if (!_tradingPaused && drawdown >= MaxDrawdownPercent)
{
_tradingPaused = true;
}
else if (_tradingPaused && drawdown < MaxDrawdownPercent * 0.5m)
{
_tradingPaused = false;
}
}
}