La estrategia de Fuerza de Divisas v1.1 replica el asesor experto de MetaTrader Currency Strength v1.1. Mide la fuerza relativa de las ocho principales divisas (USD, EUR, JPY, CAD, AUD, NZD, GBP, CHF) usando cambios porcentuales diarios de 26 pares FX líquidos. Siempre que la fuerza de dos divisas diverge más allá de un umbral configurable, la estrategia abre una posición en el par de divisas correspondiente en la dirección de la divisa más fuerte.
Mercado y datos
Universo de instrumentos: 26 pares FX mayores y cruzados (USDJPY, USDCAD, AUDUSD, USDCHF, GBPUSD, EURUSD, NZDUSD, EURJPY, EURCAD, EURGBP, EURCHF, EURAUD, EURNZD, AUDNZD, AUDCAD, AUDCHF, AUDJPY, CHFJPY, GBPCHF, GBPAUD, GBPCAD, GBPJPY, CADJPY, NZDJPY, GBPNZD, CADCHF).
Frecuencia de datos: Velas diarias (D1). Solo se procesan velas completadas para mantener cálculos consistentes.
Campos requeridos: Precios de apertura, máximo, mínimo y cierre de cada vela.
Cálculo de la fuerza de divisas
El cambio porcentual diario para cada par se calcula como:
(change) = (Close − Open) / Open × 100
Estos cambios específicos de par se combinan luego en índices de fuerza de divisas:
Cada promedio usa el mismo número de componentes que en el asesor experto original para preservar el esquema de ponderación.
Lógica de trading
Después de que todos los 26 pares reportan una nueva vela diaria terminada, se recalculan las fuerzas.
Para cada par la estrategia compara las dos fuerzas de divisas relevantes. Si la diferencia absoluta supera el parámetro DifferenceThreshold, se genera una señal de operación.
La dirección de la señal sigue a la divisa más fuerte:
Si fuerza de la divisa base > fuerza de la divisa cotizada → comprar el par.
Si fuerza de la divisa base < fuerza de la divisa cotizada → vender el par.
Las operaciones solo se permiten cuando la vela diaria del par concuerda con la señal (cierre por encima de la apertura para compras, cierre por debajo para ventas), reflejando el filtro de tendencia del EA original.
Las posiciones netas existentes se respetan. Si aparece una señal de inversión mientras hay una posición contraria abierta, la estrategia cierra la posición actual y cambia a la nueva dirección con una sola orden de mercado.
Cuando TradeOncePerDay está habilitado, cada par puede entrar largo como máximo una vez por día de trading y entrar corto como máximo una vez por día de trading.
Gestión de riesgo y salidas
El indicador opcional UseSlTp habilita la lógica de stop-loss y take-profit ejecutada en la vela diaria de cada par. Las distancias se definen en pips (StopLossPips, TakeProfitPips).
La lógica protectora evalúa el máximo/mínimo diario de la vela más reciente. Si esos extremos alcanzan los objetivos respectivos, la posición se cierra al precio de mercado en el siguiente paso de evaluación.
Sin SL/TP, las posiciones permanecen abiertas hasta que una señal opuesta fuerza una inversión o la estrategia se detiene manualmente, reflejando el comportamiento del EA fuente.
Parámetros de la estrategia
Parámetro
Descripción
CandleType
Marco temporal para velas (por defecto: diario).
DifferenceThreshold
Diferencia mínima de fuerza (en puntos porcentuales) necesaria para activar una operación.
TradeOncePerDay
Si es true, limita cada par a una entrada larga y una corta por día.
UseSlTp
Habilita la evaluación diaria de los niveles de stop-loss y take-profit.
TakeProfitPips
Distancia del take-profit medida en pips.
StopLossPips
Distancia del stop-loss medida en pips.
Parámetros de pares
Entradas individuales Security para los 26 pares FX. Cada uno debe asignarse antes de iniciar la estrategia.
Volume
Propiedad de la clase base que define el tamaño de la operación (por defecto 0.01 lotes).
Notas de implementación
La estrategia se suscribe a cada par por separado usando la API de suscripción de velas de alto nivel (SubscribeCandles).
El manejo de velas ignora estrictamente las velas incompletas, satisfaciendo las directrices de conversión de StockSharp.
Los cálculos de fuerza y la generación de señales solo funcionan cuando todos los pares reportan la misma fecha de trading, garantizando cestas de divisas sincronizadas.
Los diccionarios internos rastrean las últimas fechas de operación por dirección y almacenan información de entrada para salidas protectoras.
Consejos de uso
Asignar todos los 26 instrumentos antes de iniciar la estrategia; las entradas faltantes lanzan una excepción para evitar cálculos parciales.
Asegurarse de que el proveedor de datos suministre velas diarias para cada par configurado para que las fuerzas de divisas permanezcan sincronizadas.
Ajustar DifferenceThreshold para controlar la frecuencia de señales. Umbrales más pequeños llevan a más operaciones frecuentes pero también más inversiones.
Calibrar los stops basados en pips según la precisión de cotización de su broker; el valor predeterminado asume precios de pips fraccionarios.
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>
/// Strategy that trades based on percentage change momentum of candles.
/// Simplified from the original multi-pair currency strength approach to single-security.
/// </summary>
public class CurrencyStrengthV11Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _differenceThreshold;
private decimal? _prevChange;
private decimal? _prevMomentum;
private decimal _entryPrice;
private DateTimeOffset? _lastTradeTime;
/// <summary>
/// Candle type for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Minimum percentage change to trigger trades.
/// </summary>
public decimal DifferenceThreshold
{
get => _differenceThreshold.Value;
set => _differenceThreshold.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public CurrencyStrengthV11Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strength calculation", "General");
_differenceThreshold = Param(nameof(DifferenceThreshold), 0.2m)
.SetDisplay("Threshold", "Minimum percentage change to trigger trade", "Parameters")
.SetOptimize(0.05m, 1m, 0.05m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevChange = null;
_prevMomentum = null;
_entryPrice = 0m;
_lastTradeTime = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
var change = candle.OpenPrice != 0m
? (candle.ClosePrice - candle.OpenPrice) / candle.OpenPrice * 100m
: 0m;
if (_prevChange == null)
{
_prevChange = change;
return;
}
var momentum = change - _prevChange.Value;
var cooldownPassed = _lastTradeTime is null || candle.CloseTime - _lastTradeTime >= TimeSpan.FromHours(24);
var longSignal = _prevMomentum is decimal prevMomentum && prevMomentum <= DifferenceThreshold && momentum > DifferenceThreshold;
var shortSignal = _prevMomentum is decimal prevMomentum2 && prevMomentum2 >= -DifferenceThreshold && momentum < -DifferenceThreshold;
if (cooldownPassed && longSignal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume > 0m ? Volume : 1m);
_entryPrice = candle.ClosePrice;
_lastTradeTime = candle.CloseTime;
}
else if (cooldownPassed && shortSignal && Position >= 0)
{
if (Position > 0)
SellMarket(Position);
SellMarket(Volume > 0m ? Volume : 1m);
_entryPrice = candle.ClosePrice;
_lastTradeTime = candle.CloseTime;
}
_prevChange = change;
_prevMomentum = momentum;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class currency_strength_v11_strategy(Strategy):
def __init__(self):
super(currency_strength_v11_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._difference_threshold = self.Param("DifferenceThreshold", 0.2)
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def DifferenceThreshold(self):
return self._difference_threshold.Value
@DifferenceThreshold.setter
def DifferenceThreshold(self, value):
self._difference_threshold.Value = value
def OnStarted2(self, time):
super(currency_strength_v11_strategy, self).OnStarted2(time)
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
open_price = float(candle.OpenPrice)
close = float(candle.ClosePrice)
change = (close - open_price) / open_price * 100.0 if open_price != 0.0 else 0.0
if self._prev_change is None:
self._prev_change = change
return
momentum = change - self._prev_change
threshold = float(self.DifferenceThreshold)
long_signal = (self._prev_momentum is not None and
self._prev_momentum <= threshold and
momentum > threshold)
short_signal = (self._prev_momentum is not None and
self._prev_momentum >= -threshold and
momentum < -threshold)
cooldown_passed = (self._last_trade_time is None or
candle.CloseTime - self._last_trade_time >= TimeSpan.FromHours(24))
if cooldown_passed and long_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
vol = float(self.Volume) if float(self.Volume) > 0 else 1.0
self.BuyMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._last_trade_time = candle.CloseTime
elif cooldown_passed and short_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
vol = float(self.Volume) if float(self.Volume) > 0 else 1.0
self.SellMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._last_trade_time = candle.CloseTime
self._prev_change = change
self._prev_momentum = momentum
def OnReseted(self):
super(currency_strength_v11_strategy, self).OnReseted()
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
def CreateClone(self):
return currency_strength_v11_strategy()