Exp Blau CSI Strategy
This strategy is a C# conversion of the MetaTrader 5 expert adviser Exp_BlauCSI. It trades on the Blau Candle Stochastic Index (CSI) calculated on a selected candle series. The strategy can react either to zero-line breakdowns or to direction changes in the indicator and supports configurable stop-loss and take-profit levels measured in price steps.
Trading logic
The Blau CSI compares a momentum component with the high-low range of recent candles. Both parts are smoothed three times using a selected moving average type.
- Breakdown mode – opens a long position when the indicator crosses below zero and closes any shorts while the previous value was positive. Opens a short position on a cross above zero and closes any longs while the previous value was negative.
- Twist mode – opens a long position when the indicator turns upward (value rises compared to the previous bar after declining). Opens a short position when the indicator turns downward. The previous bar direction is always used to close existing positions for the opposite side.
Signals are evaluated on a configurable historical bar (Signal Bar) to ensure confirmation on fully closed candles.
Parameters
| Parameter | Description |
|---|---|
Entry Mode |
Selects Breakdown or Twist logic. |
Smoothing Method |
Moving average type used inside the Blau CSI (Simple, Exponential, Smoothed, LinearWeighted, or Jurik). |
Momentum Length |
Number of bars used to compute the momentum and range components. |
First/Second/Third Smoothing |
Depth of the three smoothing stages applied to momentum and range. |
Smoothing Phase |
Phase parameter for Jurik smoothing (ignored by other methods). |
Momentum Price / Reference Price |
Applied price constants for the leading and lagging momentum values (close, open, high, low, median, typical, weighted, simple, quarter, trend-following, or Demark). |
Signal Bar |
Offset (in bars) used when evaluating the Blau CSI buffer. Default 1 means the previous closed bar. |
Stop Loss (pts) |
Stop-loss distance in price steps (0 disables). |
Take Profit (pts) |
Take-profit distance in price steps (0 disables). |
Allow Long/Short Entries |
Enable or disable opening positions for each direction. |
Allow Long/Short Exits |
Enable or disable exit signals for existing positions. |
Candle Type |
Data type for the subscription (defaults to 4-hour time frame). |
Start Date / End Date |
Date filters for trading activity. |
Order Volume |
Market order volume. |
Risk management
When a new position is opened the strategy calculates stop-loss and take-profit levels using the instrument PriceStep. If the instrument does not provide a step, stops are disabled automatically. Trailing is not performed; each position keeps the initial protective levels until it is closed by a signal or by reaching a target.
Usage notes
- Attach the strategy to a security that provides candle data for the selected
Candle Type. - Choose the indicator mode and smoothing parameters according to your trading plan.
- Ensure the instrument has a valid
PriceStepwhen using stop-loss or take-profit distances. - Optionally restrict trading to a time range using
Start DateandEnd Date.
Differences compared to the original MT5 version
- The implementation uses StockSharp indicators and C# strategy APIs instead of MetaTrader trading functions.
- Lot size management is simplified: order volume is taken directly from the
Order Volumeparameter. - Only the smoothing methods provided by StockSharp are supported (Simple, Exponential, Smoothed, LinearWeighted, Jurik). Unsupported MT5 modes fall back to Exponential smoothing.
- Trade direction toggles and stop management remain compatible with the original behavior.
The strategy is ready for backtesting within StockSharp Designer, Shell, Runner, or any custom StockSharp host application.
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