JS Chaos 策略
概述
JS Chaos 策略在 StockSharp 高级 API 中复刻了 MetaTrader 专家顾问 “JS-Chaos” 的行为。策略围绕比尔·威廉姆斯的短吻鳄结构与分形突破构建入场,辅以 Awesome Oscillator 与 Acceleration/Deceleration 指标确认,并通过追踪止损、保本与复杂的时间过滤器管理持仓。
核心逻辑
- 指标体系
- 使用 13/8/5 周期、8/5/3 位移的平滑移动平均构成的短吻鳄指标(以中价为输入)。
- Awesome Oscillator 及其 5 周期 SMA 推导出的 AC 指标。
- 21 周期平滑均线作为追踪止损的核心。
- 10 周期标准差作为追踪触发条件之一。
- 最近五根 K 线高低点的分形检测,记录 10 根 K 线内最新形成的分形。
- 信号生成
- 多头背景:
AO[0] > AO[1] > 0且Lips > Teeth > Jaw。 - 空头背景:
AO[0] < AO[1] < 0且Lips < Teeth < Jaw。
- 多头背景:
- 挂单布置
- 条件满足且当前时间允许交易时,同向布置两笔突破单:主单使用 2× 基础手数,次单使用 1× 基础手数,触发价为最近符合条件且位于短吻鳄唇线之外的分形价。
- 主单止盈为
Lips ± (Fractal − Lips) * Fibo1,次单止盈使用Fibo2倍数。
- 仓位管理
- 可选:当唇线突破上一根 K 线开盘价时强制平仓对应方向持仓。
- 追踪止损:当标准差、AO 与 AC 同向走强时,将止损推至 21 周期 SMMA。
- 保本逻辑:主单离场且价格已行进到设定额外点数后,将次单止损移至保本位。
- 通过监控价格区间,当触及止盈/止损时以市价单平仓。
- 时间过滤器
- 通过开盘/收盘小时(支持跨日)限定交易窗口,并包含季节性约束:周一 03:00 之前禁用、周五 18:00 之后禁用、1 月前 9 天及 12 月 20 日之后禁用。
UseTime=false可完全关闭过滤。
- 通过开盘/收盘小时(支持跨日)限定交易窗口,并包含季节性约束:周一 03:00 之前禁用、周五 18:00 之后禁用、1 月前 9 天及 12 月 20 日之后禁用。
参数
| 参数 | 说明 |
|---|---|
UseTime |
是否启用时间过滤。 |
OpenHour / CloseHour |
交易时段的起止小时(0-23)。 |
BaseVolume |
基础下单手数(主单 2×、次单 1×)。 |
IndentingPips |
分形挂单的偏移量(以点数表示)。 |
Fibo1 / Fibo2 |
计算止盈目标的斐波那契比例。 |
UseClosePositions |
当唇线穿越上一根 K 线开盘价时是否强制平仓。 |
UseTrailing |
是否启用基于均线与振荡指标的追踪止损。 |
UseBreakeven |
是否启用次单的保本逻辑。 |
BreakevenPlusPips |
移动到保本位时在入场价基础上增加的点数。 |
CandleType |
策略所处理的 K 线类型/周期。 |
说明
- 转换保留了原 MQL5 机器人分批挂单和仓位管理的结构,同时利用 StockSharp 的 K 线订阅流程。
- 所有计算基于已收盘 K 线,原策略的盘中触发通过在价格突破后发送市价单模拟实现。
- 点值换算会根据报价精度(3 或 5 位小数)自动调整,适用于外汇品种。
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>
/// JS Chaos strategy converted from the original MQL5 expert advisor.
/// </summary>
public class JSChaosStrategy : Strategy
{
private readonly StrategyParam<bool> _useTime;
private readonly StrategyParam<int> _fractalLookback;
private readonly StrategyParam<int> _jawShift;
private readonly StrategyParam<int> _teethShift;
private readonly StrategyParam<int> _lipsShift;
private readonly StrategyParam<int> _openHour;
private readonly StrategyParam<int> _closeHour;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<int> _indentPips;
private readonly StrategyParam<decimal> _fibo1;
private readonly StrategyParam<decimal> _fibo2;
private readonly StrategyParam<bool> _useClosePositions;
private readonly StrategyParam<bool> _useTrailing;
private readonly StrategyParam<bool> _useBreakeven;
private readonly StrategyParam<int> _breakevenPlusPips;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _jaw = null!;
private SmoothedMovingAverage _teeth = null!;
private SmoothedMovingAverage _lips = null!;
private SmoothedMovingAverage _ma21 = null!;
private SimpleMovingAverage _aoShort = null!;
private SimpleMovingAverage _aoLong = null!;
private SimpleMovingAverage _aoSma = null!;
private StandardDeviation _stdDev = null!;
private readonly List<decimal> _jawQueue = new();
private readonly List<decimal> _teethQueue = new();
private readonly List<decimal> _lipsQueue = new();
private decimal? _jawValue;
private decimal? _teethValue;
private decimal? _lipsValue;
private decimal? _ma21Value;
private decimal? _aoCurrent;
private decimal? _aoPrev;
private decimal? _acCurrent;
private decimal? _acPrev;
private decimal? _stdDevCurrent;
private decimal? _stdDevPrev;
private readonly decimal?[] _highs = new decimal?[5];
private readonly decimal?[] _lows = new decimal?[5];
private int _bufferCount;
private readonly List<FractalLevel> _upFractals = new();
private readonly List<FractalLevel> _downFractals = new();
private readonly List<PendingOrder> _pendingOrders = new();
private readonly List<ActiveTrade> _activeTrades = new();
private decimal _pipSize;
private decimal _indentValue;
private decimal _breakevenPlusValue;
private bool _priceSettingsReady;
private decimal? _prevOpen;
/// <summary>
/// Use time window filter.
/// </summary>
public bool UseTime
{
get => _useTime.Value;
set => _useTime.Value = value;
}
/// <summary>
/// Trading session start hour.
/// </summary>
public int OpenHour
{
get => _openHour.Value;
set => _openHour.Value = value;
}
/// <summary>
/// Trading session end hour.
/// </summary>
public int CloseHour
{
get => _closeHour.Value;
set => _closeHour.Value = value;
}
/// <summary>
/// Base volume for staged entries.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Fractal indentation in pips.
/// </summary>
public int IndentingPips
{
get => _indentPips.Value;
set => _indentPips.Value = value;
}
/// <summary>
/// Primary take-profit multiplier.
/// </summary>
public decimal Fibo1
{
get => _fibo1.Value;
set => _fibo1.Value = value;
}
/// <summary>
/// Secondary take-profit multiplier.
/// </summary>
public decimal Fibo2
{
get => _fibo2.Value;
set => _fibo2.Value = value;
}
/// <summary>
/// Close positions when lips cross the previous open.
/// </summary>
public bool UseClosePositions
{
get => _useClosePositions.Value;
set => _useClosePositions.Value = value;
}
/// <summary>
/// Enable MA-based trailing stop.
/// </summary>
public bool UseTrailing
{
get => _useTrailing.Value;
set => _useTrailing.Value = value;
}
/// <summary>
/// Enable breakeven management for the secondary order.
/// </summary>
public bool UseBreakeven
{
get => _useBreakeven.Value;
set => _useBreakeven.Value = value;
}
/// <summary>
/// Extra pips for breakeven stop placement.
/// </summary>
public int BreakevenPlusPips
{
get => _breakevenPlusPips.Value;
set => _breakevenPlusPips.Value = value;
}
/// <summary>
/// Number of completed bars used to confirm fractals.
/// </summary>
public int FractalLookback
{
get => _fractalLookback.Value;
set => _fractalLookback.Value = value;
}
/// <summary>
/// Shift applied to the jaw moving average.
/// </summary>
public int JawShift
{
get => _jawShift.Value;
set => _jawShift.Value = value;
}
/// <summary>
/// Shift applied to the teeth moving average.
/// </summary>
public int TeethShift
{
get => _teethShift.Value;
set => _teethShift.Value = value;
}
/// <summary>
/// Shift applied to the lips moving average.
/// </summary>
public int LipsShift
{
get => _lipsShift.Value;
set => _lipsShift.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="JSChaosStrategy"/>.
/// </summary>
public JSChaosStrategy()
{
_useTime = Param(nameof(UseTime), false)
.SetDisplay("Use Time", "Enable trading window", "General");
_openHour = Param(nameof(OpenHour), 7)
.SetRange(0, 23)
.SetDisplay("Open Hour", "Hour to start trading", "General");
_closeHour = Param(nameof(CloseHour), 18)
.SetRange(0, 23)
.SetDisplay("Close Hour", "Hour to stop trading", "General");
_baseVolume = Param(nameof(BaseVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Base volume for staged entries", "Trading");
_indentPips = Param(nameof(IndentingPips), 0)
.SetRange(0, 1000)
.SetDisplay("Indenting (pips)", "Offset from fractal level", "Trading");
_fibo1 = Param(nameof(Fibo1), 1.618m)
.SetGreaterThanZero()
.SetDisplay("Fibo 1", "Primary take-profit multiplier", "Targets");
_fibo2 = Param(nameof(Fibo2), 4.618m)
.SetGreaterThanZero()
.SetDisplay("Fibo 2", "Secondary take-profit multiplier", "Targets");
_useClosePositions = Param(nameof(UseClosePositions), true)
.SetDisplay("Close Positions", "Exit when lips cross previous open", "Risk");
_useTrailing = Param(nameof(UseTrailing), true)
.SetDisplay("Use Trailing", "Enable MA trailing stop", "Risk");
_useBreakeven = Param(nameof(UseBreakeven), true)
.SetDisplay("Use Breakeven", "Move secondary trade to breakeven", "Risk");
_breakevenPlusPips = Param(nameof(BreakevenPlusPips), 1)
.SetRange(0, 1000)
.SetDisplay("Breakeven Plus", "Additional pips for breakeven", "Risk");
_fractalLookback = Param(nameof(FractalLookback), 10)
.SetGreaterThanZero()
.SetDisplay("Fractal Lookback", "Bars required to confirm fractal levels", "Indicator")
;
_jawShift = Param(nameof(JawShift), 8)
.SetRange(1, 30)
.SetDisplay("Jaw Shift", "Shift applied to the jaw moving average", "Indicator")
;
_teethShift = Param(nameof(TeethShift), 5)
.SetRange(1, 30)
.SetDisplay("Teeth Shift", "Shift applied to the teeth moving average", "Indicator")
;
_lipsShift = Param(nameof(LipsShift), 3)
.SetRange(1, 30)
.SetDisplay("Lips Shift", "Shift applied to the lips moving average", "Indicator")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to process", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_jawQueue.Clear();
_teethQueue.Clear();
_lipsQueue.Clear();
Array.Clear(_highs);
Array.Clear(_lows);
_bufferCount = 0;
_upFractals.Clear();
_downFractals.Clear();
_pendingOrders.Clear();
_activeTrades.Clear();
_jawValue = null;
_teethValue = null;
_lipsValue = null;
_ma21Value = null;
_aoCurrent = null;
_aoPrev = null;
_acCurrent = null;
_acPrev = null;
_stdDevCurrent = null;
_stdDevPrev = null;
_pipSize = 0m;
_indentValue = 0m;
_breakevenPlusValue = 0m;
_priceSettingsReady = false;
_prevOpen = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
UpdatePriceSettings();
_jaw = new SmoothedMovingAverage { Length = 13 };
_teeth = new SmoothedMovingAverage { Length = 8 };
_lips = new SmoothedMovingAverage { Length = 5 };
_ma21 = new SmoothedMovingAverage { Length = 21 };
_aoShort = new SMA { Length = 5 };
_aoLong = new SMA { Length = 34 };
_aoSma = new SMA { Length = 5 };
_stdDev = new StandardDeviation { Length = 10 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!_priceSettingsReady)
UpdatePriceSettings();
var median = (candle.HighPrice + candle.LowPrice) / 2m;
UpdateAlligator(median, candle);
var maValue = _ma21.Process(new DecimalIndicatorValue(_ma21, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
if (maValue.IsFormed)
_ma21Value = maValue.ToDecimal();
var aoShortValue = _aoShort.Process(new DecimalIndicatorValue(_aoShort, median, candle.OpenTime) { IsFinal = true });
var aoLongValue = _aoLong.Process(new DecimalIndicatorValue(_aoLong, median, candle.OpenTime) { IsFinal = true });
if (!_aoShort.IsFormed || !_aoLong.IsFormed)
return;
var ao = aoShortValue.ToDecimal() - aoLongValue.ToDecimal();
var aoSmaValue = _aoSma.Process(new DecimalIndicatorValue(_aoSma, ao, candle.OpenTime) { IsFinal = true });
if (!_aoSma.IsFormed)
return;
var aoSma = aoSmaValue.ToDecimal();
var ac = ao - aoSma;
var stdValue = _stdDev.Process(new DecimalIndicatorValue(_stdDev, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
if (!_stdDev.IsFormed)
return;
var stdDev = stdValue.ToDecimal();
if (_jawValue is null || _teethValue is null || _lipsValue is null || _ma21Value is null)
return;
UpdateHistory(ref _aoCurrent, ref _aoPrev, ao);
UpdateHistory(ref _acCurrent, ref _acPrev, ac);
UpdateStdDev(stdDev);
UpdateFractals(candle);
if (UseTrailing)
UpdateTrailing(candle.ClosePrice);
UpdateBreakeven(candle.ClosePrice);
HandleStopsAndTargets(candle);
UpdateBreakeven(candle.ClosePrice);
if (UseClosePositions)
{
ApplyLipsExit();
UpdateBreakeven(candle.ClosePrice);
}
var signal = GetSignal();
var canTrade = IsTradingTime(candle.OpenTime);
if (canTrade)
TryPlaceOrders(signal, candle.ClosePrice);
TriggerPendingOrders(candle);
if (signal == 2)
_pendingOrders.RemoveAll(o => o.Side == Sides.Buy);
else if (signal == 1)
_pendingOrders.RemoveAll(o => o.Side == Sides.Sell);
_prevOpen = candle.OpenPrice;
}
private void UpdateAlligator(decimal median, ICandleMessage candle)
{
var jawValue = _jaw.Process(new DecimalIndicatorValue(_jaw, median, candle.OpenTime) { IsFinal = true });
if (jawValue.IsFormed)
{
_jawQueue.Add(jawValue.ToDecimal());
if (_jawQueue.Count > JawShift)
{
_jawValue = _jawQueue[0];
try { _jawQueue.RemoveAt(0); } catch { }
}
}
var teethValue = _teeth.Process(new DecimalIndicatorValue(_teeth, median, candle.OpenTime) { IsFinal = true });
if (teethValue.IsFormed)
{
_teethQueue.Add(teethValue.ToDecimal());
if (_teethQueue.Count > TeethShift)
{
_teethValue = _teethQueue[0];
try { _teethQueue.RemoveAt(0); } catch { }
}
}
var lipsValue = _lips.Process(new DecimalIndicatorValue(_lips, median, candle.OpenTime) { IsFinal = true });
if (lipsValue.IsFormed)
{
_lipsQueue.Add(lipsValue.ToDecimal());
if (_lipsQueue.Count > LipsShift)
{
_lipsValue = _lipsQueue[0];
try { _lipsQueue.RemoveAt(0); } catch { }
}
}
}
private void UpdateHistory(ref decimal? current, ref decimal? previous, decimal value)
{
previous = current;
current = value;
}
private void UpdateStdDev(decimal value)
{
_stdDevPrev = _stdDevCurrent;
_stdDevCurrent = value;
}
private void UpdateFractals(ICandleMessage candle)
{
for (var i = 0; i < 4; i++)
{
_highs[i] = _highs[i + 1];
_lows[i] = _lows[i + 1];
}
_highs[4] = candle.HighPrice;
_lows[4] = candle.LowPrice;
if (_bufferCount < 5)
_bufferCount++;
IncrementFractalAges(_upFractals);
IncrementFractalAges(_downFractals);
if (_bufferCount < 5)
{
TrimFractals(_upFractals);
TrimFractals(_downFractals);
return;
}
decimal? upFractal = null;
decimal? downFractal = null;
var h0 = _highs[0];
var h1 = _highs[1];
var h2 = _highs[2];
var h3 = _highs[3];
var h4 = _highs[4];
if (h2.HasValue && h0.HasValue && h1.HasValue && h3.HasValue && h4.HasValue &&
h2.Value > h0.Value && h2.Value > h1.Value && h2.Value > h3.Value && h2.Value > h4.Value)
upFractal = h2.Value;
var l0 = _lows[0];
var l1 = _lows[1];
var l2 = _lows[2];
var l3 = _lows[3];
var l4 = _lows[4];
if (l2.HasValue && l0.HasValue && l1.HasValue && l3.HasValue && l4.HasValue &&
l2.Value < l0.Value && l2.Value < l1.Value && l2.Value < l3.Value && l2.Value < l4.Value)
downFractal = l2.Value;
if (upFractal.HasValue)
{
var price = NormalizePrice(upFractal.Value + _indentValue);
_upFractals.Insert(0, new FractalLevel(price));
}
if (downFractal.HasValue)
{
var price = NormalizePrice(downFractal.Value - _indentValue);
_downFractals.Insert(0, new FractalLevel(price));
}
TrimFractals(_upFractals);
TrimFractals(_downFractals);
}
private void IncrementFractalAges(List<FractalLevel> levels)
{
foreach (var level in levels)
level.Age++;
}
private void TrimFractals(List<FractalLevel> levels)
{
for (var i = levels.Count - 1; i >= 0; i--)
{
if (levels[i].Age >= FractalLookback)
levels.RemoveAt(i);
}
}
private int GetSignal()
{
if (_aoCurrent is not decimal ao0 || _aoPrev is not decimal ao1 ||
_lipsValue is not decimal lips || _teethValue is not decimal teeth || _jawValue is not decimal jaw)
return 0;
if (ao0 > ao1 && ao1 > 0m && lips > teeth && teeth > jaw)
return 1;
if (ao0 < ao1 && ao1 < 0m && lips < teeth && teeth < jaw)
return 2;
return 0;
}
private void TryPlaceOrders(int signal, decimal closePrice)
{
if (signal == 1)
{
var upFractal = GetLatestFractal(_upFractals);
if (upFractal.HasValue)
TryCreateBuyOrders(upFractal.Value, _lipsValue!.Value, closePrice);
}
else if (signal == 2)
{
var downFractal = GetLatestFractal(_downFractals);
if (downFractal.HasValue)
TryCreateSellOrders(downFractal.Value, _lipsValue!.Value, closePrice);
}
}
private decimal? GetLatestFractal(List<FractalLevel> levels)
{
return levels.Count > 0 ? levels[0].Price : null;
}
private void TryCreateBuyOrders(decimal upFractal, decimal lips, decimal closePrice)
{
if (upFractal <= lips)
return;
if (_activeTrades.Exists(t => t.Side == Sides.Buy))
return;
var hasPrimary = _pendingOrders.Exists(o => o.Side == Sides.Buy && o.IsPrimary);
var hasSecondary = _pendingOrders.Exists(o => o.Side == Sides.Buy && !o.IsPrimary);
if (!hasPrimary)
{
var distance = upFractal - lips;
if (_pipSize > 0m)
{
if (distance <= _pipSize)
return;
if (closePrice + _pipSize >= upFractal)
return;
}
var tp = lips + distance * Fibo1;
if (tp <= 0m)
return;
if (_pipSize > 0m && tp - upFractal <= _pipSize)
return;
var order = new PendingOrder
{
Side = Sides.Buy,
Price = NormalizePrice(upFractal),
StopLoss = NormalizePrice(lips),
TakeProfit = NormalizePrice(tp),
Volume = BaseVolume * 2m,
IsPrimary = true
};
if (order.Volume > 0m)
_pendingOrders.Add(order);
}
hasPrimary = _pendingOrders.Exists(o => o.Side == Sides.Buy && o.IsPrimary);
if (!hasPrimary || hasSecondary)
return;
var distanceSecondary = upFractal - lips;
if (_pipSize > 0m)
{
if (distanceSecondary <= _pipSize)
return;
if (closePrice + _pipSize >= upFractal)
return;
}
var tp2 = lips + distanceSecondary * Fibo2;
if (tp2 <= 0m)
return;
if (_pipSize > 0m && tp2 - upFractal <= _pipSize)
return;
var secondary = new PendingOrder
{
Side = Sides.Buy,
Price = NormalizePrice(upFractal),
StopLoss = NormalizePrice(lips),
TakeProfit = NormalizePrice(tp2),
Volume = BaseVolume,
IsPrimary = false
};
if (secondary.Volume > 0m)
_pendingOrders.Add(secondary);
}
private void TryCreateSellOrders(decimal downFractal, decimal lips, decimal closePrice)
{
if (downFractal >= lips)
return;
if (_activeTrades.Exists(t => t.Side == Sides.Sell))
return;
var hasPrimary = _pendingOrders.Exists(o => o.Side == Sides.Sell && o.IsPrimary);
var hasSecondary = _pendingOrders.Exists(o => o.Side == Sides.Sell && !o.IsPrimary);
if (!hasPrimary)
{
var distance = lips - downFractal;
if (_pipSize > 0m)
{
if (distance <= _pipSize)
return;
if (closePrice - _pipSize <= downFractal)
return;
}
var tp = lips - distance * Fibo1;
if (tp <= 0m)
return;
if (_pipSize > 0m && downFractal - tp <= _pipSize)
return;
var order = new PendingOrder
{
Side = Sides.Sell,
Price = NormalizePrice(downFractal),
StopLoss = NormalizePrice(lips),
TakeProfit = NormalizePrice(tp),
Volume = BaseVolume * 2m,
IsPrimary = true
};
if (order.Volume > 0m)
_pendingOrders.Add(order);
}
hasPrimary = _pendingOrders.Exists(o => o.Side == Sides.Sell && o.IsPrimary);
if (!hasPrimary || hasSecondary)
return;
var distanceSecondary = lips - downFractal;
if (_pipSize > 0m)
{
if (distanceSecondary <= _pipSize)
return;
if (closePrice - _pipSize <= downFractal)
return;
}
var tp2 = lips - distanceSecondary * Fibo2;
if (tp2 <= 0m)
return;
if (_pipSize > 0m && downFractal - tp2 <= _pipSize)
return;
var secondary = new PendingOrder
{
Side = Sides.Sell,
Price = NormalizePrice(downFractal),
StopLoss = NormalizePrice(lips),
TakeProfit = NormalizePrice(tp2),
Volume = BaseVolume,
IsPrimary = false
};
if (secondary.Volume > 0m)
_pendingOrders.Add(secondary);
}
private void TriggerPendingOrders(ICandleMessage candle)
{
// indicators checked above
for (var i = _pendingOrders.Count - 1; i >= 0; i--)
{
var pending = _pendingOrders[i];
var triggered = pending.Side == Sides.Buy
? candle.HighPrice >= pending.Price
: candle.LowPrice <= pending.Price;
if (!triggered)
continue;
ExecuteTrade(pending);
try { _pendingOrders.RemoveAt(i); } catch { }
}
}
private void ExecuteTrade(PendingOrder order)
{
if (order.Volume <= 0m)
return;
if (order.Side == Sides.Buy)
BuyMarket();
else
SellMarket();
_activeTrades.Add(new ActiveTrade
{
Side = order.Side,
Volume = order.Volume,
EntryPrice = order.Price,
StopLoss = order.StopLoss,
TakeProfit = order.TakeProfit,
IsPrimary = order.IsPrimary
});
}
private void UpdateTrailing(decimal closePrice)
{
if (_ma21Value is not decimal ma21 ||
_stdDevCurrent is not decimal stdDev0 || _stdDevPrev is not decimal stdDev1 ||
_aoCurrent is not decimal ao0 || _aoPrev is not decimal ao1 ||
_acCurrent is not decimal ac0 || _acPrev is not decimal ac1)
return;
foreach (var trade in _activeTrades)
{
if (trade.Side == Sides.Buy)
{
if ((trade.StopLoss <= 0m || (trade.StopLoss != ma21 && trade.StopLoss < ma21)) &&
stdDev0 > stdDev1 && ao0 > ao1 && ac0 > ac1)
{
if (_pipSize <= 0m || ma21 + _pipSize <= closePrice)
trade.StopLoss = NormalizePrice(ma21);
}
}
else
{
if ((trade.StopLoss <= 0m || (trade.StopLoss != ma21 && trade.StopLoss > ma21)) &&
stdDev0 > stdDev1 && ao0 < ao1 && ac0 < ac1)
{
if (_pipSize <= 0m || ma21 - _pipSize >= closePrice)
trade.StopLoss = NormalizePrice(ma21);
}
}
}
}
private void UpdateBreakeven(decimal closePrice)
{
if (!UseBreakeven || _breakevenPlusValue <= 0m)
return;
foreach (var trade in _activeTrades)
{
if (trade.IsPrimary || trade.MovedToBreakeven)
continue;
var primaryExists = _activeTrades.Exists(t => t.Side == trade.Side && t.IsPrimary);
if (primaryExists)
continue;
if (trade.Side == Sides.Buy)
{
if (closePrice >= trade.EntryPrice + _breakevenPlusValue && trade.StopLoss < trade.EntryPrice)
{
trade.StopLoss = NormalizePrice(trade.EntryPrice + _breakevenPlusValue);
trade.MovedToBreakeven = true;
}
}
else
{
if (closePrice <= trade.EntryPrice - _breakevenPlusValue && trade.StopLoss > trade.EntryPrice)
{
trade.StopLoss = NormalizePrice(trade.EntryPrice - _breakevenPlusValue);
trade.MovedToBreakeven = true;
}
}
}
}
private void HandleStopsAndTargets(ICandleMessage candle)
{
for (var i = _activeTrades.Count - 1; i >= 0; i--)
{
var trade = _activeTrades[i];
var close = false;
if (trade.Side == Sides.Buy)
{
if (trade.TakeProfit > 0m && candle.HighPrice >= trade.TakeProfit)
close = true;
else if (trade.StopLoss > 0m && candle.LowPrice <= trade.StopLoss)
close = true;
}
else
{
if (trade.TakeProfit > 0m && candle.LowPrice <= trade.TakeProfit)
close = true;
else if (trade.StopLoss > 0m && candle.HighPrice >= trade.StopLoss)
close = true;
}
if (!close)
continue;
CloseTrade(trade);
try { _activeTrades.RemoveAt(i); } catch { }
}
}
private void ApplyLipsExit()
{
if (_prevOpen is null || _lipsValue is null)
return;
var prevOpen = _prevOpen.Value;
var lips = _lipsValue.Value;
if (lips > prevOpen)
CloseTrades(Sides.Buy);
if (lips < prevOpen)
CloseTrades(Sides.Sell);
}
private void CloseTrades(Sides side)
{
for (var i = _activeTrades.Count - 1; i >= 0; i--)
{
var trade = _activeTrades[i];
if (trade.Side != side)
continue;
CloseTrade(trade);
try { _activeTrades.RemoveAt(i); } catch { }
}
}
private void CloseTrade(ActiveTrade trade)
{
if (trade.Side == Sides.Buy)
SellMarket();
else
BuyMarket();
}
private bool IsTradingTime(DateTime time)
{
if (!UseTime)
return true;
var hour = time.Hour;
var trading = false;
if (OpenHour > CloseHour)
trading = hour <= CloseHour || hour >= OpenHour;
else if (OpenHour < CloseHour)
trading = hour >= OpenHour && hour <= CloseHour;
else
trading = hour == OpenHour;
var dayOfWeek = (int)time.DayOfWeek;
if (dayOfWeek == 1 && hour < 3)
trading = false;
if (dayOfWeek >= 5 && hour > 18)
trading = false;
if (time.Month == 1 && time.Day < 10)
trading = false;
if (time.Month == 12 && time.Day > 20)
trading = false;
return trading;
}
private void UpdatePriceSettings()
{
if (Security is null)
return;
var step = Security.PriceStep;
if (step is not decimal priceStep || priceStep <= 0m)
return;
var decimals = CountDecimals(priceStep);
var pip = priceStep;
if (decimals == 3 || decimals == 5)
pip = priceStep * 10m;
_pipSize = pip;
_indentValue = pip * IndentingPips;
_breakevenPlusValue = pip * BreakevenPlusPips;
_priceSettingsReady = true;
}
private decimal NormalizePrice(decimal price)
{
var step = Security?.PriceStep;
if (step is decimal priceStep && priceStep > 0m)
return Math.Round(price / priceStep, MidpointRounding.AwayFromZero) * priceStep;
return price;
}
private static int CountDecimals(decimal value)
{
var bits = decimal.GetBits(value);
return (bits[3] >> 16) & 0xFF;
}
private sealed class FractalLevel
{
public FractalLevel(decimal price)
{
Price = price;
}
public decimal Price { get; }
public int Age { get; set; }
}
private sealed class PendingOrder
{
public Sides Side { get; init; }
public decimal Price { get; init; }
public decimal StopLoss { get; init; }
public decimal TakeProfit { get; init; }
public decimal Volume { get; init; }
public bool IsPrimary { get; init; }
}
private sealed class ActiveTrade
{
public Sides Side { get; init; }
public decimal Volume { get; init; }
public decimal EntryPrice { get; init; }
public decimal StopLoss { get; set; }
public decimal TakeProfit { get; init; }
public bool IsPrimary { get; init; }
public bool MovedToBreakeven { get; set; }
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
SmoothedMovingAverage,
SimpleMovingAverage,
StandardDeviation,
)
from indicator_extensions import *
class js_chaos_strategy(Strategy):
"""JS Chaos: Alligator + AO + AC + fractals with staged entries and trailing."""
def __init__(self):
super(js_chaos_strategy, self).__init__()
self._use_time = self.Param("UseTime", False) \
.SetDisplay("Use Time", "Enable trading window", "General")
self._open_hour = self.Param("OpenHour", 7) \
.SetDisplay("Open Hour", "Hour to start trading", "General")
self._close_hour = self.Param("CloseHour", 18) \
.SetDisplay("Close Hour", "Hour to stop trading", "General")
self._base_volume = self.Param("BaseVolume", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Base Volume", "Base volume for staged entries", "Trading")
self._indent_pips = self.Param("IndentingPips", 0) \
.SetDisplay("Indenting (pips)", "Offset from fractal level", "Trading")
self._fibo1 = self.Param("Fibo1", 1.618) \
.SetGreaterThanZero() \
.SetDisplay("Fibo 1", "Primary take-profit multiplier", "Targets")
self._fibo2 = self.Param("Fibo2", 4.618) \
.SetGreaterThanZero() \
.SetDisplay("Fibo 2", "Secondary take-profit multiplier", "Targets")
self._use_close_positions = self.Param("UseClosePositions", True) \
.SetDisplay("Close Positions", "Exit when lips cross previous open", "Risk")
self._use_trailing = self.Param("UseTrailing", True) \
.SetDisplay("Use Trailing", "Enable MA trailing stop", "Risk")
self._use_breakeven = self.Param("UseBreakeven", True) \
.SetDisplay("Use Breakeven", "Move secondary trade to breakeven", "Risk")
self._breakeven_plus_pips = self.Param("BreakevenPlusPips", 1) \
.SetDisplay("Breakeven Plus", "Additional pips for breakeven", "Risk")
self._fractal_lookback = self.Param("FractalLookback", 10) \
.SetGreaterThanZero() \
.SetDisplay("Fractal Lookback", "Bars required to confirm fractal levels", "Indicator")
self._jaw_shift = self.Param("JawShift", 8) \
.SetDisplay("Jaw Shift", "Shift applied to the jaw moving average", "Indicator")
self._teeth_shift = self.Param("TeethShift", 5) \
.SetDisplay("Teeth Shift", "Shift applied to the teeth moving average", "Indicator")
self._lips_shift = self.Param("LipsShift", 3) \
.SetDisplay("Lips Shift", "Shift applied to the lips moving average", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to process", "General")
self._jaw_queue = []
self._teeth_queue = []
self._lips_queue = []
self._jaw_value = None
self._teeth_value = None
self._lips_value = None
self._ma21_value = None
self._ao_current = None
self._ao_prev = None
self._ac_current = None
self._ac_prev = None
self._std_dev_current = None
self._std_dev_prev = None
self._highs = [None] * 5
self._lows = [None] * 5
self._buffer_count = 0
self._up_fractals = []
self._down_fractals = []
self._pending_orders = []
self._active_trades = []
self._pip_size = 0.0
self._indent_value = 0.0
self._breakeven_plus_value = 0.0
self._price_settings_ready = False
self._prev_open = None
@property
def UseTime(self):
return self._use_time.Value
@property
def OpenHour(self):
return int(self._open_hour.Value)
@property
def CloseHour(self):
return int(self._close_hour.Value)
@property
def BaseVolume(self):
return float(self._base_volume.Value)
@property
def IndentingPips(self):
return int(self._indent_pips.Value)
@property
def Fibo1(self):
return float(self._fibo1.Value)
@property
def Fibo2(self):
return float(self._fibo2.Value)
@property
def UseClosePositions(self):
return self._use_close_positions.Value
@property
def UseTrailing(self):
return self._use_trailing.Value
@property
def UseBreakeven(self):
return self._use_breakeven.Value
@property
def BreakevenPlusPips(self):
return int(self._breakeven_plus_pips.Value)
@property
def FractalLookback(self):
return int(self._fractal_lookback.Value)
@property
def JawShift(self):
return int(self._jaw_shift.Value)
@property
def TeethShift(self):
return int(self._teeth_shift.Value)
@property
def LipsShift(self):
return int(self._lips_shift.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _update_price_settings(self):
sec = self.Security
if sec is None:
return
ps = sec.PriceStep
if ps is None:
return
step = float(ps)
if step <= 0:
return
decimals = self._count_decimals(step)
pip = step * 10.0 if (decimals == 3 or decimals == 5) else step
self._pip_size = pip
self._indent_value = pip * self.IndentingPips
self._breakeven_plus_value = pip * self.BreakevenPlusPips
self._price_settings_ready = True
def _count_decimals(self, value):
v = abs(value)
count = 0
while v != int(v) and count < 10:
v *= 10
count += 1
return count
def _normalize_price(self, price):
sec = self.Security
if sec is not None and sec.PriceStep is not None:
step = float(sec.PriceStep)
if step > 0:
return round(price / step) * step
return price
def OnStarted2(self, time):
super(js_chaos_strategy, self).OnStarted2(time)
self._update_price_settings()
self._jaw_ind = SmoothedMovingAverage()
self._jaw_ind.Length = 13
self._teeth_ind = SmoothedMovingAverage()
self._teeth_ind.Length = 8
self._lips_ind = SmoothedMovingAverage()
self._lips_ind.Length = 5
self._ma21_ind = SmoothedMovingAverage()
self._ma21_ind.Length = 21
self._ao_short = SimpleMovingAverage()
self._ao_short.Length = 5
self._ao_long = SimpleMovingAverage()
self._ao_long.Length = 34
self._ao_sma = SimpleMovingAverage()
self._ao_sma.Length = 5
self._std_dev = StandardDeviation()
self._std_dev.Length = 10
self._jaw_queue = []
self._teeth_queue = []
self._lips_queue = []
self._jaw_value = None
self._teeth_value = None
self._lips_value = None
self._ma21_value = None
self._ao_current = None
self._ao_prev = None
self._ac_current = None
self._ac_prev = None
self._std_dev_current = None
self._std_dev_prev = None
self._highs = [None] * 5
self._lows = [None] * 5
self._buffer_count = 0
self._up_fractals = []
self._down_fractals = []
self._pending_orders = []
self._active_trades = []
self._prev_open = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if not self._price_settings_ready:
self._update_price_settings()
median = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
t = candle.ServerTime
# Alligator
self._update_alligator(median, t)
# MA21
ma_val = process_float(self._ma21_ind, Decimal(float(candle.ClosePrice)), t, True)
if ma_val.IsFormed:
self._ma21_value = float(ma_val.Value)
# AO
ao_short_val = process_float(self._ao_short, Decimal(median), t, True)
ao_long_val = process_float(self._ao_long, Decimal(median), t, True)
if not self._ao_short.IsFormed or not self._ao_long.IsFormed:
return
ao = float(ao_short_val.Value) - float(ao_long_val.Value)
ao_sma_val = process_float(self._ao_sma, Decimal(ao), t, True)
if not self._ao_sma.IsFormed:
return
ao_sma = float(ao_sma_val.Value)
ac = ao - ao_sma
# StdDev
std_val = process_float(self._std_dev, Decimal(float(candle.ClosePrice)), t, True)
if not self._std_dev.IsFormed:
return
std_dev = float(std_val.Value)
if self._jaw_value is None or self._teeth_value is None or self._lips_value is None or self._ma21_value is None:
return
# Update history
self._ao_prev = self._ao_current
self._ao_current = ao
self._ac_prev = self._ac_current
self._ac_current = ac
self._std_dev_prev = self._std_dev_current
self._std_dev_current = std_dev
self._update_fractals(candle)
close_price = float(candle.ClosePrice)
if self.UseTrailing:
self._update_trailing(close_price)
self._update_breakeven(close_price)
self._handle_stops_and_targets(candle)
self._update_breakeven(close_price)
if self.UseClosePositions:
self._apply_lips_exit()
self._update_breakeven(close_price)
signal = self._get_signal()
can_trade = self._is_trading_time(candle.OpenTime)
if can_trade:
self._try_place_orders(signal, close_price)
self._trigger_pending_orders(candle)
if signal == 2:
self._pending_orders = [o for o in self._pending_orders if o["side"] != "buy"]
elif signal == 1:
self._pending_orders = [o for o in self._pending_orders if o["side"] != "sell"]
self._prev_open = float(candle.OpenPrice)
def _update_alligator(self, median, t):
jaw_val = process_float(self._jaw_ind, Decimal(median), t, True)
if jaw_val.IsFormed:
self._jaw_queue.append(float(jaw_val.Value))
if len(self._jaw_queue) > self.JawShift:
self._jaw_value = self._jaw_queue.pop(0)
teeth_val = process_float(self._teeth_ind, Decimal(median), t, True)
if teeth_val.IsFormed:
self._teeth_queue.append(float(teeth_val.Value))
if len(self._teeth_queue) > self.TeethShift:
self._teeth_value = self._teeth_queue.pop(0)
lips_val = process_float(self._lips_ind, Decimal(median), t, True)
if lips_val.IsFormed:
self._lips_queue.append(float(lips_val.Value))
if len(self._lips_queue) > self.LipsShift:
self._lips_value = self._lips_queue.pop(0)
def _update_fractals(self, candle):
for i in range(4):
self._highs[i] = self._highs[i + 1]
self._lows[i] = self._lows[i + 1]
self._highs[4] = float(candle.HighPrice)
self._lows[4] = float(candle.LowPrice)
if self._buffer_count < 5:
self._buffer_count += 1
# Increment ages
for f in self._up_fractals:
f["age"] += 1
for f in self._down_fractals:
f["age"] += 1
if self._buffer_count < 5:
self._trim_fractals(self._up_fractals)
self._trim_fractals(self._down_fractals)
return
up_fractal = None
down_fractal = None
h = self._highs
if all(x is not None for x in h):
if h[2] > h[0] and h[2] > h[1] and h[2] > h[3] and h[2] > h[4]:
up_fractal = h[2]
lo = self._lows
if all(x is not None for x in lo):
if lo[2] < lo[0] and lo[2] < lo[1] and lo[2] < lo[3] and lo[2] < lo[4]:
down_fractal = lo[2]
if up_fractal is not None:
price = self._normalize_price(up_fractal + self._indent_value)
self._up_fractals.insert(0, {"price": price, "age": 0})
if down_fractal is not None:
price = self._normalize_price(down_fractal - self._indent_value)
self._down_fractals.insert(0, {"price": price, "age": 0})
self._trim_fractals(self._up_fractals)
self._trim_fractals(self._down_fractals)
def _trim_fractals(self, levels):
i = len(levels) - 1
while i >= 0:
if levels[i]["age"] >= self.FractalLookback:
levels.pop(i)
i -= 1
def _get_signal(self):
if self._ao_current is None or self._ao_prev is None:
return 0
if self._lips_value is None or self._teeth_value is None or self._jaw_value is None:
return 0
ao0 = self._ao_current
ao1 = self._ao_prev
lips = self._lips_value
teeth = self._teeth_value
jaw = self._jaw_value
if ao0 > ao1 and ao1 > 0 and lips > teeth and teeth > jaw:
return 1
if ao0 < ao1 and ao1 < 0 and lips < teeth and teeth < jaw:
return 2
return 0
def _try_place_orders(self, signal, close_price):
if signal == 1:
up_frac = self._get_latest_fractal(self._up_fractals)
if up_frac is not None:
self._try_create_buy_orders(up_frac, self._lips_value, close_price)
elif signal == 2:
down_frac = self._get_latest_fractal(self._down_fractals)
if down_frac is not None:
self._try_create_sell_orders(down_frac, self._lips_value, close_price)
def _get_latest_fractal(self, levels):
return levels[0]["price"] if len(levels) > 0 else None
def _try_create_buy_orders(self, up_fractal, lips, close_price):
if up_fractal <= lips:
return
if any(t["side"] == "buy" for t in self._active_trades):
return
has_primary = any(o["side"] == "buy" and o["is_primary"] for o in self._pending_orders)
has_secondary = any(o["side"] == "buy" and not o["is_primary"] for o in self._pending_orders)
if not has_primary:
distance = up_fractal - lips
if self._pip_size > 0:
if distance <= self._pip_size:
return
if close_price + self._pip_size >= up_fractal:
return
tp = lips + distance * self.Fibo1
if tp <= 0:
return
if self._pip_size > 0 and tp - up_fractal <= self._pip_size:
return
vol = self.BaseVolume * 2.0
if vol > 0:
self._pending_orders.append({
"side": "buy", "price": self._normalize_price(up_fractal),
"stop_loss": self._normalize_price(lips),
"take_profit": self._normalize_price(tp),
"volume": vol, "is_primary": True
})
has_primary = any(o["side"] == "buy" and o["is_primary"] for o in self._pending_orders)
if not has_primary or has_secondary:
return
distance = up_fractal - lips
if self._pip_size > 0:
if distance <= self._pip_size:
return
if close_price + self._pip_size >= up_fractal:
return
tp2 = lips + distance * self.Fibo2
if tp2 <= 0:
return
if self._pip_size > 0 and tp2 - up_fractal <= self._pip_size:
return
vol2 = self.BaseVolume
if vol2 > 0:
self._pending_orders.append({
"side": "buy", "price": self._normalize_price(up_fractal),
"stop_loss": self._normalize_price(lips),
"take_profit": self._normalize_price(tp2),
"volume": vol2, "is_primary": False
})
def _try_create_sell_orders(self, down_fractal, lips, close_price):
if down_fractal >= lips:
return
if any(t["side"] == "sell" for t in self._active_trades):
return
has_primary = any(o["side"] == "sell" and o["is_primary"] for o in self._pending_orders)
has_secondary = any(o["side"] == "sell" and not o["is_primary"] for o in self._pending_orders)
if not has_primary:
distance = lips - down_fractal
if self._pip_size > 0:
if distance <= self._pip_size:
return
if close_price - self._pip_size <= down_fractal:
return
tp = lips - distance * self.Fibo1
if tp <= 0:
return
if self._pip_size > 0 and down_fractal - tp <= self._pip_size:
return
vol = self.BaseVolume * 2.0
if vol > 0:
self._pending_orders.append({
"side": "sell", "price": self._normalize_price(down_fractal),
"stop_loss": self._normalize_price(lips),
"take_profit": self._normalize_price(tp),
"volume": vol, "is_primary": True
})
has_primary = any(o["side"] == "sell" and o["is_primary"] for o in self._pending_orders)
if not has_primary or has_secondary:
return
distance = lips - down_fractal
if self._pip_size > 0:
if distance <= self._pip_size:
return
if close_price - self._pip_size <= down_fractal:
return
tp2 = lips - distance * self.Fibo2
if tp2 <= 0:
return
if self._pip_size > 0 and down_fractal - tp2 <= self._pip_size:
return
vol2 = self.BaseVolume
if vol2 > 0:
self._pending_orders.append({
"side": "sell", "price": self._normalize_price(down_fractal),
"stop_loss": self._normalize_price(lips),
"take_profit": self._normalize_price(tp2),
"volume": vol2, "is_primary": False
})
def _trigger_pending_orders(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
i = len(self._pending_orders) - 1
while i >= 0:
pending = self._pending_orders[i]
triggered = False
if pending["side"] == "buy":
triggered = h >= pending["price"]
else:
triggered = lo <= pending["price"]
if triggered:
self._execute_trade(pending)
self._pending_orders.pop(i)
i -= 1
def _execute_trade(self, order):
if order["volume"] <= 0:
return
if order["side"] == "buy":
self.BuyMarket()
else:
self.SellMarket()
self._active_trades.append({
"side": order["side"],
"volume": order["volume"],
"entry_price": order["price"],
"stop_loss": order["stop_loss"],
"take_profit": order["take_profit"],
"is_primary": order["is_primary"],
"moved_to_breakeven": False
})
def _update_trailing(self, close_price):
if self._ma21_value is None:
return
if self._std_dev_current is None or self._std_dev_prev is None:
return
if self._ao_current is None or self._ao_prev is None:
return
if self._ac_current is None or self._ac_prev is None:
return
ma21 = self._ma21_value
std0 = self._std_dev_current
std1 = self._std_dev_prev
ao0 = self._ao_current
ao1 = self._ao_prev
ac0 = self._ac_current
ac1 = self._ac_prev
for trade in self._active_trades:
if trade["side"] == "buy":
sl = trade["stop_loss"]
if (sl <= 0 or (sl != ma21 and sl < ma21)) and std0 > std1 and ao0 > ao1 and ac0 > ac1:
if self._pip_size <= 0 or ma21 + self._pip_size <= close_price:
trade["stop_loss"] = self._normalize_price(ma21)
else:
sl = trade["stop_loss"]
if (sl <= 0 or (sl != ma21 and sl > ma21)) and std0 > std1 and ao0 < ao1 and ac0 < ac1:
if self._pip_size <= 0 or ma21 - self._pip_size >= close_price:
trade["stop_loss"] = self._normalize_price(ma21)
def _update_breakeven(self, close_price):
if not self.UseBreakeven or self._breakeven_plus_value <= 0:
return
for trade in self._active_trades:
if trade["is_primary"] or trade["moved_to_breakeven"]:
continue
primary_exists = any(t["side"] == trade["side"] and t["is_primary"] for t in self._active_trades)
if primary_exists:
continue
if trade["side"] == "buy":
if close_price >= trade["entry_price"] + self._breakeven_plus_value and trade["stop_loss"] < trade["entry_price"]:
trade["stop_loss"] = self._normalize_price(trade["entry_price"] + self._breakeven_plus_value)
trade["moved_to_breakeven"] = True
else:
if close_price <= trade["entry_price"] - self._breakeven_plus_value and trade["stop_loss"] > trade["entry_price"]:
trade["stop_loss"] = self._normalize_price(trade["entry_price"] - self._breakeven_plus_value)
trade["moved_to_breakeven"] = True
def _handle_stops_and_targets(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
i = len(self._active_trades) - 1
while i >= 0:
trade = self._active_trades[i]
close = False
if trade["side"] == "buy":
if trade["take_profit"] > 0 and h >= trade["take_profit"]:
close = True
elif trade["stop_loss"] > 0 and lo <= trade["stop_loss"]:
close = True
else:
if trade["take_profit"] > 0 and lo <= trade["take_profit"]:
close = True
elif trade["stop_loss"] > 0 and h >= trade["stop_loss"]:
close = True
if close:
self._close_trade(trade)
self._active_trades.pop(i)
i -= 1
def _apply_lips_exit(self):
if self._prev_open is None or self._lips_value is None:
return
prev_open = self._prev_open
lips = self._lips_value
if lips > prev_open:
self._close_trades_by_side("buy")
if lips < prev_open:
self._close_trades_by_side("sell")
def _close_trades_by_side(self, side):
i = len(self._active_trades) - 1
while i >= 0:
trade = self._active_trades[i]
if trade["side"] == side:
self._close_trade(trade)
self._active_trades.pop(i)
i -= 1
def _close_trade(self, trade):
if trade["side"] == "buy":
self.SellMarket()
else:
self.BuyMarket()
def _is_trading_time(self, time):
if not self.UseTime:
return True
hour = time.Hour
trading = False
if self.OpenHour > self.CloseHour:
trading = hour <= self.CloseHour or hour >= self.OpenHour
elif self.OpenHour < self.CloseHour:
trading = hour >= self.OpenHour and hour <= self.CloseHour
else:
trading = hour == self.OpenHour
day_of_week = int(time.DayOfWeek)
if day_of_week == 1 and hour < 3:
trading = False
if day_of_week >= 5 and hour > 18:
trading = False
month = time.Month
day = time.Day
if month == 1 and day < 10:
trading = False
if month == 12 and day > 20:
trading = False
return trading
def OnReseted(self):
super(js_chaos_strategy, self).OnReseted()
self._jaw_queue = []
self._teeth_queue = []
self._lips_queue = []
self._jaw_value = None
self._teeth_value = None
self._lips_value = None
self._ma21_value = None
self._ao_current = None
self._ao_prev = None
self._ac_current = None
self._ac_prev = None
self._std_dev_current = None
self._std_dev_prev = None
self._highs = [None] * 5
self._lows = [None] * 5
self._buffer_count = 0
self._up_fractals = []
self._down_fractals = []
self._pending_orders = []
self._active_trades = []
self._pip_size = 0.0
self._indent_value = 0.0
self._breakeven_plus_value = 0.0
self._price_settings_ready = False
self._prev_open = None
def CreateClone(self):
return js_chaos_strategy()