Estrategia de correlación de dos pares
Descripción general
La Estrategia de correlación de dos pares transfiere el asesor experto MetaTrader "Correlación de 2 pares EA" (paquete MQL/52043) al API de alto nivel de StockSharp. Observa los precios de oferta de dos símbolos criptográficos altamente correlacionados (BTCUSD como tramo principal y ETHUSD como tramo de cobertura) y realiza una operación neutral en el mercado cuando su diferencial se desvía de un umbral configurable.
Flujo de trabajo principal
- Control de riesgos: el capital de la cartera se monitorea continuamente. Si la reducción desde el pico histórico supera
MaxDrawdownPercent, las nuevas operaciones se suspenden hasta que el capital se recupere por encima de RecoveryPercent del valor máximo.
- Filtro de volatilidad: ambos instrumentos introducen un flujo de velas de 5 minutos en un indicador
AverageTrueRange de longitud AtrPeriod. Las operaciones se omiten cuando ATR excede PriceDifferenceThreshold * 0.01, imitando la "pausa de alta volatilidad" del código MQL.
- Detección de diferencial: la estrategia se suscribe a datos de nivel uno para ambos instrumentos y evalúa el diferencial de precio de oferta en cada actualización. Cuando
Bid(BTCUSD) - Bid(ETHUSD) > PriceDifferenceThreshold, compra BTCUSD y vende ETHUSD. Cuando el diferencial cae por debajo de -PriceDifferenceThreshold, las posiciones se invierten (BTCUSD corto, ETHUSD largo).
- Tamaño de lote dinámico: el volumen por tramo se deriva de
RiskPercent del capital de la cartera actual, dividido por la distancia de parada sintética StopLossPips * PriceStep. El resultado se normaliza con las restricciones de volumen de intercambio antes de enviar las órdenes.
- Salida de la cesta: el beneficio flotante total de ambos tramos se rastrea en la moneda de la cuenta. Una vez que llega a
MinimumTotalProfit, la estrategia cierra todo el par independientemente de la dirección de entrada.
Datos de mercado requeridos
- Nivel 1 (mejor oferta/demanda) tanto para el valor principal (
Security) como para el valor de cobertura (SecondSecurity).
- Velas de tipo
AtrCandleType (predeterminado en un período de tiempo de 5 minutos) para que los mismos dos instrumentos alimenten el filtro ATR.
Asegúrese de que los valores expongan valores significativos de volumen PriceStep, StepPrice, VolumeStep y mínimo/máximo para que el tamaño del lote y la conversión de ganancias reflejen el comportamiento de MetaTrader.
Parámetros
| Nombre |
Tipo |
Predeterminado |
Descripción |
SecondSecurity |
Security |
— |
Instrumento de cobertura (ETHUSD en el EA original). |
MaxDrawdownPercent |
decimal |
20 |
Umbral de reducción que detiene nuevas operaciones. |
RiskPercent |
decimal |
2 |
Participación de la cartera arriesgada por operación para el tamaño de la posición. |
PriceDifferenceThreshold |
decimal |
100 |
Se requiere divergencia del precio de oferta para abrir el par. |
MinimumTotalProfit |
decimal |
0.30 |
Objetivo de beneficio en la moneda de la cuenta para cerrar ambos tramos. |
AtrPeriod |
int |
14 |
ATR longitud para el filtro de volatilidad. |
RecoveryPercent |
decimal |
95 |
Porcentaje del capital máximo necesario para reanudar la negociación después de una reducción. |
StopLossPips |
int |
50 |
Parada sintética utilizada para traducir RiskPercent en lotes. |
AtrCandleType |
DataType |
TimeSpan.FromMinutes(5).TimeFrame() |
Serie de velas utilizada para el cálculo de ATR. |
Archivos
CS/TwoPairCorrelationStrategy.cs: implementación de estrategia basada en el nivel alto API.
README.md – esta documentación (inglés).
README_zh.md – documentación en chino.
README_ru.md – documentación en ruso.
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;
}
}
}
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 AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class two_pair_correlation_strategy(Strategy):
def __init__(self):
super(two_pair_correlation_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._price_difference_threshold = self.Param("PriceDifferenceThreshold", 5.0)
self._minimum_total_profit = self.Param("MinimumTotalProfit", 3.0)
self._atr_period = self.Param("AtrPeriod", 14)
self._atr_value = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def PriceDifferenceThreshold(self):
return self._price_difference_threshold.Value
@PriceDifferenceThreshold.setter
def PriceDifferenceThreshold(self, value):
self._price_difference_threshold.Value = value
@property
def MinimumTotalProfit(self):
return self._minimum_total_profit.Value
@MinimumTotalProfit.setter
def MinimumTotalProfit(self, value):
self._minimum_total_profit.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
def OnReseted(self):
super(two_pair_correlation_strategy, self).OnReseted()
self._atr_value = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(two_pair_correlation_strategy, self).OnStarted2(time)
self._atr_value = 0.0
self._entry_price = 0.0
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
sma = SimpleMovingAverage()
sma.Length = 20
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, sma, self._process_candle).Start()
def _process_candle(self, candle, atr_value, sma_value):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr_value)
sma_val = float(sma_value)
price = float(candle.ClosePrice)
self._atr_value = atr_val
# Check profit target
if self.Position != 0 and self._entry_price > 0:
if self.Position > 0:
pnl = price - self._entry_price
else:
pnl = self._entry_price - price
profit_target = max(float(self.MinimumTotalProfit), atr_val * 0.5)
if profit_target > 0 and pnl >= profit_target:
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._entry_price = 0.0
return
if self.Position != 0:
return
deviation = price - sma_val
entry_threshold = max(float(self.PriceDifferenceThreshold), atr_val)
if deviation > entry_threshold:
self.SellMarket()
self._entry_price = price
elif deviation < -entry_threshold:
self.BuyMarket()
self._entry_price = price
def CreateClone(self):
return two_pair_correlation_strategy()