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>
/// FX-CHAOS scalp strategy adapted to the StockSharp high-level API.
/// </summary>
public class FxChaosScalpStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _tradingCandleType;
private readonly StrategyParam<DataType> _dailyCandleType;
private readonly StrategyParam<int> _zigZagWindowSize;
private AwesomeOscillator _awesomeOscillator;
private FractalZigZagTracker _hourlyTracker;
private FractalZigZagTracker _dailyTracker;
private decimal _previousHigh;
private decimal _previousLow;
private bool _hasPrevious;
private decimal _entryPrice;
private bool _hasEntry;
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public DataType TradingCandleType
{
get => _tradingCandleType.Value;
set => _tradingCandleType.Value = value;
}
public DataType DailyCandleType
{
get => _dailyCandleType.Value;
set => _dailyCandleType.Value = value;
}
public int ZigZagWindowSize
{
get => _zigZagWindowSize.Value;
set
{
var sanitized = Math.Max(3, value);
if ((sanitized & 1) == 0)
sanitized += 1;
_zigZagWindowSize.Value = sanitized;
}
}
public FxChaosScalpStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume in lots", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 50m)
.SetDisplay("Stop Loss (pts)", "Stop loss distance in points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
.SetDisplay("Take Profit (pts)", "Take profit distance in points", "Risk");
_tradingCandleType = Param(nameof(TradingCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Trading Candle", "Primary trading timeframe", "General");
_dailyCandleType = Param(nameof(DailyCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Daily Candle", "Higher timeframe for ZigZag filter", "General");
_zigZagWindowSize = Param(nameof(ZigZagWindowSize), 5)
.SetRange(3, 20)
.SetDisplay("ZigZag Window", "Candle count for ZigZag detection", "Indicators");
_hourlyTracker = null;
_dailyTracker = null;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return
[
(Security, TradingCandleType),
(Security, DailyCandleType)
];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Volume = OrderVolume;
_hourlyTracker = null;
_dailyTracker = null;
_previousHigh = 0m;
_previousLow = 0m;
_hasPrevious = false;
_entryPrice = 0m;
_hasEntry = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = OrderVolume;
_awesomeOscillator = new AwesomeOscillator
{
ShortMa = { Length = 5 },
LongMa = { Length = 34 }
};
_hourlyTracker = new FractalZigZagTracker(ZigZagWindowSize);
_dailyTracker = new FractalZigZagTracker(ZigZagWindowSize);
var dailySubscription = SubscribeCandles(DailyCandleType);
dailySubscription.Bind(ProcessDailyCandle).Start();
var tradingSubscription = SubscribeCandles(TradingCandleType);
tradingSubscription.Bind(ProcessTradingCandleRaw).Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, tradingSubscription);
DrawOwnTrades(area);
}
}
private void ProcessDailyCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Update higher timeframe ZigZag filter.
_dailyTracker.Update(candle);
}
private void ProcessTradingCandleRaw(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Track ZigZag swings for the trading timeframe.
_hourlyTracker.Update(candle);
var aoValue = _awesomeOscillator.Process(candle);
if (!_hasPrevious)
{
UpdatePreviousLevels(candle);
return;
}
if (aoValue.IsEmpty || !_awesomeOscillator.IsFormed)
{
UpdatePreviousLevels(candle);
return;
}
var ao = aoValue.ToDecimal();
var open = candle.OpenPrice;
var close = candle.ClosePrice;
// Evaluate breakout conditions relative to previous levels and AO.
var longSignal = open < _previousHigh && close > _previousHigh && ao < 0m;
var shortSignal = open > _previousLow && close < _previousLow && ao > 0m;
if (longSignal && Position == 0)
{
BuyMarket();
_entryPrice = close;
_hasEntry = true;
}
else if (shortSignal && Position == 0)
{
SellMarket();
_entryPrice = close;
_hasEntry = true;
}
if (Position == 0)
{
_hasEntry = false;
_entryPrice = 0m;
}
UpdatePreviousLevels(candle);
}
private void UpdatePreviousLevels(ICandleMessage candle)
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_hasPrevious = true;
}
private bool ManageRisk(ICandleMessage candle)
{
if (Position == 0)
{
_hasEntry = false;
_entryPrice = 0m;
return false;
}
if (!_hasEntry)
return false;
var step = GetPriceStep();
if (Position > 0)
{
var stop = StopLossPoints > 0m ? _entryPrice - StopLossPoints * step : (decimal?)null;
var take = TakeProfitPoints > 0m ? _entryPrice + TakeProfitPoints * step : (decimal?)null;
if (stop is decimal stopPrice && candle.LowPrice <= stopPrice)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
_hasEntry = false;
_entryPrice = 0m;
return true;
}
if (take is decimal takePrice && candle.HighPrice >= takePrice)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
_hasEntry = false;
_entryPrice = 0m;
return true;
}
}
else if (Position < 0)
{
var stop = StopLossPoints > 0m ? _entryPrice + StopLossPoints * step : (decimal?)null;
var take = TakeProfitPoints > 0m ? _entryPrice - TakeProfitPoints * step : (decimal?)null;
if (stop is decimal stopPrice && candle.HighPrice >= stopPrice)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
_hasEntry = false;
_entryPrice = 0m;
return true;
}
if (take is decimal takePrice && candle.LowPrice <= takePrice)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
_hasEntry = false;
_entryPrice = 0m;
return true;
}
}
return false;
}
private decimal GetPriceStep()
{
var step = Security?.PriceStep;
return step is decimal value && value > 0m ? value : 1m;
}
private sealed class FractalZigZagTracker
{
private readonly int _windowSize;
private readonly CandleInfo[] _window;
private int _count;
private decimal? _lastValue;
private int _direction;
public FractalZigZagTracker(int windowSize)
{
if (windowSize < 3)
windowSize = 3;
if ((windowSize & 1) == 0)
windowSize += 1;
_windowSize = windowSize;
_window = new CandleInfo[_windowSize];
}
public decimal? LastValue => _lastValue;
public void Reset()
{
Array.Clear(_window, 0, _window.Length);
_count = 0;
_lastValue = null;
_direction = 0;
}
public decimal? Update(ICandleMessage candle)
{
if (_count < _windowSize)
{
_window[_count++] = new CandleInfo(candle.HighPrice, candle.LowPrice);
if (_count < _windowSize)
return _lastValue;
Evaluate();
return _lastValue;
}
for (var i = 0; i < _windowSize - 1; i++)
_window[i] = _window[i + 1];
_window[_windowSize - 1] = new CandleInfo(candle.HighPrice, candle.LowPrice);
Evaluate();
return _lastValue;
}
private void Evaluate()
{
if (_count < _windowSize)
return;
var centerIndex = _windowSize / 2;
var center = _window[centerIndex];
var isUp = true;
var isDown = true;
for (var i = 0; i < _windowSize; i++)
{
if (i == centerIndex)
continue;
var candle = _window[i];
if (i < centerIndex)
{
if (center.High <= candle.High)
isUp = false;
if (center.Low >= candle.Low)
isDown = false;
}
else
{
if (center.High < candle.High)
isUp = false;
if (center.Low > candle.Low)
isDown = false;
}
if (!isUp && !isDown)
break;
}
if (isUp)
{
if (_direction == 1)
{
if (_lastValue == null || center.High > _lastValue.Value)
_lastValue = center.High;
}
else
{
_lastValue = center.High;
_direction = 1;
}
}
else if (isDown)
{
if (_direction == -1)
{
if (_lastValue == null || center.Low < _lastValue.Value)
_lastValue = center.Low;
}
else
{
_lastValue = center.Low;
_direction = -1;
}
}
}
private readonly struct CandleInfo
{
public CandleInfo(decimal high, decimal low)
{
High = high;
Low = low;
}
public decimal High { get; }
public decimal Low { get; }
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import AwesomeOscillator, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class fx_chaos_scalp_strategy(Strategy):
def __init__(self):
super(fx_chaos_scalp_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 50.0)
self._take_profit_points = self.Param("TakeProfitPoints", 50.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._zig_zag_window_size = self.Param("ZigZagWindowSize", 5)
self._ao = None
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous = False
self._entry_price = 0.0
self._has_entry = False
self._zz_window = []
self._zz_last_value = None
self._zz_direction = 0
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(fx_chaos_scalp_strategy, self).OnStarted2(time)
self._ao = AwesomeOscillator()
self._ao.ShortMa.Length = 5
self._ao.LongMa.Length = 34
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._update_zigzag(candle)
civ = CandleIndicatorValue(self._ao, candle)
civ.IsFinal = True
ao_result = self._ao.Process(civ)
if not self._has_previous:
self._update_previous_levels(candle)
return
if ao_result.IsEmpty or not self._ao.IsFormed:
self._update_previous_levels(candle)
return
ao = float(ao_result.Value)
open_price = float(candle.OpenPrice)
close_price = float(candle.ClosePrice)
long_signal = open_price < self._previous_high and close_price > self._previous_high and ao < 0.0
short_signal = open_price > self._previous_low and close_price < self._previous_low and ao > 0.0
if long_signal and self.Position == 0:
self.BuyMarket()
self._entry_price = close_price
self._has_entry = True
elif short_signal and self.Position == 0:
self.SellMarket()
self._entry_price = close_price
self._has_entry = True
if self.Position != 0 and self._has_entry:
if self._manage_risk(candle):
pass
if self.Position == 0:
self._has_entry = False
self._entry_price = 0.0
self._update_previous_levels(candle)
def _manage_risk(self, candle):
if self.Position == 0:
self._has_entry = False
self._entry_price = 0.0
return False
if not self._has_entry:
return False
step = self._get_price_step()
if self.Position > 0:
stop = self._entry_price - self._stop_loss_points.Value * step if self._stop_loss_points.Value > 0 else None
take = self._entry_price + self._take_profit_points.Value * step if self._take_profit_points.Value > 0 else None
if stop is not None and float(candle.LowPrice) <= stop:
self.SellMarket(self.Position)
self._has_entry = False
self._entry_price = 0.0
return True
if take is not None and float(candle.HighPrice) >= take:
self.SellMarket(self.Position)
self._has_entry = False
self._entry_price = 0.0
return True
elif self.Position < 0:
stop = self._entry_price + self._stop_loss_points.Value * step if self._stop_loss_points.Value > 0 else None
take = self._entry_price - self._take_profit_points.Value * step if self._take_profit_points.Value > 0 else None
if stop is not None and float(candle.HighPrice) >= stop:
self.BuyMarket(abs(self.Position))
self._has_entry = False
self._entry_price = 0.0
return True
if take is not None and float(candle.LowPrice) <= take:
self.BuyMarket(abs(self.Position))
self._has_entry = False
self._entry_price = 0.0
return True
return False
def _update_zigzag(self, candle):
ws = self._zig_zag_window_size.Value
if ws < 3:
ws = 3
if ws % 2 == 0:
ws += 1
self._zz_window.append((float(candle.HighPrice), float(candle.LowPrice)))
if len(self._zz_window) > ws:
self._zz_window = self._zz_window[len(self._zz_window) - ws:]
if len(self._zz_window) < ws:
return
center = ws // 2
ch, cl = self._zz_window[center]
is_up = True
is_down = True
for i in range(ws):
if i == center:
continue
h, l = self._zz_window[i]
if i < center:
if ch <= h:
is_up = False
if cl >= l:
is_down = False
else:
if ch < h:
is_up = False
if cl > l:
is_down = False
if not is_up and not is_down:
break
if is_up:
if self._zz_direction == 1:
if self._zz_last_value is None or ch > self._zz_last_value:
self._zz_last_value = ch
else:
self._zz_last_value = ch
self._zz_direction = 1
elif is_down:
if self._zz_direction == -1:
if self._zz_last_value is None or cl < self._zz_last_value:
self._zz_last_value = cl
else:
self._zz_last_value = cl
self._zz_direction = -1
def _update_previous_levels(self, candle):
self._previous_high = float(candle.HighPrice)
self._previous_low = float(candle.LowPrice)
self._has_previous = True
def _get_price_step(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
return step if step > 0 else 1.0
def OnReseted(self):
super(fx_chaos_scalp_strategy, self).OnReseted()
self._ao = None
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous = False
self._entry_price = 0.0
self._has_entry = False
self._zz_window = []
self._zz_last_value = None
self._zz_direction = 0
def CreateClone(self):
return fx_chaos_scalp_strategy()