Estrategia YenTrader051 (C#)
Descripción general
La estrategia YenTrader051 replica el asesor experto original de MetaTrader que arbitra la relación entre tres pares de divisas:
- Cruz negociada – el instrumento que aloja la instancia de la estrategia (por ejemplo GBPJPY).
- Par principal – típicamente la moneda base de la cruz contra USD (por ejemplo GBPUSD).
- USDJPY – usado para confirmar el tramo del yen del triángulo.
Un rompimiento en el par principal combinado con confirmación de USDJPY genera las señales de trading. Filtros opcionales de RSI, CCI, RVI y media móvil refinan las entradas. La gestión de posiciones soporta tanto el promediado como el piramidado, mientras que la gestión del riesgo reproduce el manejo de stops basado en pip/ATR del EA.
Lógica de trading
- Detección de rompimiento
LoopBackBarscontrola la ventana de lookback. Cuando es mayor a 1, la estrategia verifica:- máximos/mínimos recientes (
PriceReference = HighLow), o - cierres de
LoopBackBarsbarras atrás (PriceReference = Close).
- máximos/mínimos recientes (
MajorDirectiondefine cómo el par principal y el tramo del yen deben moverse en relación entre sí cuando la cruz se cotiza como principal/yen (Left) o yen/principal (Right).
- Filtros de entrada
UseRsiFilterrequiere RSI por encima/debajo de 50 dependiendo del alineamiento de tendencia esperado.UseCciFilterobliga a que el CCI sea positivo/negativo.UseRviFilterespera a que el RVI cruce su línea de señal. La línea de señal es una SMA de 4 períodos de los valores del RVI, igual que en la implementación de MT4.UseMovingAverageFiltermantiene las entradas alineadas con una media móvil configurable (MaMode,MaPeriod).
- Estilo de entrada
EntryMode = Bothpermite cualquier rompimiento.EntryMode = Pyramidingsolo agrega en velas alcistas/bajistas en la dirección de la operación.EntryMode = Averagingsolo agrega cuando la vela anterior cerró en contra de la posición para promediar.
- Dimensionamiento de órdenes
FixedLotSizecoloca un volumen constante.- Cuando el lote fijo es cero, la estrategia usa
BalancePercentLotSizey el valor actual del portafolio para dimensionar las operaciones. MaxOpenPositionslimita el tamaño acumulativo (número de entradas aditivas).
- Gestión del riesgo
- Las distancias en pips (
StopLossPips,TakeProfitPips,BreakEvenPips,ProfitLockPips,TrailingStopPips,TrailingStepPips) se traducen medianteSecurity.MinPriceStep. - Cuando
EnableAtrLevelsestá activo, las distancias ATR reemplazan los pips usando el ATR diario (AtrCandleType,AtrPeriod) y los multiplicadores respectivos. - Stops, take-profits, break-even, bloqueo de ganancias y niveles de trailing se actualizan desde velas completadas, igual que en la implementación MQL.
CloseOnOppositecerrará las posiciones existentes en lugar de apilar nuevas cuando aparezca un rompimiento opuesto.AllowHedgingpermite a la estrategia agregar a una posición incluso si hay una posición contraria aún abierta. Ten en cuenta que las estrategias de StockSharp usan posiciones netas, por lo que las posiciones simultáneas larga/corta no son compatibles; el flag controla efectivamente si la estrategia puede aumentar la exposición cuando la posición neta actual apunta en la otra dirección.
- Las distancias en pips (
Parámetros
| Grupo | Nombre | Descripción |
|---|---|---|
| Instrumentos | MajorSecurity |
Par principal usado para confirmación de rompimiento. |
UsdJpySecurity |
Instrumento USDJPY para confirmación del tramo del yen. | |
| Datos | CandleType |
Marco temporal de señal para los tres pares. |
| Filtros | MajorDirection |
Alineación entre el par principal y la cruz negociada (Left = principal/yen, Right = yen/principal). |
PriceReference |
Rompimiento de alto/bajo o comparación de cierre diferido. | |
LoopBackBars |
Número de barras históricas para evaluar el rompimiento. | |
EntryMode |
Promediado, piramidado o ambos. | |
| Indicadores | UseRsiFilter, UseCciFilter, UseRviFilter, UseMovingAverageFilter |
Activar/desactivar filtros de confirmación adicionales. |
MaPeriod, MaMode |
Configuración de media móvil. | |
| Riesgo | FixedLotSize, BalancePercentLotSize |
Controles de volumen. |
MaxOpenPositions |
Número máximo de entradas aditivas. | |
StopLossPips, TakeProfitPips, BreakEvenPips, ProfitLockPips, TrailingStopPips, TrailingStepPips |
Distancias de riesgo basadas en pips. | |
EnableAtrLevels, AtrCandleType, AtrPeriod, AtrStopLossMultiplier, AtrTakeProfitMultiplier, AtrTrailingMultiplier, AtrBreakEvenMultiplier, AtrProfitLockMultiplier |
Configuración de riesgo basada en ATR. | |
| Comportamiento | CloseOnOpposite |
Cerrar o invertir posiciones en señales opuestas. |
AllowHedging |
Permitir entradas cuando existe una posición neta contraria. |
Notas de uso
- Asigna el instrumento de la cruz negociada a la propiedad
Securityde la estrategia, luego estableceMajorSecurityyUsdJpySecuritypara los instrumentos de soporte. - Asegúrate de que el portafolio esté conectado; el dimensionamiento de lotes variables requiere
Portfolio.CurrentValue. - La estrategia espera datos de velas sincronizados para los tres instrumentos. Si diferentes bolsas entregan datos con calendarios de sesión diferentes, considera resamplear a un marco temporal común.
- Los cálculos de ATR se suscriben al
AtrCandleTypeconfigurado. Mantenlo alineado con los valores predeterminados del EA original (diario, 21 períodos) para un comportamiento comparable. - La lógica de riesgo opera en velas cerradas, por lo que las órdenes de protección se ejecutan mediante salidas de mercado cuando los umbrales se violan durante la vela subsiguiente.
Diferencias vs. la versión MT4
- StockSharp usa posiciones netas agregadas; el verdadero hedging (mantener largo y corto simultáneamente) no está disponible.
AllowHedgingsimplemente controla si la estrategia puede invertir posiciones automáticamente cuando aparece una nueva señal. - La gestión de stop/límite se implementa con salidas de mercado después de que los umbrales se activan en los datos de velas. El EA original modifica los stops de las órdenes directamente porque opera a nivel de tick.
- La línea de señal del RVI se implementa como una SMA de cuatro períodos de los valores del RVI, coincidiendo con el comportamiento de
MODE_SIGNALen MT4.
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>
/// Port of the Yen Trader expert advisor that trades a JPY cross with confirmation from a major pair and USDJPY.
/// </summary>
public class YenTrader051Strategy : Strategy
{
private readonly StrategyParam<Security> _majorSecurity;
private readonly StrategyParam<Security> _usdJpySecurity;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<YenTraderMajorDirections> _majorDirection;
private readonly StrategyParam<YenTraderEntryModes> _entryMode;
private readonly StrategyParam<YenTraderPriceReferences> _priceReference;
private readonly StrategyParam<int> _loopBackBars;
private readonly StrategyParam<bool> _useRsiFilter;
private readonly StrategyParam<bool> _useCciFilter;
private readonly StrategyParam<bool> _useRviFilter;
private readonly StrategyParam<bool> _useMovingAverageFilter;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<MovingAverageModes> _maMode;
private readonly StrategyParam<decimal> _fixedLotSize;
private readonly StrategyParam<decimal> _balancePercentLotSize;
private readonly StrategyParam<int> _maxOpenPositions;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _breakEvenPips;
private readonly StrategyParam<int> _profitLockPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<bool> _closeOnOpposite;
private readonly StrategyParam<bool> _allowHedging;
private readonly StrategyParam<bool> _enableAtrLevels;
private readonly StrategyParam<DataType> _atrCandleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrStopLossMultiplier;
private readonly StrategyParam<decimal> _atrTakeProfitMultiplier;
private readonly StrategyParam<decimal> _atrTrailingMultiplier;
private readonly StrategyParam<decimal> _atrBreakEvenMultiplier;
private readonly StrategyParam<decimal> _atrProfitLockMultiplier;
private Highest _majorHighest = null!;
private Lowest _majorLowest = null!;
private RelativeStrengthIndex _majorRsi = null!;
private CommodityChannelIndex _majorCci = null!;
private RelativeVigorIndex _majorRvi = null!;
private SimpleMovingAverage _majorRviSignal = null!;
private IIndicator _majorMa = null!;
private Highest _usdJpyHighest = null!;
private Lowest _usdJpyLowest = null!;
private RelativeStrengthIndex _usdJpyRsi = null!;
private CommodityChannelIndex _usdJpyCci = null!;
private RelativeVigorIndex _usdJpyRvi = null!;
private SimpleMovingAverage _usdJpyRviSignal = null!;
private IIndicator _usdJpyMa = null!;
private AverageTrueRange _atr;
private readonly Queue<decimal> _majorCloses = new();
private readonly Queue<decimal> _usdJpyCloses = new();
private decimal? _majorLastClose;
private decimal? _majorLookbackClose;
private decimal? _majorHighestValue;
private decimal? _majorLowestValue;
private decimal? _majorRsiValue;
private decimal? _majorCciValue;
private decimal? _majorRviValue;
private decimal? _majorRviSignalValue;
private decimal? _majorMaValue;
private decimal? _usdJpyLastClose;
private decimal? _usdJpyLookbackClose;
private decimal? _usdJpyHighestValue;
private decimal? _usdJpyLowestValue;
private decimal? _usdJpyRsiValue;
private decimal? _usdJpyCciValue;
private decimal? _usdJpyRviValue;
private decimal? _usdJpyRviSignalValue;
private decimal? _usdJpyMaValue;
private decimal? _atrValue;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private bool _breakEvenActivated;
private bool _profitLockActivated;
private decimal _highestSinceEntry;
private decimal _lowestSinceEntry;
/// <summary>
/// Initializes a new instance of the <see cref="YenTrader051Strategy"/> class.
/// </summary>
public YenTrader051Strategy()
{
_majorSecurity = Param(nameof(MajorSecurity), default(Security))
.SetDisplay("Major Security", "Major currency pair used for confirmation", "Instruments");
_usdJpySecurity = Param(nameof(UsdJpySecurity), default(Security))
.SetDisplay("USDJPY Security", "USDJPY pair used for confirmation", "Instruments");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Signal Candles", "Primary timeframe for signals", "Data");
_majorDirection = Param(nameof(MajorDirection), YenTraderMajorDirections.Left)
.SetDisplay("Major Direction", "Alignment between major and cross", "Filters");
_entryMode = Param(nameof(EntryMode), YenTraderEntryModes.Both)
.SetDisplay("Entry Mode", "Control averaging or pyramiding behaviour", "Filters");
_priceReference = Param(nameof(PriceReference), YenTraderPriceReferences.Close)
.SetDisplay("Price Reference", "Breakout reference for loop back bars", "Filters");
_loopBackBars = Param(nameof(LoopBackBars), 40)
.SetDisplay("Loop Back Bars", "Number of historical bars for breakout logic", "Filters");
_useRsiFilter = Param(nameof(UseRsiFilter), true)
.SetDisplay("Use RSI", "Enable RSI confirmation filter", "Indicators");
_useCciFilter = Param(nameof(UseCciFilter), false)
.SetDisplay("Use CCI", "Enable CCI confirmation filter", "Indicators");
_useRviFilter = Param(nameof(UseRviFilter), false)
.SetDisplay("Use RVI", "Enable RVI confirmation filter", "Indicators");
_useMovingAverageFilter = Param(nameof(UseMovingAverageFilter), true)
.SetDisplay("Use Moving Average", "Enable moving average confirmation filter", "Indicators");
_maPeriod = Param(nameof(MaPeriod), 34)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Moving average period", "Indicators");
_maMode = Param(nameof(MaMode), MovingAverageModes.Smoothed)
.SetDisplay("MA Mode", "Moving average calculation mode", "Indicators");
_fixedLotSize = Param(nameof(FixedLotSize), 0m)
.SetDisplay("Fixed Volume", "Fixed volume per trade (0 = disabled)", "Risk");
_balancePercentLotSize = Param(nameof(BalancePercentLotSize), 1m)
.SetDisplay("Balance Percent Volume", "Portfolio percent used to size trades when fixed volume is disabled", "Risk");
_maxOpenPositions = Param(nameof(MaxOpenPositions), 1)
.SetDisplay("Max Positions", "Maximum number of additive entries", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 1000)
.SetDisplay("Stop Loss (pips)", "Stop loss distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 5000)
.SetDisplay("Take Profit (pips)", "Take profit distance in pips", "Risk");
_breakEvenPips = Param(nameof(BreakEvenPips), 200)
.SetDisplay("Break Even (pips)", "Distance before moving stop to break even", "Risk");
_profitLockPips = Param(nameof(ProfitLockPips), 200)
.SetDisplay("Profit Lock (pips)", "Distance before locking additional profit", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 200)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 10)
.SetDisplay("Trailing Step (pips)", "Minimum trailing stop step in pips", "Risk");
_closeOnOpposite = Param(nameof(CloseOnOpposite), false)
.SetDisplay("Close On Opposite", "Close current position when opposite signal appears", "Risk");
_allowHedging = Param(nameof(AllowHedging), true)
.SetDisplay("Allow Hedging", "Allow simultaneous trades without closing existing ones", "Risk");
_enableAtrLevels = Param(nameof(EnableAtrLevels), false)
.SetDisplay("Use ATR Levels", "Use ATR based distances instead of pips", "Risk");
_atrCandleType = Param(nameof(AtrCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("ATR Candles", "Timeframe for ATR calculations", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR lookback period", "Risk");
_atrStopLossMultiplier = Param(nameof(AtrStopLossMultiplier), 2m)
.SetDisplay("ATR SL Multiplier", "ATR multiplier for stop loss", "Risk");
_atrTakeProfitMultiplier = Param(nameof(AtrTakeProfitMultiplier), 4m)
.SetDisplay("ATR TP Multiplier", "ATR multiplier for take profit", "Risk");
_atrTrailingMultiplier = Param(nameof(AtrTrailingMultiplier), 1m)
.SetDisplay("ATR Trail Multiplier", "ATR multiplier for trailing stop", "Risk");
_atrBreakEvenMultiplier = Param(nameof(AtrBreakEvenMultiplier), 0.5m)
.SetDisplay("ATR BE Multiplier", "ATR multiplier for break even distance", "Risk");
_atrProfitLockMultiplier = Param(nameof(AtrProfitLockMultiplier), 2m)
.SetDisplay("ATR PL Multiplier", "ATR multiplier for profit lock distance", "Risk");
}
/// <summary>
/// Major pair used for confirmation.
/// </summary>
public Security MajorSecurity
{
get => _majorSecurity.Value;
set => _majorSecurity.Value = value;
}
/// <summary>
/// USDJPY pair used for confirmation.
/// </summary>
public Security UsdJpySecurity
{
get => _usdJpySecurity.Value;
set => _usdJpySecurity.Value = value;
}
/// <summary>
/// Main candle type used for trading signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Relationship between the major pair and the traded cross.
/// </summary>
public YenTraderMajorDirections MajorDirection
{
get => _majorDirection.Value;
set => _majorDirection.Value = value;
}
/// <summary>
/// Entry behaviour when stacking orders.
/// </summary>
public YenTraderEntryModes EntryMode
{
get => _entryMode.Value;
set => _entryMode.Value = value;
}
/// <summary>
/// Price reference used for breakout detection.
/// </summary>
public YenTraderPriceReferences PriceReference
{
get => _priceReference.Value;
set => _priceReference.Value = value;
}
/// <summary>
/// Number of bars used for breakout checks.
/// </summary>
public int LoopBackBars
{
get => _loopBackBars.Value;
set => _loopBackBars.Value = value;
}
/// <summary>
/// Enable RSI confirmation.
/// </summary>
public bool UseRsiFilter
{
get => _useRsiFilter.Value;
set => _useRsiFilter.Value = value;
}
/// <summary>
/// Enable CCI confirmation.
/// </summary>
public bool UseCciFilter
{
get => _useCciFilter.Value;
set => _useCciFilter.Value = value;
}
/// <summary>
/// Enable RVI confirmation.
/// </summary>
public bool UseRviFilter
{
get => _useRviFilter.Value;
set => _useRviFilter.Value = value;
}
/// <summary>
/// Enable moving average confirmation.
/// </summary>
public bool UseMovingAverageFilter
{
get => _useMovingAverageFilter.Value;
set => _useMovingAverageFilter.Value = value;
}
/// <summary>
/// Moving average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Moving average calculation mode.
/// </summary>
public MovingAverageModes MaMode
{
get => _maMode.Value;
set => _maMode.Value = value;
}
/// <summary>
/// Fixed volume per trade.
/// </summary>
public decimal FixedLotSize
{
get => _fixedLotSize.Value;
set => _fixedLotSize.Value = value;
}
/// <summary>
/// Percentage of portfolio balance used when variable sizing is active.
/// </summary>
public decimal BalancePercentLotSize
{
get => _balancePercentLotSize.Value;
set => _balancePercentLotSize.Value = value;
}
/// <summary>
/// Maximum number of additive entries.
/// </summary>
public int MaxOpenPositions
{
get => _maxOpenPositions.Value;
set => _maxOpenPositions.Value = value;
}
/// <summary>
/// Stop loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Break even trigger distance in pips.
/// </summary>
public int BreakEvenPips
{
get => _breakEvenPips.Value;
set => _breakEvenPips.Value = value;
}
/// <summary>
/// Profit lock trigger distance in pips.
/// </summary>
public int ProfitLockPips
{
get => _profitLockPips.Value;
set => _profitLockPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimum trailing stop update step in pips.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Close current position when an opposite signal is generated.
/// </summary>
public bool CloseOnOpposite
{
get => _closeOnOpposite.Value;
set => _closeOnOpposite.Value = value;
}
/// <summary>
/// Allow adding positions even if an opposite trade is still open.
/// </summary>
public bool AllowHedging
{
get => _allowHedging.Value;
set => _allowHedging.Value = value;
}
/// <summary>
/// Use ATR based levels instead of pip distances.
/// </summary>
public bool EnableAtrLevels
{
get => _enableAtrLevels.Value;
set => _enableAtrLevels.Value = value;
}
/// <summary>
/// Candle type used for ATR calculations.
/// </summary>
public DataType AtrCandleType
{
get => _atrCandleType.Value;
set => _atrCandleType.Value = value;
}
/// <summary>
/// ATR lookback period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for stop loss.
/// </summary>
public decimal AtrStopLossMultiplier
{
get => _atrStopLossMultiplier.Value;
set => _atrStopLossMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier for take profit.
/// </summary>
public decimal AtrTakeProfitMultiplier
{
get => _atrTakeProfitMultiplier.Value;
set => _atrTakeProfitMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier for trailing stop distance.
/// </summary>
public decimal AtrTrailingMultiplier
{
get => _atrTrailingMultiplier.Value;
set => _atrTrailingMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier for break even activation.
/// </summary>
public decimal AtrBreakEvenMultiplier
{
get => _atrBreakEvenMultiplier.Value;
set => _atrBreakEvenMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier for profit lock activation.
/// </summary>
public decimal AtrProfitLockMultiplier
{
get => _atrProfitLockMultiplier.Value;
set => _atrProfitLockMultiplier.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
if (MajorSecurity != null)
yield return (MajorSecurity, CandleType);
if (UsdJpySecurity != null)
yield return (UsdJpySecurity, CandleType);
if (EnableAtrLevels && Security != null)
yield return (Security, AtrCandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
InitializeIndicators();
var useSingleSecurity = MajorSecurity == null && UsdJpySecurity == null;
var tradingSubscription = SubscribeCandles(CandleType);
if (useSingleSecurity)
{
// When no separate securities are configured, use the primary security data for all streams.
tradingSubscription.Bind(c =>
{
ProcessMajorCandle(c);
ProcessUsdJpyCandle(c);
ProcessTradingCandle(c);
}).Start();
}
else
{
tradingSubscription.Bind(ProcessTradingCandle).Start();
if (MajorSecurity != null)
{
var majorSubscription = SubscribeCandles(CandleType, true, MajorSecurity);
majorSubscription.Bind(ProcessMajorCandle).Start();
}
if (UsdJpySecurity != null)
{
var usdJpySubscription = SubscribeCandles(CandleType, true, UsdJpySecurity);
usdJpySubscription.Bind(ProcessUsdJpyCandle).Start();
}
}
if (EnableAtrLevels)
{
_atr = new AverageTrueRange { Length = AtrPeriod };
var atrSubscription = SubscribeCandles(AtrCandleType, true, Security);
atrSubscription.Bind(ProcessAtrCandle).Start();
}
else
{
_atr = null;
}
StartProtection(null, null);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, tradingSubscription);
DrawOwnTrades(area);
}
}
private void ProcessMajorCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
UpdateBreakoutIndicators(candle, _majorHighest, _majorLowest, ref _majorHighestValue, ref _majorLowestValue);
UpdateOscillators(candle, _majorRsi, ref _majorRsiValue, _majorCci, ref _majorCciValue, _majorRvi, _majorRviSignal, ref _majorRviValue, ref _majorRviSignalValue);
_majorMaValue = UpdateMovingAverage(_majorMa, candle);
_majorLastClose = candle.ClosePrice;
UpdateLookbackQueue(_majorCloses, LoopBackBars, candle.ClosePrice, ref _majorLookbackClose);
}
private void ProcessUsdJpyCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
UpdateBreakoutIndicators(candle, _usdJpyHighest, _usdJpyLowest, ref _usdJpyHighestValue, ref _usdJpyLowestValue);
UpdateOscillators(candle, _usdJpyRsi, ref _usdJpyRsiValue, _usdJpyCci, ref _usdJpyCciValue, _usdJpyRvi, _usdJpyRviSignal, ref _usdJpyRviValue, ref _usdJpyRviSignalValue);
_usdJpyMaValue = UpdateMovingAverage(_usdJpyMa, candle);
_usdJpyLastClose = candle.ClosePrice;
UpdateLookbackQueue(_usdJpyCloses, LoopBackBars, candle.ClosePrice, ref _usdJpyLookbackClose);
}
private void ProcessAtrCandle(ICandleMessage candle)
{
if (!EnableAtrLevels || _atr == null)
return;
if (candle.State != CandleStates.Finished)
return;
var atrValue = _atr.Process(candle);
if (atrValue.IsFinal)
{
var v = TryGetDecimal(atrValue);
if (v.HasValue)
_atrValue = v.Value;
}
}
private void ProcessTradingCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
UpdateRiskManagement(candle);
if (!IsFormed)
return;
if (!IsSignalReady())
return;
var longSignal = CalculateBreakoutSignal(true);
var shortSignal = CalculateBreakoutSignal(false);
ApplyEntryMode(candle, ref longSignal, ref shortSignal);
ApplyIndicatorFilters(ref longSignal, ref shortSignal);
if (longSignal)
TryEnterLong(candle);
if (shortSignal)
TryEnterShort(candle);
}
private void ApplyEntryMode(ICandleMessage candle, ref bool longSignal, ref bool shortSignal)
{
if (EntryMode == YenTraderEntryModes.Averaging)
{
longSignal &= candle.ClosePrice < candle.OpenPrice;
shortSignal &= candle.ClosePrice > candle.OpenPrice;
}
else if (EntryMode == YenTraderEntryModes.Pyramiding)
{
longSignal &= candle.ClosePrice > candle.OpenPrice;
shortSignal &= candle.ClosePrice < candle.OpenPrice;
}
}
private void ApplyIndicatorFilters(ref bool longSignal, ref bool shortSignal)
{
if (!longSignal && !shortSignal)
return;
if (UseRsiFilter)
{
if (!_majorRsiValue.HasValue || !_usdJpyRsiValue.HasValue)
{
longSignal = false;
shortSignal = false;
}
else if (MajorDirection == YenTraderMajorDirections.Left)
{
longSignal &= _majorRsiValue > 50m && _usdJpyRsiValue > 50m;
shortSignal &= _majorRsiValue < 50m && _usdJpyRsiValue < 50m;
}
else
{
longSignal &= _majorRsiValue < 50m && _usdJpyRsiValue > 50m;
shortSignal &= _majorRsiValue > 50m && _usdJpyRsiValue < 50m;
}
}
if (UseCciFilter && (longSignal || shortSignal))
{
if (!_majorCciValue.HasValue || !_usdJpyCciValue.HasValue)
{
longSignal = false;
shortSignal = false;
}
else if (MajorDirection == YenTraderMajorDirections.Left)
{
longSignal &= _majorCciValue > 0m && _usdJpyCciValue > 0m;
shortSignal &= _majorCciValue < 0m && _usdJpyCciValue < 0m;
}
else
{
longSignal &= _majorCciValue < 0m && _usdJpyCciValue > 0m;
shortSignal &= _majorCciValue > 0m && _usdJpyCciValue < 0m;
}
}
if (UseRviFilter && (longSignal || shortSignal))
{
if (!_majorRviValue.HasValue || !_usdJpyRviValue.HasValue || !_majorRviSignalValue.HasValue || !_usdJpyRviSignalValue.HasValue)
{
longSignal = false;
shortSignal = false;
}
else if (MajorDirection == YenTraderMajorDirections.Left)
{
longSignal &= _majorRviValue > _majorRviSignalValue && _usdJpyRviValue > _usdJpyRviSignalValue;
shortSignal &= _majorRviValue < _majorRviSignalValue && _usdJpyRviValue < _usdJpyRviSignalValue;
}
else
{
longSignal &= _majorRviValue < _majorRviSignalValue && _usdJpyRviValue > _usdJpyRviSignalValue;
shortSignal &= _majorRviValue > _majorRviSignalValue && _usdJpyRviValue < _usdJpyRviSignalValue;
}
}
if (UseMovingAverageFilter && (longSignal || shortSignal))
{
if (!_majorMaValue.HasValue || !_usdJpyMaValue.HasValue || !_majorLastClose.HasValue || !_usdJpyLastClose.HasValue)
{
longSignal = false;
shortSignal = false;
}
else if (MajorDirection == YenTraderMajorDirections.Left)
{
longSignal &= _majorLastClose > _majorMaValue && _usdJpyLastClose > _usdJpyMaValue;
shortSignal &= _majorLastClose < _majorMaValue && _usdJpyLastClose < _usdJpyMaValue;
}
else
{
longSignal &= _majorLastClose < _majorMaValue && _usdJpyLastClose > _usdJpyMaValue;
shortSignal &= _majorLastClose > _majorMaValue && _usdJpyLastClose < _usdJpyMaValue;
}
}
}
private bool CalculateBreakoutSignal(bool isLong)
{
if (_majorLastClose == null || _usdJpyLastClose == null)
return false;
if (LoopBackBars <= 1)
return true;
if (PriceReference == YenTraderPriceReferences.HighLow)
{
if (_majorHighestValue == null || _majorLowestValue == null || _usdJpyHighestValue == null || _usdJpyLowestValue == null)
return false;
return MajorDirection == YenTraderMajorDirections.Left
? isLong
? _majorLastClose > _majorHighestValue && _usdJpyLastClose > _usdJpyHighestValue
: _majorLastClose < _majorLowestValue && _usdJpyLastClose < _usdJpyLowestValue
: isLong
? _majorLastClose < _majorLowestValue && _usdJpyLastClose > _usdJpyHighestValue
: _majorLastClose > _majorHighestValue && _usdJpyLastClose < _usdJpyLowestValue;
}
if (_majorLookbackClose == null || _usdJpyLookbackClose == null)
return false;
return MajorDirection == YenTraderMajorDirections.Left
? isLong
? _majorLastClose > _majorLookbackClose && _usdJpyLastClose > _usdJpyLookbackClose
: _majorLastClose < _majorLookbackClose && _usdJpyLastClose < _usdJpyLookbackClose
: isLong
? _majorLastClose < _majorLookbackClose && _usdJpyLastClose > _usdJpyLookbackClose
: _majorLastClose > _majorLookbackClose && _usdJpyLastClose < _usdJpyLookbackClose;
}
private void TryEnterLong(ICandleMessage candle)
{
if (Position > 0 && MaxOpenPositions <= 1)
return;
if (!AllowHedging && Position < 0)
return;
var orderVolume = GetOrderVolume(candle.ClosePrice, Sides.Buy);
if (orderVolume <= 0m)
return;
var totalVolume = orderVolume;
if (CloseOnOpposite && Position < 0)
totalVolume += Math.Abs(Position);
if (totalVolume <= 0m)
return;
BuyMarket(totalVolume);
InitializePositionState(candle, Sides.Buy);
}
private void TryEnterShort(ICandleMessage candle)
{
if (Position < 0 && MaxOpenPositions <= 1)
return;
if (!AllowHedging && Position > 0)
return;
var orderVolume = GetOrderVolume(candle.ClosePrice, Sides.Sell);
if (orderVolume <= 0m)
return;
var totalVolume = orderVolume;
if (CloseOnOpposite && Position > 0)
totalVolume += Math.Abs(Position);
if (totalVolume <= 0m)
return;
SellMarket(totalVolume);
InitializePositionState(candle, Sides.Sell);
}
private void InitializePositionState(ICandleMessage candle, Sides side)
{
_entryPrice = candle.ClosePrice;
_stopPrice = null;
_takeProfitPrice = null;
_breakEvenActivated = false;
_profitLockActivated = false;
_highestSinceEntry = candle.HighPrice;
_lowestSinceEntry = candle.LowPrice;
var atrDistance = EnableAtrLevels ? _atrValue : null;
var stopDistance = GetDistance(StopLossPips, AtrStopLossMultiplier, atrDistance);
var takeDistance = GetDistance(TakeProfitPips, AtrTakeProfitMultiplier, atrDistance);
if (side == Sides.Buy)
{
if (stopDistance > 0m)
_stopPrice = _entryPrice - stopDistance;
if (takeDistance > 0m)
_takeProfitPrice = _entryPrice + takeDistance;
}
else
{
if (stopDistance > 0m)
_stopPrice = _entryPrice + stopDistance;
if (takeDistance > 0m)
_takeProfitPrice = _entryPrice - takeDistance;
}
}
private void UpdateRiskManagement(ICandleMessage candle)
{
if (Position == 0)
{
ResetPositionState();
return;
}
if (_entryPrice == null)
return;
if (Position > 0)
{
_highestSinceEntry = Math.Max(_highestSinceEntry, candle.HighPrice);
if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
{
SellMarket(Position);
ResetPositionState();
return;
}
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Position);
ResetPositionState();
return;
}
ApplyTrailingRules(candle, Sides.Buy);
}
else
{
_lowestSinceEntry = Math.Min(_lowestSinceEntry, candle.LowPrice);
if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return;
}
ApplyTrailingRules(candle, Sides.Sell);
}
}
private void ApplyTrailingRules(ICandleMessage candle, Sides side)
{
if (_entryPrice == null)
return;
var atrDistance = EnableAtrLevels ? _atrValue : null;
var breakEvenDistance = GetDistance(BreakEvenPips, AtrBreakEvenMultiplier, atrDistance);
var profitLockDistance = GetDistance(ProfitLockPips, AtrProfitLockMultiplier, atrDistance);
var trailingDistance = GetDistance(TrailingStopPips, AtrTrailingMultiplier, atrDistance);
var trailingStep = ConvertPipsToPrice(TrailingStepPips);
if (side == Sides.Buy)
{
if (!_breakEvenActivated && breakEvenDistance > 0m && candle.HighPrice >= _entryPrice + breakEvenDistance)
{
var newStop = (_entryPrice + breakEvenDistance).Value;
_stopPrice = _stopPrice.HasValue ? Math.Max(_stopPrice.Value, newStop) : newStop;
_breakEvenActivated = true;
}
if (!_profitLockActivated && profitLockDistance > 0m && candle.HighPrice >= _entryPrice + profitLockDistance)
{
var newStop = (_entryPrice + profitLockDistance).Value;
_stopPrice = _stopPrice.HasValue ? Math.Max(_stopPrice.Value, newStop) : newStop;
_profitLockActivated = true;
}
if (trailingDistance > 0m)
{
var desiredStop = Math.Max(_entryPrice.Value, candle.HighPrice - trailingDistance);
if (!_stopPrice.HasValue || desiredStop > _stopPrice.Value + trailingStep)
_stopPrice = desiredStop;
}
}
else
{
if (!_breakEvenActivated && breakEvenDistance > 0m && candle.LowPrice <= _entryPrice - breakEvenDistance)
{
var newStop = (_entryPrice - breakEvenDistance).Value;
_stopPrice = _stopPrice.HasValue ? Math.Min(_stopPrice.Value, newStop) : newStop;
_breakEvenActivated = true;
}
if (!_profitLockActivated && profitLockDistance > 0m && candle.LowPrice <= _entryPrice - profitLockDistance)
{
var newStop = (_entryPrice - profitLockDistance).Value;
_stopPrice = _stopPrice.HasValue ? Math.Min(_stopPrice.Value, newStop) : newStop;
_profitLockActivated = true;
}
if (trailingDistance > 0m)
{
var desiredStop = Math.Min(_entryPrice.Value, candle.LowPrice + trailingDistance);
if (!_stopPrice.HasValue || desiredStop < _stopPrice.Value - trailingStep)
_stopPrice = desiredStop;
}
}
}
private decimal GetOrderVolume(decimal price, Sides side)
{
var baseVolume = FixedLotSize > 0m ? FixedLotSize : Volume;
if (FixedLotSize <= 0m && BalancePercentLotSize > 0m && Portfolio != null && price > 0m)
{
var portfolioValue = Portfolio.CurrentValue ?? 0m;
if (portfolioValue > 0m)
baseVolume = portfolioValue * BalancePercentLotSize / 100m / price;
}
var step = Security?.VolumeStep ?? 0m;
if (step > 0m && baseVolume > 0m)
baseVolume = Math.Max(step, Math.Round(baseVolume / step) * step);
if (MaxOpenPositions > 0 && Security != null)
{
var current = side == Sides.Buy ? Math.Max(0m, Position) : Math.Max(0m, -Position);
var maxVolume = baseVolume * MaxOpenPositions;
var available = maxVolume - current;
if (available <= 0m)
return 0m;
baseVolume = Math.Min(baseVolume, available);
}
return Math.Max(0m, baseVolume);
}
private decimal GetDistance(int pips, decimal multiplier, decimal? atrValue)
{
if (EnableAtrLevels && atrValue.HasValue)
return atrValue.Value * multiplier;
if (pips <= 0)
return 0m;
return ConvertPipsToPrice(pips);
}
private decimal ConvertPipsToPrice(int pips)
{
var step = Security?.PriceStep ?? 0m;
return step > 0m ? pips * step : pips;
}
private void ResetPositionState()
{
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
_breakEvenActivated = false;
_profitLockActivated = false;
_highestSinceEntry = 0m;
_lowestSinceEntry = 0m;
}
private void ResetState()
{
_majorCloses.Clear();
_usdJpyCloses.Clear();
_majorLastClose = null;
_majorLookbackClose = null;
_majorHighestValue = null;
_majorLowestValue = null;
_majorRsiValue = null;
_majorCciValue = null;
_majorRviValue = null;
_majorRviSignalValue = null;
_majorMaValue = null;
_usdJpyLastClose = null;
_usdJpyLookbackClose = null;
_usdJpyHighestValue = null;
_usdJpyLowestValue = null;
_usdJpyRsiValue = null;
_usdJpyCciValue = null;
_usdJpyRviValue = null;
_usdJpyRviSignalValue = null;
_usdJpyMaValue = null;
_atrValue = null;
ResetPositionState();
}
private bool IsSignalReady()
{
if (_majorLastClose == null || _usdJpyLastClose == null)
return false;
if (LoopBackBars > 1)
{
if (PriceReference == YenTraderPriceReferences.HighLow)
{
if (_majorHighestValue == null || _majorLowestValue == null || _usdJpyHighestValue == null || _usdJpyLowestValue == null)
return false;
}
else
{
if (_majorLookbackClose == null || _usdJpyLookbackClose == null)
return false;
}
}
if (UseRsiFilter && (!_majorRsiValue.HasValue || !_usdJpyRsiValue.HasValue))
return false;
if (UseCciFilter && (!_majorCciValue.HasValue || !_usdJpyCciValue.HasValue))
return false;
if (UseRviFilter && (!_majorRviValue.HasValue || !_majorRviSignalValue.HasValue || !_usdJpyRviValue.HasValue || !_usdJpyRviSignalValue.HasValue))
return false;
if (UseMovingAverageFilter && (!_majorMaValue.HasValue || !_usdJpyMaValue.HasValue))
return false;
return true;
}
private void InitializeIndicators()
{
var breakoutLength = Math.Max(LoopBackBars, 2);
_majorHighest = new Highest { Length = breakoutLength };
_majorLowest = new Lowest { Length = breakoutLength };
_majorRsi = new RelativeStrengthIndex { Length = 14 };
_majorCci = new CommodityChannelIndex { Length = 14 };
_majorRvi = new RelativeVigorIndex();
_majorRviSignal = new SimpleMovingAverage { Length = 4 };
_majorMa = CreateMovingAverage(MaMode, MaPeriod);
_usdJpyHighest = new Highest { Length = breakoutLength };
_usdJpyLowest = new Lowest { Length = breakoutLength };
_usdJpyRsi = new RelativeStrengthIndex { Length = 14 };
_usdJpyCci = new CommodityChannelIndex { Length = 14 };
_usdJpyRvi = new RelativeVigorIndex();
_usdJpyRviSignal = new SimpleMovingAverage { Length = 4 };
_usdJpyMa = CreateMovingAverage(MaMode, MaPeriod);
}
private static void UpdateBreakoutIndicators(ICandleMessage candle, Highest highest, Lowest lowest, ref decimal? highValue, ref decimal? lowValue)
{
var highVal = highest.Process(candle);
if (highVal.IsFinal)
{
var v = TryGetDecimal(highVal);
if (v.HasValue)
highValue = v.Value;
}
var lowVal = lowest.Process(candle);
if (lowVal.IsFinal)
{
var v = TryGetDecimal(lowVal);
if (v.HasValue)
lowValue = v.Value;
}
}
private static decimal? TryGetDecimal(IIndicatorValue value)
{
if (value == null || value.IsEmpty)
return null;
try
{
return value.ToDecimal();
}
catch
{
return null;
}
}
private static void UpdateOscillators(
ICandleMessage candle,
RelativeStrengthIndex rsi,
ref decimal? rsiValue,
CommodityChannelIndex cci,
ref decimal? cciValue,
RelativeVigorIndex rvi,
SimpleMovingAverage rviSignal,
ref decimal? rviMain,
ref decimal? rviSignalValue)
{
var rsiInput = new DecimalIndicatorValue(rsi, candle.ClosePrice, candle.CloseTime) { IsFinal = true };
var rsiVal = rsi.Process(rsiInput);
if (rsiVal.IsFinal)
{
var v = TryGetDecimal(rsiVal);
if (v.HasValue)
rsiValue = v.Value;
}
var cciVal = cci.Process(candle);
if (cciVal.IsFinal)
{
var v = TryGetDecimal(cciVal);
if (v.HasValue)
cciValue = v.Value;
}
var rviVal = rvi.Process(candle);
if (rviVal.IsFinal)
{
var v = TryGetDecimal(rviVal);
if (v.HasValue)
{
rviMain = v.Value;
var signalVal = rviSignal.Process(new DecimalIndicatorValue(rviSignal, v.Value, candle.CloseTime) { IsFinal = true });
if (signalVal.IsFinal)
{
var sv = TryGetDecimal(signalVal);
if (sv.HasValue)
rviSignalValue = sv.Value;
}
}
}
}
private static decimal? UpdateMovingAverage(IIndicator indicator, ICandleMessage candle)
{
var input = new DecimalIndicatorValue(indicator, candle.ClosePrice, candle.CloseTime) { IsFinal = true };
var value = indicator.Process(input);
return value.IsFinal ? TryGetDecimal(value) : null;
}
private static void UpdateLookbackQueue(Queue<decimal> queue, int loopBackBars, decimal close, ref decimal? lookback)
{
queue.Enqueue(close);
var maxCount = Math.Max(loopBackBars + 1, 2);
while (queue.Count > maxCount)
queue.Dequeue();
if (loopBackBars > 0 && queue.Count > loopBackBars)
{
var values = queue.ToArray();
var index = values.Length - 1 - loopBackBars;
if (index >= 0)
lookback = values[index];
}
else
{
lookback = null;
}
}
private static IIndicator CreateMovingAverage(MovingAverageModes mode, int period)
{
var length = Math.Max(1, period);
return mode switch
{
MovingAverageModes.Simple => new SimpleMovingAverage { Length = length },
MovingAverageModes.Exponential => new ExponentialMovingAverage { Length = length },
MovingAverageModes.Smoothed => new SmoothedMovingAverage { Length = length },
MovingAverageModes.LinearWeighted => new WeightedMovingAverage { Length = length },
_ => new SimpleMovingAverage { Length = length },
};
}
/// <summary>
/// Entry stacking behaviour.
/// </summary>
public enum YenTraderEntryModes
{
/// <summary>Allow both averaging and pyramiding entries.</summary>
Both,
/// <summary>Only add to profitable trades.</summary>
Pyramiding,
/// <summary>Only add to losing trades.</summary>
Averaging
}
/// <summary>
/// Mapping between major pair and traded cross.
/// </summary>
public enum YenTraderMajorDirections
{
/// <summary>Major pair acts as the left component.</summary>
Left,
/// <summary>Major pair acts as the right component.</summary>
Right
}
/// <summary>
/// Breakout reference type.
/// </summary>
public enum YenTraderPriceReferences
{
/// <summary>Use delayed close values.</summary>
Close,
/// <summary>Use highest highs and lowest lows.</summary>
HighLow
}
/// <summary>
/// Moving average calculation modes supported by the strategy.
/// </summary>
public enum MovingAverageModes
{
/// <summary>Simple moving average.</summary>
Simple,
/// <summary>Exponential moving average.</summary>
Exponential,
/// <summary>Smoothed moving average.</summary>
Smoothed,
/// <summary>Linear weighted moving average.</summary>
LinearWeighted
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
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.Strategies import Strategy
from StockSharp.Algo.Indicators import (
SmoothedMovingAverage, RelativeStrengthIndex,
CommodityChannelIndex, CandleIndicatorValue
)
from indicator_extensions import *
from collections import deque
class yen_trader051_strategy(Strategy):
"""Multi-security JPY cross strategy with RSI/CCI/MA confirmation (simplified to single security)."""
def __init__(self):
super(yen_trader051_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Signal Candles", "Primary timeframe for signals", "Data")
self._loop_back_bars = self.Param("LoopBackBars", 40) \
.SetDisplay("Loop Back Bars", "Number of historical bars for breakout logic", "Filters")
self._use_rsi_filter = self.Param("UseRsiFilter", True) \
.SetDisplay("Use RSI", "Enable RSI confirmation filter", "Indicators")
self._use_cci_filter = self.Param("UseCciFilter", False) \
.SetDisplay("Use CCI", "Enable CCI confirmation filter", "Indicators")
self._use_ma_filter = self.Param("UseMovingAverageFilter", True) \
.SetDisplay("Use Moving Average", "Enable moving average confirmation filter", "Indicators")
self._ma_period = self.Param("MaPeriod", 34) \
.SetGreaterThanZero() \
.SetDisplay("MA Period", "Moving average period", "Indicators")
self._stop_loss_pips = self.Param("StopLossPips", 1000) \
.SetDisplay("Stop Loss (pips)", "Stop loss distance in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 5000) \
.SetDisplay("Take Profit (pips)", "Take profit distance in pips", "Risk")
self._break_even_pips = self.Param("BreakEvenPips", 200) \
.SetDisplay("Break Even (pips)", "Distance before moving stop to break even", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 200) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk")
self._trailing_step_pips = self.Param("TrailingStepPips", 10) \
.SetDisplay("Trailing Step (pips)", "Minimum trailing stop step in pips", "Risk")
self._close_on_opposite = self.Param("CloseOnOpposite", False) \
.SetDisplay("Close On Opposite", "Close current position when opposite signal appears", "Risk")
self._allow_hedging = self.Param("AllowHedging", True) \
.SetDisplay("Allow Hedging", "Allow simultaneous trades", "Risk")
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
self._break_even_activated = False
self._highest_since_entry = 0.0
self._lowest_since_entry = 0.0
self._last_close = None
self._closes = deque()
self._rsi_value = None
self._cci_value = None
self._ma_value = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def LoopBackBars(self):
return self._loop_back_bars.Value
@property
def UseRsiFilter(self):
return self._use_rsi_filter.Value
@property
def UseCciFilter(self):
return self._use_cci_filter.Value
@property
def UseMovingAverageFilter(self):
return self._use_ma_filter.Value
@property
def MaPeriod(self):
return self._ma_period.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def BreakEvenPips(self):
return self._break_even_pips.Value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@property
def CloseOnOpposite(self):
return self._close_on_opposite.Value
@property
def AllowHedging(self):
return self._allow_hedging.Value
def OnStarted2(self, time):
super(yen_trader051_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = 14
self._cci = CommodityChannelIndex()
self._cci.Length = 14
self._ma = SmoothedMovingAverage()
self._ma.Length = max(1, self.MaPeriod)
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(self.process_candle) \
.Start()
self.StartProtection(None, None)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi_out = process_float(self._rsi, candle.ClosePrice, candle.CloseTime, True)
if rsi_out.IsFinal:
self._rsi_value = float(rsi_out)
cci_inp = CandleIndicatorValue(self._cci, candle)
cci_inp.IsFinal = True
cci_out = self._cci.Process(cci_inp)
if cci_out.IsFinal:
self._cci_value = float(cci_out)
ma_out = process_float(self._ma, candle.ClosePrice, candle.CloseTime, True)
if ma_out.IsFinal:
self._ma_value = float(ma_out)
self._last_close = close
self._closes.append(close)
max_count = max(self.LoopBackBars + 1, 2)
while len(self._closes) > max_count:
self._closes.popleft()
self._update_risk(candle)
if not self.IsFormed:
return
if self._rsi_value is None or self._cci_value is None or self._ma_value is None:
return
long_sig = self._breakout_check(True)
short_sig = self._breakout_check(False)
if long_sig:
if self.UseRsiFilter and self._rsi_value <= 50:
long_sig = False
if self.UseCciFilter and self._cci_value <= 0:
long_sig = False
if self.UseMovingAverageFilter and self._last_close <= self._ma_value:
long_sig = False
if short_sig:
if self.UseRsiFilter and self._rsi_value >= 50:
short_sig = False
if self.UseCciFilter and self._cci_value >= 0:
short_sig = False
if self.UseMovingAverageFilter and self._last_close >= self._ma_value:
short_sig = False
if long_sig:
self._try_long(candle)
if short_sig:
self._try_short(candle)
def _breakout_check(self, is_long):
if self._last_close is None:
return False
if self.LoopBackBars <= 1:
return True
if len(self._closes) <= self.LoopBackBars:
return False
vals = list(self._closes)
idx = len(vals) - 1 - self.LoopBackBars
if idx < 0:
return False
lb = vals[idx]
return self._last_close > lb if is_long else self._last_close < lb
def _try_long(self, candle):
if not self.AllowHedging and self.Position < 0:
return
vol = self.Volume
if self.CloseOnOpposite and self.Position < 0:
vol += abs(self.Position)
if vol <= 0:
return
self.BuyMarket(vol)
self._init_pos(candle, True)
def _try_short(self, candle):
if not self.AllowHedging and self.Position > 0:
return
vol = self.Volume
if self.CloseOnOpposite and self.Position > 0:
vol += abs(self.Position)
if vol <= 0:
return
self.SellMarket(vol)
self._init_pos(candle, False)
def _init_pos(self, candle, is_long):
close = float(candle.ClosePrice)
self._entry_price = close
self._stop_price = None
self._take_profit_price = None
self._break_even_activated = False
self._highest_since_entry = float(candle.HighPrice)
self._lowest_since_entry = float(candle.LowPrice)
sd = self._pips_to_price(self.StopLossPips)
td = self._pips_to_price(self.TakeProfitPips)
if is_long:
if sd > 0:
self._stop_price = close - sd
if td > 0:
self._take_profit_price = close + td
else:
if sd > 0:
self._stop_price = close + sd
if td > 0:
self._take_profit_price = close - td
def _update_risk(self, candle):
if self.Position == 0:
self._reset_pos()
return
if self._entry_price is None:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
self._highest_since_entry = max(self._highest_since_entry, high)
if self._take_profit_price is not None and high >= self._take_profit_price:
self.SellMarket(self.Position)
self._reset_pos()
return
if self._stop_price is not None and low <= self._stop_price:
self.SellMarket(self.Position)
self._reset_pos()
return
self._trail(candle, True)
else:
self._lowest_since_entry = min(self._lowest_since_entry, low)
if self._take_profit_price is not None and low <= self._take_profit_price:
self.BuyMarket(abs(self.Position))
self._reset_pos()
return
if self._stop_price is not None and high >= self._stop_price:
self.BuyMarket(abs(self.Position))
self._reset_pos()
return
self._trail(candle, False)
def _trail(self, candle, is_long):
if self._entry_price is None:
return
be = self._pips_to_price(self.BreakEvenPips)
td = self._pips_to_price(self.TrailingStopPips)
ts = self._pips_to_price(self.TrailingStepPips)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if is_long:
if not self._break_even_activated and be > 0 and high >= self._entry_price + be:
ns = self._entry_price + be
self._stop_price = max(self._stop_price, ns) if self._stop_price is not None else ns
self._break_even_activated = True
if td > 0:
desired = max(self._entry_price, high - td)
if self._stop_price is None or desired > self._stop_price + ts:
self._stop_price = desired
else:
if not self._break_even_activated and be > 0 and low <= self._entry_price - be:
ns = self._entry_price - be
self._stop_price = min(self._stop_price, ns) if self._stop_price is not None else ns
self._break_even_activated = True
if td > 0:
desired = min(self._entry_price, low + td)
if self._stop_price is None or desired < self._stop_price - ts:
self._stop_price = desired
def _pips_to_price(self, pips):
if pips <= 0:
return 0.0
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
return pips * step
def _reset_pos(self):
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
self._break_even_activated = False
self._highest_since_entry = 0.0
self._lowest_since_entry = 0.0
def OnReseted(self):
super(yen_trader051_strategy, self).OnReseted()
self._last_close = None
self._closes.clear()
self._rsi_value = None
self._cci_value = None
self._ma_value = None
self._reset_pos()
def CreateClone(self):
return yen_trader051_strategy()