Exp Blau CSI 策略
该策略是 MetaTrader 5 专家顾问 Exp_BlauCSI 的 C# 版本,利用 Blau Candle Stochastic Index(CSI)对所选 K 线序列进行分析。策略可以基于指标穿越零轴或趋势转折进行交易,并支持以最小价格步长定义的止损和止盈距离。
交易逻辑
Blau CSI 将价格动量与最近 K 线的高低价范围进行比较,两者均通过三层移动平均进行平滑处理。
- Breakdown 模式:当指标向下穿越零轴时开多,并在前一数值大于零时关闭所有空头;当指标向上穿越零轴时开空,并在前一数值小于零时关闭所有多头。
- Twist 模式:当指标出现由跌转涨的拐点时开多,并关闭空头;当指标由涨转跌时开空,并关闭多头。上一柱的方向始终用于管理已有头寸。
所有信号均基于可配置的历史柱(Signal Bar)进行确认,以确保使用完整收盘的 K 线。
参数
| 参数 | 说明 |
|---|---|
Entry Mode |
选择 Breakdown 或 Twist 交易逻辑。 |
Smoothing Method |
Blau CSI 内部的平滑方式(Simple、Exponential、Smoothed、LinearWeighted、Jurik)。 |
Momentum Length |
计算动量和范围时使用的柱数。 |
First/Second/Third Smoothing |
三层平滑的周期长度。 |
Smoothing Phase |
Jurik 平滑的相位参数(其他方法忽略)。 |
Momentum Price / Reference Price |
动量领先值与滞后值所使用的价格常量(收盘、开盘、高、低、中价、典型价、加权价、均值价、四分价、趋势价、Demark 等)。 |
Signal Bar |
评估指标时向前回溯的柱数,默认 1 表示上一根已收盘 K 线。 |
Stop Loss (pts) |
止损距离,单位为价格步长(0 表示禁用)。 |
Take Profit (pts) |
止盈距离,单位为价格步长(0 表示禁用)。 |
Allow Long/Short Entries |
控制是否允许开多/开空。 |
Allow Long/Short Exits |
控制是否允许平多/平空信号。 |
Candle Type |
订阅的数据类型(默认 4 小时 K 线)。 |
Start Date / End Date |
限制策略参与交易的日期范围。 |
Order Volume |
市价单的下单量。 |
风险控制
开仓时根据交易品种的 PriceStep 计算止损和止盈价位。如果品种没有提供价格步长,策略会自动禁用止损止盈。策略不包含追踪逻辑,头寸在平仓或达到目标前始终保持初始保护水平。
使用说明
- 将策略连接到能提供所需
Candle Type数据的标的。 - 按需求设置指标模式和平滑参数。
- 如需启用止损/止盈,请确保标的具有有效的
PriceStep。 - 通过
Start Date和End Date可限制策略在指定日期范围内运行。
与 MT5 原版的差异
- 使用 StockSharp 指标与策略 API,替代了 MetaTrader 的交易函数。
- 头寸规模控制被简化,直接使用
Order Volume参数。 - 仅支持 StockSharp 提供的平滑方法(Simple、Exponential、Smoothed、LinearWeighted、Jurik),其他 MT5 特定方法自动回退到指数平滑。
- 持仓方向开关与止损止盈逻辑保持与原版一致。
该策略可在 StockSharp Designer、Shell、Runner 或任何自定义的 StockSharp 主程序中进行回测与实盘部署。
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>
/// Blau Candle Stochastic Index strategy converted from MetaTrader 5.
/// </summary>
public class ExpBlauCsiStrategy : Strategy
{
/// <summary>
/// Available entry modes for the Blau CSI strategy.
/// </summary>
public enum BlauCsiEntryModes
{
/// <summary>
/// Use zero level breakdowns as signals.
/// </summary>
Breakdown,
/// <summary>
/// Use direction changes (twists) as signals.
/// </summary>
Twist
}
/// <summary>
/// Applied price constants supported by the Blau CSI indicator.
/// </summary>
public enum BlauCsiAppliedPrices
{
/// <summary>
/// Close price.
/// </summary>
Close = 1,
/// <summary>
/// Open price.
/// </summary>
Open = 2,
/// <summary>
/// High price.
/// </summary>
High = 3,
/// <summary>
/// Low price.
/// </summary>
Low = 4,
/// <summary>
/// Median price = (High + Low) / 2.
/// </summary>
Median = 5,
/// <summary>
/// Typical price = (High + Low + Close) / 3.
/// </summary>
Typical = 6,
/// <summary>
/// Weighted close price = (2 * Close + High + Low) / 4.
/// </summary>
Weighted = 7,
/// <summary>
/// Average of open and close.
/// </summary>
Simple = 8,
/// <summary>
/// Average of open, high, low, and close.
/// </summary>
Quarter = 9,
/// <summary>
/// Trend-following price variant 0.
/// </summary>
TrendFollow0 = 10,
/// <summary>
/// Trend-following price variant 1.
/// </summary>
TrendFollow1 = 11,
/// <summary>
/// Demark price.
/// </summary>
Demark = 12
}
/// <summary>
/// Smoothing methods available for Blau CSI.
/// </summary>
public enum BlauCsiSmoothMethods
{
/// <summary>
/// Simple moving average.
/// </summary>
Simple,
/// <summary>
/// Exponential moving average.
/// </summary>
Exponential,
/// <summary>
/// Smoothed moving average (RMA).
/// </summary>
Smoothed,
/// <summary>
/// Linear weighted moving average.
/// </summary>
LinearWeighted,
/// <summary>
/// Jurik moving average.
/// </summary>
Jurik
}
private readonly StrategyParam<BlauCsiEntryModes> _entryMode;
private readonly StrategyParam<BlauCsiSmoothMethods> _smoothMethod;
private readonly StrategyParam<int> _momentumLength;
private readonly StrategyParam<int> _firstSmoothLength;
private readonly StrategyParam<int> _secondSmoothLength;
private readonly StrategyParam<int> _thirdSmoothLength;
private readonly StrategyParam<int> _smoothingPhase;
private readonly StrategyParam<BlauCsiAppliedPrices> _firstPrice;
private readonly StrategyParam<BlauCsiAppliedPrices> _secondPrice;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<bool> _allowLongEntries;
private readonly StrategyParam<bool> _allowShortEntries;
private readonly StrategyParam<bool> _allowLongExits;
private readonly StrategyParam<bool> _allowShortExits;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DateTimeOffset> _startDate;
private readonly StrategyParam<DateTimeOffset> _endDate;
private BlauCsiIndicator _blauCsi = null!;
private readonly List<decimal> _indicatorValues = new();
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Initializes a new instance of the <see cref="ExpBlauCsiStrategy"/> class.
/// </summary>
public ExpBlauCsiStrategy()
{
_entryMode = Param(nameof(EntryMode), BlauCsiEntryModes.Breakdown)
.SetDisplay("Entry Mode", "Zero cross or direction change logic", "Parameters");
_smoothMethod = Param(nameof(SmoothingMethod), BlauCsiSmoothMethods.Exponential)
.SetDisplay("Smoothing Method", "Moving average type used inside Blau CSI", "Indicator");
_momentumLength = Param(nameof(MomentumLength), 1)
.SetGreaterThanZero()
.SetDisplay("Momentum Length", "Number of bars for momentum calculation", "Indicator")
.SetOptimize(1, 20, 1);
_firstSmoothLength = Param(nameof(FirstSmoothingLength), 10)
.SetGreaterThanZero()
.SetDisplay("First Smoothing", "Depth of first smoothing stage", "Indicator")
.SetOptimize(5, 60, 5);
_secondSmoothLength = Param(nameof(SecondSmoothingLength), 3)
.SetGreaterThanZero()
.SetDisplay("Second Smoothing", "Depth of second smoothing stage", "Indicator")
.SetOptimize(2, 30, 1);
_thirdSmoothLength = Param(nameof(ThirdSmoothingLength), 2)
.SetGreaterThanZero()
.SetDisplay("Third Smoothing", "Depth of third smoothing stage", "Indicator")
.SetOptimize(2, 20, 1);
_smoothingPhase = Param(nameof(SmoothingPhase), 15)
.SetDisplay("Smoothing Phase", "Phase parameter used by Jurik smoothing", "Indicator");
_firstPrice = Param(nameof(FirstPrice), BlauCsiAppliedPrices.Close)
.SetDisplay("Momentum Price", "Price constant for the leading value", "Indicator");
_secondPrice = Param(nameof(SecondPrice), BlauCsiAppliedPrices.Open)
.SetDisplay("Reference Price", "Price constant for the lagging value", "Indicator");
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Offset in bars used to confirm a signal", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss (pts)", "Stop loss distance measured in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit (pts)", "Take profit distance measured in price steps", "Risk");
_allowLongEntries = Param(nameof(AllowLongEntries), true)
.SetDisplay("Allow Long Entries", "Enable opening long positions", "Trading");
_allowShortEntries = Param(nameof(AllowShortEntries), true)
.SetDisplay("Allow Short Entries", "Enable opening short positions", "Trading");
_allowLongExits = Param(nameof(AllowLongExits), true)
.SetDisplay("Allow Long Exits", "Enable closing long positions", "Trading");
_allowShortExits = Param(nameof(AllowShortExits), true)
.SetDisplay("Allow Short Exits", "Enable closing short positions", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for indicator calculations", "General");
_startDate = Param(nameof(StartDate), new DateTimeOffset(2018, 1, 1, 0, 0, 0, TimeSpan.Zero))
.SetDisplay("Start Date", "Backtest start date", "General");
_endDate = Param(nameof(EndDate), new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero))
.SetDisplay("End Date", "Backtest end date", "General");
Volume = 1m;
}
/// <summary>
/// Entry mode determining how Blau CSI generates signals.
/// </summary>
public BlauCsiEntryModes EntryMode
{
get => _entryMode.Value;
set => _entryMode.Value = value;
}
/// <summary>
/// Smoothing method used inside Blau CSI calculation.
/// </summary>
public BlauCsiSmoothMethods SmoothingMethod
{
get => _smoothMethod.Value;
set => _smoothMethod.Value = value;
}
/// <summary>
/// Momentum length controlling the lookback for price difference and range.
/// </summary>
public int MomentumLength
{
get => _momentumLength.Value;
set => _momentumLength.Value = value;
}
/// <summary>
/// First smoothing depth.
/// </summary>
public int FirstSmoothingLength
{
get => _firstSmoothLength.Value;
set => _firstSmoothLength.Value = value;
}
/// <summary>
/// Second smoothing depth.
/// </summary>
public int SecondSmoothingLength
{
get => _secondSmoothLength.Value;
set => _secondSmoothLength.Value = value;
}
/// <summary>
/// Third smoothing depth.
/// </summary>
public int ThirdSmoothingLength
{
get => _thirdSmoothLength.Value;
set => _thirdSmoothLength.Value = value;
}
/// <summary>
/// Phase parameter used by Jurik smoothing.
/// </summary>
public int SmoothingPhase
{
get => _smoothingPhase.Value;
set => _smoothingPhase.Value = value;
}
/// <summary>
/// Price constant for the leading momentum value.
/// </summary>
public ExpBlauCsiStrategy.BlauCsiAppliedPrices FirstPrice
{
get => _firstPrice.Value;
set => _firstPrice.Value = value;
}
/// <summary>
/// Price constant for the lagging momentum value.
/// </summary>
public ExpBlauCsiStrategy.BlauCsiAppliedPrices SecondPrice
{
get => _secondPrice.Value;
set => _secondPrice.Value = value;
}
/// <summary>
/// Offset in bars used when checking the Blau CSI buffer.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Stop loss size expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit size expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Toggle controlling long entries.
/// </summary>
public bool AllowLongEntries
{
get => _allowLongEntries.Value;
set => _allowLongEntries.Value = value;
}
/// <summary>
/// Toggle controlling short entries.
/// </summary>
public bool AllowShortEntries
{
get => _allowShortEntries.Value;
set => _allowShortEntries.Value = value;
}
/// <summary>
/// Toggle controlling long exits.
/// </summary>
public bool AllowLongExits
{
get => _allowLongExits.Value;
set => _allowLongExits.Value = value;
}
/// <summary>
/// Toggle controlling short exits.
/// </summary>
public bool AllowShortExits
{
get => _allowShortExits.Value;
set => _allowShortExits.Value = value;
}
/// <summary>
/// Candle type used to drive the indicator.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Start date filter for trading.
/// </summary>
public DateTimeOffset StartDate
{
get => _startDate.Value;
set => _startDate.Value = value;
}
/// <summary>
/// End date filter for trading.
/// </summary>
public DateTimeOffset EndDate
{
get => _endDate.Value;
set => _endDate.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_indicatorValues.Clear();
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_blauCsi = new BlauCsiIndicator
{
SmoothMethod = SmoothingMethod,
MomentumLength = MomentumLength,
FirstSmoothingLength = FirstSmoothingLength,
SecondSmoothingLength = SecondSmoothingLength,
ThirdSmoothingLength = ThirdSmoothingLength,
Phase = SmoothingPhase,
FirstPrice = FirstPrice,
SecondPrice = SecondPrice
};
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var result = _blauCsi.Process(new CandleIndicatorValue(_blauCsi, candle));
var indicatorValue = result.IsEmpty ? 0m : result.GetValue<decimal>();
if (HandleStops(candle))
return;
var time = candle.OpenTime;
var inRange = time >= StartDate && time <= EndDate;
if (!inRange)
{
if (Position > 0)
{
SellMarket();
ResetTargets();
}
else if (Position < 0)
{
BuyMarket();
ResetTargets();
}
return;
}
StoreIndicatorValue(indicatorValue);
var (openLong, openShort, closeLong, closeShort) = EvaluateSignals();
if (closeLong && AllowLongExits && Position > 0)
{
SellMarket();
ResetTargets();
}
if (closeShort && AllowShortExits && Position < 0)
{
BuyMarket();
ResetTargets();
}
if (openLong && AllowLongEntries && Position <= 0)
{
BuyMarket();
SetTargets(candle.ClosePrice, true);
}
else if (openShort && AllowShortEntries && Position >= 0)
{
SellMarket();
SetTargets(candle.ClosePrice, false);
}
}
private (bool openLong, bool openShort, bool closeLong, bool closeShort) EvaluateSignals()
{
var required = EntryMode == BlauCsiEntryModes.Twist ? 3 : 2;
var count = _indicatorValues.Count;
if (SignalBar < 0)
return (false, false, false, false);
var signalIndex = count - 1 - SignalBar;
if (signalIndex < required - 1)
return (false, false, false, false);
var openLong = false;
var openShort = false;
var closeLong = false;
var closeShort = false;
if (EntryMode == BlauCsiEntryModes.Breakdown)
{
var current = _indicatorValues[signalIndex];
var previous = _indicatorValues[signalIndex - 1];
if (previous > 0m)
{
if (current <= 0m)
openLong = true;
closeShort = true;
}
if (previous < 0m)
{
if (current >= 0m)
openShort = true;
closeLong = true;
}
}
else
{
var current = _indicatorValues[signalIndex];
var previous = _indicatorValues[signalIndex - 1];
var older = _indicatorValues[signalIndex - 2];
if (previous < older)
{
if (current >= previous)
openLong = true;
closeShort = true;
}
if (previous > older)
{
if (current <= previous)
openShort = true;
closeLong = true;
}
}
return (openLong, openShort, closeLong, closeShort);
}
private bool HandleStops(ICandleMessage candle)
{
var triggered = false;
if (Position > 0)
{
if (_stopPrice != null && candle.LowPrice <= _stopPrice)
{
SellMarket();
triggered = true;
}
else if (_takePrice != null && candle.HighPrice >= _takePrice)
{
SellMarket();
triggered = true;
}
}
else if (Position < 0)
{
if (_stopPrice != null && candle.HighPrice >= _stopPrice)
{
BuyMarket();
triggered = true;
}
else if (_takePrice != null && candle.LowPrice <= _takePrice)
{
BuyMarket();
triggered = true;
}
}
if (triggered)
ResetTargets();
return triggered;
}
private void SetTargets(decimal entryPrice, bool isLong)
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
{
_stopPrice = null;
_takePrice = null;
return;
}
_stopPrice = StopLossPoints > 0
? isLong ? entryPrice - StopLossPoints * step : entryPrice + StopLossPoints * step
: null;
_takePrice = TakeProfitPoints > 0
? isLong ? entryPrice + TakeProfitPoints * step : entryPrice - TakeProfitPoints * step
: null;
}
private void ResetTargets()
{
_stopPrice = null;
_takePrice = null;
}
private void StoreIndicatorValue(decimal value)
{
_indicatorValues.Add(value);
var keep = SignalBar + (EntryMode == BlauCsiEntryModes.Twist ? 3 : 2) + 5;
if (keep < 10)
keep = 10;
if (_indicatorValues.Count > keep)
_indicatorValues.RemoveRange(0, _indicatorValues.Count - keep);
}
}
/// <summary>
/// Blau Candle Stochastic Index implementation.
/// </summary>
public class BlauCsiIndicator : BaseIndicator
{
private readonly List<ICandleMessage> _window = new();
private IIndicator _momentumStage1;
private IIndicator _momentumStage2;
private IIndicator _momentumStage3;
private IIndicator _rangeStage1;
private IIndicator _rangeStage2;
private IIndicator _rangeStage3;
/// <summary>
/// Selected smoothing method.
/// </summary>
public ExpBlauCsiStrategy.BlauCsiSmoothMethods SmoothMethod { get; set; } = ExpBlauCsiStrategy.BlauCsiSmoothMethods.Exponential;
/// <summary>
/// Momentum length.
/// </summary>
public int MomentumLength { get; set; } = 1;
/// <summary>
/// First smoothing length.
/// </summary>
public int FirstSmoothingLength { get; set; } = 20;
/// <summary>
/// Second smoothing length.
/// </summary>
public int SecondSmoothingLength { get; set; } = 5;
/// <summary>
/// Third smoothing length.
/// </summary>
public int ThirdSmoothingLength { get; set; } = 3;
/// <summary>
/// Phase parameter for Jurik average.
/// </summary>
public int Phase { get; set; } = 15;
/// <summary>
/// Price constant used for the leading price.
/// </summary>
public ExpBlauCsiStrategy.BlauCsiAppliedPrices FirstPrice { get; set; } = ExpBlauCsiStrategy.BlauCsiAppliedPrices.Close;
/// <summary>
/// Price constant used for the lagging price.
/// </summary>
public ExpBlauCsiStrategy.BlauCsiAppliedPrices SecondPrice { get; set; } = ExpBlauCsiStrategy.BlauCsiAppliedPrices.Open;
/// <inheritdoc />
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
ICandleMessage candle = null;
if (input is CandleIndicatorValue civ)
candle = civ.GetValue<ICandleMessage>(default);
if (candle == null || candle.State != CandleStates.Finished)
return new DecimalIndicatorValue(this, default, input.Time);
if (_momentumStage1 == null)
Initialize();
_window.Add(candle);
while (_window.Count > Math.Max(MomentumLength, 1))
_window.RemoveAt(0);
if (_window.Count < Math.Max(MomentumLength, 1))
{
IsFormed = false;
return new DecimalIndicatorValue(this, default, input.Time);
}
var currentPrice = GetPrice(candle, FirstPrice);
var pastCandle = _window[0];
var pastPrice = GetPrice(pastCandle, SecondPrice);
var min = decimal.MaxValue;
var max = decimal.MinValue;
foreach (var item in _window)
{
if (item.LowPrice < min)
min = item.LowPrice;
if (item.HighPrice > max)
max = item.HighPrice;
}
var range = max - min;
var momentum = currentPrice - pastPrice;
var time = input.Time;
var m1 = _momentumStage1!.Process(new DecimalIndicatorValue(_momentumStage1, momentum, time)).ToDecimal();
var r1 = _rangeStage1!.Process(new DecimalIndicatorValue(_rangeStage1, range, time)).ToDecimal();
var m2 = _momentumStage2!.Process(new DecimalIndicatorValue(_momentumStage2, m1, time)).ToDecimal();
var r2 = _rangeStage2!.Process(new DecimalIndicatorValue(_rangeStage2, r1, time)).ToDecimal();
var m3 = _momentumStage3!.Process(new DecimalIndicatorValue(_momentumStage3, m2, time)).ToDecimal();
var r3 = _rangeStage3!.Process(new DecimalIndicatorValue(_rangeStage3, r2, time)).ToDecimal();
decimal value;
if (r3 != 0m)
value = 100m * m3 / r3;
else
value = 0m;
IsFormed = _momentumStage3.IsFormed && _rangeStage3.IsFormed;
return new DecimalIndicatorValue(this, value, input.Time);
}
private void Initialize()
{
_momentumStage1 = CreateSmoother(FirstSmoothingLength);
_momentumStage2 = CreateSmoother(SecondSmoothingLength);
_momentumStage3 = CreateSmoother(ThirdSmoothingLength);
_rangeStage1 = CreateSmoother(FirstSmoothingLength);
_rangeStage2 = CreateSmoother(SecondSmoothingLength);
_rangeStage3 = CreateSmoother(ThirdSmoothingLength);
}
private IIndicator CreateSmoother(int length)
{
return SmoothMethod switch
{
ExpBlauCsiStrategy.BlauCsiSmoothMethods.Simple => new SimpleMovingAverage { Length = length },
ExpBlauCsiStrategy.BlauCsiSmoothMethods.Smoothed => new SmoothedMovingAverage { Length = length },
ExpBlauCsiStrategy.BlauCsiSmoothMethods.LinearWeighted => new WeightedMovingAverage { Length = length },
ExpBlauCsiStrategy.BlauCsiSmoothMethods.Jurik => new JurikMovingAverage { Length = length, Phase = Phase },
_ => new ExponentialMovingAverage { Length = length }
};
}
private static decimal GetPrice(ICandleMessage candle, ExpBlauCsiStrategy.BlauCsiAppliedPrices price)
{
return price switch
{
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Close => candle.ClosePrice,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Open => candle.OpenPrice,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.High => candle.HighPrice,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Low => candle.LowPrice,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.TrendFollow0 => candle.ClosePrice > candle.OpenPrice
? candle.HighPrice
: candle.ClosePrice < candle.OpenPrice
? candle.LowPrice
: candle.ClosePrice,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice
? (candle.HighPrice + candle.ClosePrice) / 2m
: candle.ClosePrice < candle.OpenPrice
? (candle.LowPrice + candle.ClosePrice) / 2m
: candle.ClosePrice,
ExpBlauCsiStrategy.BlauCsiAppliedPrices.Demark => GetDemarkPrice(candle),
_ => candle.ClosePrice
};
}
private static decimal GetDemarkPrice(ICandleMessage candle)
{
var sum = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
if (candle.ClosePrice < candle.OpenPrice)
sum = (sum + candle.LowPrice) / 2m;
else if (candle.ClosePrice > candle.OpenPrice)
sum = (sum + candle.HighPrice) / 2m;
else
sum = (sum + candle.ClosePrice) / 2m;
return ((sum - candle.LowPrice) + (sum - candle.HighPrice)) / 2m;
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (
SimpleMovingAverage, ExponentialMovingAverage, SmoothedMovingAverage,
WeightedMovingAverage, JurikMovingAverage
)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class exp_blau_csi_strategy(Strategy):
def __init__(self):
super(exp_blau_csi_strategy, self).__init__()
self._entry_mode = self.Param("EntryMode", 0)
self._smooth_method = self.Param("SmoothingMethod", 1)
self._momentum_length = self.Param("MomentumLength", 1)
self._first_smooth_length = self.Param("FirstSmoothingLength", 10)
self._second_smooth_length = self.Param("SecondSmoothingLength", 3)
self._third_smooth_length = self.Param("ThirdSmoothingLength", 2)
self._smoothing_phase = self.Param("SmoothingPhase", 15)
self._first_price = self.Param("FirstPrice", 1)
self._second_price = self.Param("SecondPrice", 2)
self._signal_bar = self.Param("SignalBar", 1)
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._allow_long_entries = self.Param("AllowLongEntries", True)
self._allow_short_entries = self.Param("AllowShortEntries", True)
self._allow_long_exits = self.Param("AllowLongExits", True)
self._allow_short_exits = self.Param("AllowShortExits", True)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._indicator_values = []
self._stop_price = None
self._take_price = None
self._csi_calc = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EntryMode(self):
return self._entry_mode.Value
@property
def SmoothingMethod(self):
return self._smooth_method.Value
@property
def MomentumLength(self):
return self._momentum_length.Value
@property
def FirstSmoothingLength(self):
return self._first_smooth_length.Value
@property
def SecondSmoothingLength(self):
return self._second_smooth_length.Value
@property
def ThirdSmoothingLength(self):
return self._third_smooth_length.Value
@property
def SmoothingPhase(self):
return self._smoothing_phase.Value
@property
def FirstPrice(self):
return self._first_price.Value
@property
def SecondPrice(self):
return self._second_price.Value
@property
def SignalBar(self):
return self._signal_bar.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def AllowLongEntries(self):
return self._allow_long_entries.Value
@property
def AllowShortEntries(self):
return self._allow_short_entries.Value
@property
def AllowLongExits(self):
return self._allow_long_exits.Value
@property
def AllowShortExits(self):
return self._allow_short_exits.Value
def OnStarted2(self, time):
super(exp_blau_csi_strategy, self).OnStarted2(time)
self._indicator_values = []
self._stop_price = None
self._take_price = None
self._csi_calc = _BlauCsiCalc(
self.SmoothingMethod, self.MomentumLength,
self.FirstSmoothingLength, self.SecondSmoothingLength, self.ThirdSmoothingLength,
self.SmoothingPhase, self.FirstPrice, self.SecondPrice
)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
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
val = self._csi_calc.process(candle)
if self._handle_stops(candle):
return
self._store_value(val)
open_long, open_short, close_long, close_short = self._evaluate_signals()
if close_long and self.AllowLongExits and self.Position > 0:
self.SellMarket()
self._reset_targets()
if close_short and self.AllowShortExits and self.Position < 0:
self.BuyMarket()
self._reset_targets()
if open_long and self.AllowLongEntries and self.Position <= 0:
self.BuyMarket()
self._set_targets(float(candle.ClosePrice), True)
elif open_short and self.AllowShortEntries and self.Position >= 0:
self.SellMarket()
self._set_targets(float(candle.ClosePrice), False)
def _evaluate_signals(self):
required = 3 if self.EntryMode == 1 else 2
count = len(self._indicator_values)
if self.SignalBar < 0:
return False, False, False, False
signal_index = count - 1 - self.SignalBar
if signal_index < required - 1:
return False, False, False, False
open_long = False
open_short = False
close_long = False
close_short = False
if self.EntryMode == 0:
current = self._indicator_values[signal_index]
previous = self._indicator_values[signal_index - 1]
if previous > 0:
if current <= 0:
open_long = True
close_short = True
if previous < 0:
if current >= 0:
open_short = True
close_long = True
else:
current = self._indicator_values[signal_index]
previous = self._indicator_values[signal_index - 1]
older = self._indicator_values[signal_index - 2]
if previous < older:
if current >= previous:
open_long = True
close_short = True
if previous > older:
if current <= previous:
open_short = True
close_long = True
return open_long, open_short, close_long, close_short
def _handle_stops(self, candle):
triggered = False
if self.Position > 0:
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket()
triggered = True
elif self._take_price is not None and float(candle.HighPrice) >= self._take_price:
self.SellMarket()
triggered = True
elif self.Position < 0:
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket()
triggered = True
elif self._take_price is not None and float(candle.LowPrice) <= self._take_price:
self.BuyMarket()
triggered = True
if triggered:
self._reset_targets()
return triggered
def _set_targets(self, entry_price, is_long):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0
if step <= 0:
self._stop_price = None
self._take_price = None
return
if self.StopLossPoints > 0:
self._stop_price = entry_price - self.StopLossPoints * step if is_long else entry_price + self.StopLossPoints * step
else:
self._stop_price = None
if self.TakeProfitPoints > 0:
self._take_price = entry_price + self.TakeProfitPoints * step if is_long else entry_price - self.TakeProfitPoints * step
else:
self._take_price = None
def _reset_targets(self):
self._stop_price = None
self._take_price = None
def _store_value(self, value):
self._indicator_values.append(value)
keep = self.SignalBar + (3 if self.EntryMode == 1 else 2) + 5
if keep < 10:
keep = 10
if len(self._indicator_values) > keep:
self._indicator_values = self._indicator_values[-keep:]
def OnReseted(self):
super(exp_blau_csi_strategy, self).OnReseted()
self._indicator_values = []
self._stop_price = None
self._take_price = None
self._csi_calc = None
def CreateClone(self):
return exp_blau_csi_strategy()
class _BlauCsiCalc:
def __init__(self, method, mom_len, s1_len, s2_len, s3_len, phase, price1, price2):
self._mom_len = max(1, mom_len)
self._price1 = price1
self._price2 = price2
self._window = []
self._m1 = self._make_ma(method, max(1, s1_len), phase)
self._m2 = self._make_ma(method, max(1, s2_len), phase)
self._m3 = self._make_ma(method, max(1, s3_len), phase)
self._r1 = self._make_ma(method, max(1, s1_len), phase)
self._r2 = self._make_ma(method, max(1, s2_len), phase)
self._r3 = self._make_ma(method, max(1, s3_len), phase)
def process(self, candle):
current_price = self._get_price(candle, self._price1)
past_price = self._get_price(candle, self._price2)
self._window.append(candle)
while len(self._window) > self._mom_len:
self._window.pop(0)
if len(self._window) < self._mom_len:
return 0.0
past_candle = self._window[0]
past_price = self._get_price(past_candle, self._price2)
lo = float('inf')
hi = float('-inf')
for c in self._window:
lp = float(c.LowPrice)
hp = float(c.HighPrice)
if lp < lo:
lo = lp
if hp > hi:
hi = hp
rng = hi - lo
momentum = current_price - past_price
t = candle.OpenTime
mr1 = process_float(self._m1, momentum, t, True)
rr1 = process_float(self._r1, rng, t, True)
m1v = float(mr1)
r1v = float(rr1)
mr2 = process_float(self._m2, m1v, t, True)
rr2 = process_float(self._r2, r1v, t, True)
m2v = float(mr2)
r2v = float(rr2)
mr3 = process_float(self._m3, m2v, t, True)
rr3 = process_float(self._r3, r2v, t, True)
m3v = float(mr3)
r3v = float(rr3)
if r3v != 0.0:
return 100.0 * m3v / r3v
return 0.0
def _make_ma(self, method, length, phase):
if method == 0:
m = SimpleMovingAverage(); m.Length = length; return m
elif method == 1:
m = ExponentialMovingAverage(); m.Length = length; return m
elif method == 2:
m = SmoothedMovingAverage(); m.Length = length; return m
elif method == 3:
m = WeightedMovingAverage(); m.Length = length; return m
elif method == 4:
m = JurikMovingAverage(); m.Length = length; m.Phase = phase; return m
else:
m = ExponentialMovingAverage(); m.Length = length; return m
def _get_price(self, c, pt):
cl = float(c.ClosePrice); op = float(c.OpenPrice)
hi = float(c.HighPrice); lo = float(c.LowPrice)
if pt == 1: return cl
elif pt == 2: return op
elif pt == 3: return hi
elif pt == 4: return lo
elif pt == 5: return (hi + lo) / 2.0
elif pt == 6: return (hi + lo + cl) / 3.0
elif pt == 7: return (2.0 * cl + hi + lo) / 4.0
elif pt == 8: return (op + cl) / 2.0
elif pt == 9: return (op + cl + hi + lo) / 4.0
elif pt == 10:
return hi if cl > op else (lo if cl < op else cl)
elif pt == 11:
return (hi + cl) / 2.0 if cl > op else ((lo + cl) / 2.0 if cl < op else cl)
elif pt == 12:
r = hi + lo + cl
if cl < op: r = (r + lo) / 2.0
elif cl > op: r = (r + hi) / 2.0
else: r = (r + cl) / 2.0
return ((r - lo) + (r - hi)) / 2.0
else: return cl