Yen Trader 05.1 Strategy (C#)
Overview
The Yen Trader 05.1 strategy replicates the original MetaTrader expert advisor that arbitrages the relationship between three currency pairs:
- Trading cross – the instrument that hosts the strategy instance (for example GBPJPY).
- Major pair – typically the base currency of the cross against USD (for example GBPUSD).
- USDJPY – used to confirm the yen leg of the triangle.
A breakout on the major pair combined with confirmation from USDJPY generates the trading signals. Optional RSI, CCI, RVI and moving-average filters refine the entries. Position management supports both averaging and pyramiding, while risk management reproduces the pip/ATR based stop handling from the EA.
Trading Logic
- Breakout detection
LoopBackBarscontrols the lookback window. When it is greater than 1 the strategy checks either:- recent highs/lows (
PriceReference = HighLow), or - closes from
LoopBackBarsbars ago (PriceReference = Close).
- recent highs/lows (
MajorDirectiondefines how the major and the yen legs should move relative to each other when the cross is quoted as major/yen (Left) or yen/major (Right).
- Entry filters
UseRsiFilterrequires RSI above/below 50 depending on the expected trend alignment.UseCciFilterenforces CCI to be positive/negative.UseRviFilterwaits for RVI to cross its signal line. The signal line is a 4-period SMA of the RVI values, just like the MT4 implementation.UseMovingAverageFilterkeeps entries aligned with a configurable moving average (MaMode,MaPeriod).
- Entry style
EntryMode = Bothallows any breakout.EntryMode = Pyramidingonly adds on bullish/bearish candles in the trade direction.EntryMode = Averagingonly adds when the previous candle closed against the position to average down.
- Order sizing
FixedLotSizeplaces a constant volume.- When the fixed lot is zero the strategy uses
BalancePercentLotSizeand the current portfolio value to size trades. MaxOpenPositionslimits the cumulative size (number of additive entries).
- Risk management
- Pip distances (
StopLossPips,TakeProfitPips,BreakEvenPips,ProfitLockPips,TrailingStopPips,TrailingStepPips) are translated viaSecurity.MinPriceStep. - When
EnableAtrLevelsis active, ATR distances replace pips using the daily ATR (AtrCandleType,AtrPeriod) and the respective multipliers. - Stops, take-profits, break-even, profit lock and trailing levels are updated from completed candles, just like the MQL implementation.
CloseOnOppositewill flip existing positions instead of stacking new ones when an opposite breakout appears.AllowHedginglets the strategy add to a position even if an opposite position is still open. Note that StockSharp strategies use net positions, so simultaneous long/short positions are not supported; the flag effectively controls whether the strategy is allowed to increase exposure when the current net position points the other way.
- Pip distances (
Parameters
| Group | Name | Description |
|---|---|---|
| Instruments | MajorSecurity |
Major pair used for breakout confirmation. |
UsdJpySecurity |
USDJPY security for the yen leg confirmation. | |
| Data | CandleType |
Signal timeframe for all three pairs. |
| Filters | MajorDirection |
Alignment between the major pair and the traded cross (Left = major/yen, Right = yen/major). |
PriceReference |
Either high/low breakout or delayed close comparison. | |
LoopBackBars |
Number of historical bars to evaluate the breakout. | |
EntryMode |
Averaging, pyramiding or both. | |
| Indicators | UseRsiFilter, UseCciFilter, UseRviFilter, UseMovingAverageFilter |
Enable/disable additional confirmation filters. |
MaPeriod, MaMode |
Moving average configuration. | |
| Risk | FixedLotSize, BalancePercentLotSize |
Volume controls. |
MaxOpenPositions |
Maximum number of additive entries. | |
StopLossPips, TakeProfitPips, BreakEvenPips, ProfitLockPips, TrailingStopPips, TrailingStepPips |
Pip-based risk distances. | |
EnableAtrLevels, AtrCandleType, AtrPeriod, AtrStopLossMultiplier, AtrTakeProfitMultiplier, AtrTrailingMultiplier, AtrBreakEvenMultiplier, AtrProfitLockMultiplier |
ATR-based risk configuration. | |
| Behaviour | CloseOnOpposite |
Close or flip positions on opposite signals. |
AllowHedging |
Allow entries when an opposite net position exists. |
Usage Notes
- Assign the traded cross security to the strategy
Securityproperty, then setMajorSecurityandUsdJpySecurityto the supporting instruments. - Ensure the portfolio is connected; variable lot sizing requires
Portfolio.CurrentValue. - The strategy expects synchronized candle data for all three instruments. If different exchanges deliver data with different session calendars, consider resampling to a common timeframe.
- ATR calculations subscribe to the configured
AtrCandleType. Keep it aligned with the original EA defaults (daily, 21 periods) for comparable behaviour. - Risk logic operates on closed candles, so protective orders are executed by market exits when the thresholds are breached during the subsequent candle.
Differences vs. MT4 Version
- StockSharp uses aggregated net positions; true hedging (holding long and short simultaneously) is not available.
AllowHedgingsimply controls whether the strategy can flip positions automatically when a new signal appears. - Stop/limit management is implemented with market exits after the thresholds are triggered on candle data. The original EA modifies order stops directly because it operates at tick level.
- The RVI signal line is implemented as a four-period SMA of the RVI values, matching the behaviour of
MODE_SIGNALin 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()