日元交易者 05.1 策略(C#)
概述
日元交易者 05.1 策略重现了原始 MetaTrader 智能交易系统,通过三条货币对之间的三角关系寻找突破机会:
- 交易交叉盘 —— 本策略实例绑定的证券(例如 GBPJPY)。
- 主要货币对 —— 交叉盘基准货币与美元的组合(例如 GBPUSD),用于捕捉主驱动趋势。
- USDJPY —— 用于确认日元腿的动量。
当主要货币对出现突破并得到 USDJPY 的同步确认时,策略会在交叉盘上建立头寸。可选的 RSI、CCI、RVI 与均线过滤器帮助筛选信号。仓位管理同时支持金字塔加仓与摊平补仓,风险控制部分完整复刻了 EA 中基于点差和 ATR 的止损逻辑。
交易逻辑
- 突破检测
LoopBackBars指定回溯窗口。当大于 1 时,策略会检查:- 最近的最高价/最低价(
PriceReference = HighLow),或 LoopBackBars根之前的收盘价(PriceReference = Close)。
- 最近的最高价/最低价(
MajorDirection决定主要货币对与日元腿应当如何协同(Left 表示交叉盘报价为主要货币/日元,Right 表示日元/主要货币)。
- 过滤器
UseRsiFilter要求 RSI 位于 50 上方或下方以确认趋势方向。UseCciFilter强制 CCI 为正或为负。UseRviFilter等待 RVI 上穿或下穿其信号线。信号线使用 4 周期简单移动平均,与 MT4 中的MODE_SIGNAL一致。UseMovingAverageFilter通过指定周期与类型的均线确保入场方向顺应趋势。
- 入场模式
EntryMode = Both允许任意突破信号。EntryMode = Pyramiding仅在顺势 K 线后加仓。EntryMode = Averaging仅在逆势 K 线后补仓摊平。
- 仓位管理
FixedLotSize设定固定下单量。- 当固定手数为 0 时,使用
BalancePercentLotSize与组合市值计算动态手数。 MaxOpenPositions限制最大加仓次数(累积仓位规模)。
- 风险控制
- 所有点差参数(
StopLossPips、TakeProfitPips、BreakEvenPips、ProfitLockPips、TrailingStopPips、TrailingStepPips)都会通过Security.MinPriceStep转换为价格距离。 - 启用
EnableAtrLevels后,策略改用 ATR 距离,ATR 通过AtrCandleType与AtrPeriod订阅的 K 线计算,倍数由各个乘数参数控制。 - 止损、止盈、保本、锁盈与跟踪止损均基于收盘数据更新,保持与原 EA 相同的节奏。
CloseOnOpposite在出现反向信号时会平掉已有仓位并按需要反向建仓。AllowHedging允许在持有反向仓位时继续加仓。由于 StockSharp 采用净仓位模型,无法真正同时持有多空仓,该选项仅决定策略是否允许在当前净仓与信号方向相反时直接翻仓。
- 所有点差参数(
参数一览
| 组别 | 名称 | 说明 |
|---|---|---|
| 仪表 | MajorSecurity |
用于确认的主要货币对。 |
UsdJpySecurity |
用于确认日元腿的 USDJPY。 | |
| 数据 | CandleType |
三个货币对共享的信号级别。 |
| 过滤 | MajorDirection |
主要货币对与交叉盘的方向关系。 |
PriceReference |
突破参考类型:高低点或延迟收盘价。 | |
LoopBackBars |
回溯计算的历史根数。 | |
EntryMode |
加仓模式:同时支持、仅金字塔或仅摊平。 | |
| 指标 | UseRsiFilter、UseCciFilter、UseRviFilter、UseMovingAverageFilter |
是否启用各类过滤器。 |
MaPeriod、MaMode |
均线周期与类型。 | |
| 风险 | FixedLotSize、BalancePercentLotSize |
下单量控制。 |
MaxOpenPositions |
最大加仓次数。 | |
StopLossPips 等 |
基于点差的风险参数。 | |
EnableAtrLevels 及 ATR 相关参数 |
基于 ATR 的风险设置。 | |
| 行为 | CloseOnOpposite |
是否在反向信号时平仓。 |
AllowHedging |
是否允许在反向净仓位情况下继续入场。 |
使用建议
- 将交易交叉盘赋给策略的
Security属性,同时配置MajorSecurity与UsdJpySecurity。 - 动态手数需要组合估值,运行前确保投资组合已连接并更新
Portfolio.CurrentValue。 - 请保证三个货币对的 K 线时间轴一致,如来自不同市场,可预先统一到同一时间框架。
- ATR 订阅使用
AtrCandleType指定的级别,保持默认(日线、21 周期)即可与原 EA 行为一致。 - 风险控制基于收盘价执行,触发后通过市价单平仓,模拟 EA 中的止损移动逻辑。
与 MT4 版本的差异
- StockSharp 以净仓位计算,不支持真正的对冲持仓。
AllowHedging仅决定策略是否允许自动翻仓。 - 原 EA 在 tick 级别修改挂单止损,本策略在收盘时检测阈值后以市价单离场。
- RVI 信号线通过对 RVI 值进行四周期 SMA 计算,与 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()