Стратегия Fuzzy Logic
Общее описание
Стратегия Fuzzy Logic переносит эксперт Fuzzy logic (редакция barabashkakvn) с MT5 на высокоуровневый API StockSharp. Алгоритм измеряет силу тренда и признаки истощения при помощи индикаторов Билла Вильямса и классических осцилляторов, переводит эти данные в степени принадлежности и собирает их в единый показатель решения от 0 до 1.
Торговые действия выполняются при пересечении установленных границ:
- Decision > 0.75 – открыть короткую позицию (выраженное истощение, перекупленность).
- Decision < 0.25 – открыть длинную позицию (сильный сигнал разворота вверх).
Сделки сопровождаются фиксированными уровнями тейк-профита и стоп-лосса (в шагах цены). При задании величины трейлинг-стопа обычный стоп автоматически переводится в режим сопровождения.
Набор индикаторов
| Компонент | Назначение |
|---|---|
| Гатор (на основе линий Аллигатора) | Сумма разрывов между челюстью, зубами и губами отражает расширение или сжатие тренда. |
| Williams %R (14) | Отслеживает зоны перекупленности и перепроданности. |
| Acceleration/Deceleration Oscillator | Считает количество последовательных сдвигов импульса, оценивая ускорение тренда. |
| DeMarker (14) | Подтверждает истощение через сравнение экстремумов. Реализован вручную в коде стратегии. |
| RSI (14) | Классический осциллятор импульса. |
Линии Аллигатора рассчитываются сглаженными средними и сдвигаются вперед (8/5/3 бара) аналогично оригинальному советнику, чтобы получить идентичный Гатор. Значения AC формируются из индикатора Awesome Oscillator (SMA 5 и 34) и его 5-периодной средней, что полностью совпадает с функцией iAC в MT5.
Логика торговли
- Значения каждого индикатора преобразуются в пять уровней принадлежности (от сильно медвежьего до сильно бычьего). Используются те же линейные участки, что и в исходном коде MT5.
- Пять групп взвешиваются коэффициентами 0.133, 0.133, 0.133, 0.268, 0.333 и суммируются в четыре агрегированные корзины.
- Итоговый показатель решения вычисляется по формуле
Σ summary[x] * (0.2 * (x + 1) - 0.1)и лежит в диапазоне[0, 1]. - Сигналы оцениваются только на закрытых свечах. Новая позиция открывается, только если стратегия находится вне рынка.
- Объём ордера задаётся свойством
Volume(по умолчанию 1). Защита позиций управляется черезStartProtection.
Управление рисками
- StopLossPoints – дистанция стоп-лосса в шагах цены. Используется, если
TrailingStopPoints= 0. - TrailingStopPoints – при значении > 0 стоп-лосс принимает эту дистанцию и переходит в режим трейлинг-стопа.
- TakeProfitPoints – дистанция тейк-профита в шагах цены.
Параметры
| Параметр | Описание |
|---|---|
CandleType |
Тип / таймфрейм свечей для расчётов. |
BuyThreshold |
Порог, ниже которого открывается длинная позиция. Значение по умолчанию 0.25. |
SellThreshold |
Порог, выше которого открывается короткая позиция. Значение по умолчанию 0.75. |
StopLossPoints |
Дистанция стоп-лосса в шагах цены. Значение по умолчанию 60. |
TakeProfitPoints |
Дистанция тейк-профита в шагах цены. Значение по умолчанию 20. |
TrailingStopPoints |
Дистанция трейлинг-стопа в шагах цены. Значение по умолчанию 0 (выключено). |
WilliamsPeriod |
Период индикатора Williams %R. Значение по умолчанию 14. |
RsiPeriod |
Период RSI. Значение по умолчанию 14. |
DeMarkerPeriod |
Период расчёта встроенного DeMarker. Значение по умолчанию 14. |
Особенности реализации
- Индикатор DeMarker реализован вручную: изменения максимумов и минимумов накапливаются в очередях, что повторяет логику MT5.
- Для AC сохраняются последние пять значений, поэтому условия последовательного ускорения полностью соответствуют вызовам
iAC(..., shift). - Сдвиги линий Аллигатора (8/5/3 баров) воспроизведены через внутренние буферы, благодаря чему величина Гатора совпадает с оригиналом.
- Стратегия открывает новую позицию только при
Position == 0, тем самым повторяя поведение исходного советника, который работал с единственной позицией.
Как использовать
- Присоедините стратегию к портфелю и инструменту в Designer или Backtester.
- Задайте нужный тип свечей через параметр
CandleType. - При необходимости скорректируйте пороги решений и уровни стопов.
- Запустите стратегию – сделки будут появляться автоматически при пересечении заданных уровней fuzzy-оценки.
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>
/// Fuzzy logic strategy combining Bill Williams oscillators, RSI and DeMarker.
/// Opens short positions when the fuzzy score indicates exhaustion and
/// opens long positions during oversold momentum reversals.
/// </summary>
public class FuzzyLogicStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _buyThreshold;
private readonly StrategyParam<decimal> _sellThreshold;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<int> _williamsPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _deMarkerPeriod;
private WilliamsR _williamsIndicator = null!;
private RelativeStrengthIndex _rsiIndicator = null!;
private readonly SmoothedMovingAverage _jaw = new() { Length = 13 };
private readonly SmoothedMovingAverage _teeth = new() { Length = 8 };
private readonly SmoothedMovingAverage _lips = new() { Length = 5 };
private readonly SimpleMovingAverage _aoFast = new() { Length = 5 };
private readonly SimpleMovingAverage _aoSlow = new() { Length = 34 };
private readonly SimpleMovingAverage _acAverage = new() { Length = 5 };
private readonly decimal?[] _jawBuffer = new decimal?[9];
private readonly decimal?[] _teethBuffer = new decimal?[6];
private readonly decimal?[] _lipsBuffer = new decimal?[4];
private int _jawCount;
private int _teethCount;
private int _lipsCount;
private readonly decimal[] _acHistory = new decimal[5];
private int _acCount;
private readonly Queue<decimal> _deMaxQueue = new();
private readonly Queue<decimal> _deMinQueue = new();
private decimal _deMaxSum;
private decimal _deMinSum;
private decimal? _previousHigh;
private decimal? _previousLow;
/// <summary>
/// Candle series type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Decision value that triggers long entries.
/// </summary>
public decimal BuyThreshold
{
get => _buyThreshold.Value;
set => _buyThreshold.Value = value;
}
/// <summary>
/// Decision value that triggers short entries.
/// </summary>
public decimal SellThreshold
{
get => _sellThreshold.Value;
set => _sellThreshold.Value = value;
}
/// <summary>
/// Initial stop-loss distance in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Williams %R lookback.
/// </summary>
public int WilliamsPeriod
{
get => _williamsPeriod.Value;
set => _williamsPeriod.Value = value;
}
/// <summary>
/// RSI lookback.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// DeMarker oscillator lookback.
/// </summary>
public int DeMarkerPeriod
{
get => _deMarkerPeriod.Value;
set => _deMarkerPeriod.Value = value;
}
/// <summary>
/// Initialize <see cref="FuzzyLogicStrategy"/>.
/// </summary>
public FuzzyLogicStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to analyze", "General");
_buyThreshold = Param(nameof(BuyThreshold), 0.15m)
.SetDisplay("Buy Threshold", "Decision level for long entries", "Trading")
.SetRange(0.1m, 0.5m)
;
_sellThreshold = Param(nameof(SellThreshold), 0.85m)
.SetDisplay("Sell Threshold", "Decision level for short entries", "Trading")
.SetRange(0.5m, 0.9m)
;
_stopLossPoints = Param(nameof(StopLossPoints), 60m)
.SetDisplay("Stop Loss (points)", "Protective stop distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 40m)
.SetDisplay("Take Profit (points)", "Target distance in price steps", "Risk");
_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
.SetDisplay("Trailing Stop (points)", "Trailing stop distance in price steps", "Risk");
_williamsPeriod = Param(nameof(WilliamsPeriod), 14)
.SetDisplay("Williams %R Period", "Lookback for Williams %R", "Indicators")
;
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetDisplay("RSI Period", "Lookback for RSI", "Indicators")
;
_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
.SetDisplay("DeMarker Period", "Lookback for DeMarker", "Indicators")
;
Volume = 1;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Array.Clear(_jawBuffer);
Array.Clear(_teethBuffer);
Array.Clear(_lipsBuffer);
_jawCount = 0;
_teethCount = 0;
_lipsCount = 0;
Array.Clear(_acHistory);
_acCount = 0;
_deMaxQueue.Clear();
_deMinQueue.Clear();
_deMaxSum = 0m;
_deMinSum = 0m;
_previousHigh = null;
_previousLow = null;
_jaw.Reset();
_teeth.Reset();
_lips.Reset();
_aoFast.Reset();
_aoSlow.Reset();
_acAverage.Reset();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_williamsIndicator = new WilliamsR { Length = WilliamsPeriod };
_rsiIndicator = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_williamsIndicator, _rsiIndicator, ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 1m;
var stopDistance = TrailingStopPoints > 0m ? TrailingStopPoints : StopLossPoints;
var slUnit = stopDistance > 0m ? new Unit(stopDistance * step, UnitTypes.Absolute) : new Unit();
var tpUnit = TakeProfitPoints > 0m ? new Unit(TakeProfitPoints * step, UnitTypes.Absolute) : new Unit();
StartProtection(slUnit, tpUnit);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _williamsIndicator);
DrawIndicator(area, _rsiIndicator);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue wprValue, IIndicatorValue rsiValue)
{
// Work only with finished candles to avoid partial data.
if (candle.State != CandleStates.Finished)
return;
var hl2 = (candle.HighPrice + candle.LowPrice) / 2m;
var hl2Input = new DecimalIndicatorValue(_jaw, hl2, candle.OpenTime) { IsFinal = true };
var jawValue = _jaw.Process(hl2Input);
var teethValue = _teeth.Process(new DecimalIndicatorValue(_teeth, hl2, candle.OpenTime) { IsFinal = true });
var lipsValue = _lips.Process(new DecimalIndicatorValue(_lips, hl2, candle.OpenTime) { IsFinal = true });
var aoFastValue = _aoFast.Process(new DecimalIndicatorValue(_aoFast, hl2, candle.OpenTime) { IsFinal = true });
var aoSlowValue = _aoSlow.Process(new DecimalIndicatorValue(_aoSlow, hl2, candle.OpenTime) { IsFinal = true });
if (!jawValue.IsFinal || !teethValue.IsFinal || !lipsValue.IsFinal || !aoFastValue.IsFinal || !aoSlowValue.IsFinal)
{
UpdateDeMarker(candle);
return;
}
var jawShifted = UpdateShiftBuffer(_jawBuffer, ref _jawCount, 8, jawValue.GetValue<decimal>());
var teethShifted = UpdateShiftBuffer(_teethBuffer, ref _teethCount, 5, teethValue.GetValue<decimal>());
var lipsShifted = UpdateShiftBuffer(_lipsBuffer, ref _lipsCount, 3, lipsValue.GetValue<decimal>());
if (jawShifted is null || teethShifted is null || lipsShifted is null)
{
UpdateDeMarker(candle);
return;
}
var ao = aoFastValue.GetValue<decimal>() - aoSlowValue.GetValue<decimal>();
var acAverageValue = _acAverage.Process(new DecimalIndicatorValue(_acAverage, ao, candle.OpenTime) { IsFinal = true });
if (!acAverageValue.IsFinal)
{
UpdateDeMarker(candle);
return;
}
var ac = ao - acAverageValue.GetValue<decimal>();
var deMarker = UpdateDeMarker(candle);
if (deMarker is null)
{
UpdateAcHistory(ac);
return;
}
if (!wprValue.IsFinal || !rsiValue.IsFinal)
{
UpdateAcHistory(ac);
return;
}
if (_acCount < _acHistory.Length)
{
UpdateAcHistory(ac);
return;
}
var sumGator = Math.Abs(jawShifted.Value - teethShifted.Value) + Math.Abs(teethShifted.Value - lipsShifted.Value);
var wpr = wprValue.ToDecimal();
var rsi = rsiValue.ToDecimal();
var decision = CalculateDecision(sumGator, wpr, deMarker.Value, rsi);
if (Position == 0)
{
if (decision > SellThreshold)
{
SellMarket();
}
else if (decision < BuyThreshold)
{
BuyMarket();
}
}
UpdateAcHistory(ac);
}
private decimal? UpdateShiftBuffer(decimal?[] buffer, ref int filled, int shift, decimal value)
{
for (var i = 0; i < shift; i++)
buffer[i] = buffer[i + 1];
buffer[shift] = value;
if (filled >= shift)
return buffer[0];
filled++;
return null;
}
private decimal? UpdateDeMarker(ICandleMessage candle)
{
// Store previous extremes to compute DeMarker increments.
if (_previousHigh is null || _previousLow is null)
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
return null;
}
var deMax = Math.Max(candle.HighPrice - _previousHigh.Value, 0m);
var deMin = Math.Max(_previousLow.Value - candle.LowPrice, 0m);
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
if (_deMaxQueue.Count == DeMarkerPeriod)
{
_deMaxSum -= _deMaxQueue.Dequeue();
_deMinSum -= _deMinQueue.Dequeue();
}
_deMaxQueue.Enqueue(deMax);
_deMinQueue.Enqueue(deMin);
_deMaxSum += deMax;
_deMinSum += deMin;
if (_deMaxQueue.Count < DeMarkerPeriod)
return null;
var denominator = _deMaxSum + _deMinSum;
return denominator == 0m ? 0m : _deMaxSum / denominator;
}
private void UpdateAcHistory(decimal ac)
{
for (var i = _acHistory.Length - 1; i > 0; i--)
_acHistory[i] = _acHistory[i - 1];
_acHistory[0] = ac;
if (_acCount < _acHistory.Length)
_acCount++;
}
private decimal CalculateDecision(decimal sumGator, decimal wpr, decimal deMarker, decimal rsi)
{
var rang = new decimal[5, 5];
var summary = new decimal[5];
var gatorLevels = new[] { 0.010m, 0.020m, 0.030m, 0.040m, 0.040m, 0.030m, 0.020m, 0.010m };
var wprLevels = new[] { -95m, -90m, -80m, -75m, -25m, -20m, -10m, -5m };
var acLevels = new[] { 5m, 4m, 3m, 2m, 2m, 3m, 4m, 5m };
var deMarkerLevels = new[] { 0.15m, 0.20m, 0.25m, 0.30m, 0.70m, 0.75m, 0.80m, 0.85m };
var rsiLevels = new[] { 25m, 30m, 35m, 40m, 60m, 65m, 70m, 75m };
var weights = new[] { 0.133m, 0.133m, 0.133m, 0.268m, 0.333m };
// 1) Gator oscillator membership.
if (sumGator < gatorLevels[0])
{
rang[0, 0] = 0.5m;
rang[0, 4] = 0.5m;
}
if (sumGator >= gatorLevels[0] && sumGator < gatorLevels[1])
{
var part = (sumGator - gatorLevels[0]) / (gatorLevels[1] - gatorLevels[0]);
rang[0, 0] = (1m - part) / 2m;
rang[0, 1] = (1m - rang[0, 0] * 2m) / 2m;
rang[0, 4] = rang[0, 0];
rang[0, 3] = rang[0, 1];
}
if (sumGator >= gatorLevels[1] && sumGator < gatorLevels[2])
{
rang[0, 1] = 0.5m;
rang[0, 3] = 0.5m;
}
if (sumGator >= gatorLevels[2] && sumGator < gatorLevels[3])
{
var part = (sumGator - gatorLevels[2]) / (gatorLevels[3] - gatorLevels[2]);
rang[0, 1] = (1m - part) / 2m;
rang[0, 2] = 1m - rang[0, 1] * 2m;
rang[0, 3] = rang[0, 1];
}
if (sumGator >= gatorLevels[3])
rang[0, 2] = 1m;
// 2) Williams %R membership.
if (wpr < wprLevels[0])
rang[1, 0] = 1m;
if (wpr >= wprLevels[0] && wpr < wprLevels[1])
{
var part = (wpr - wprLevels[0]) / (wprLevels[1] - wprLevels[0]);
rang[1, 0] = 1m - part;
rang[1, 1] = 1m - rang[1, 0];
}
if (wpr >= wprLevels[1] && wpr < wprLevels[2])
rang[1, 1] = 1m;
if (wpr >= wprLevels[2] && wpr < wprLevels[3])
{
var part = (wpr - wprLevels[2]) / (wprLevels[3] - wprLevels[2]);
rang[1, 1] = 1m - part;
rang[1, 2] = 1m - rang[1, 1];
}
if (wpr >= wprLevels[3] && wpr < wprLevels[4])
rang[1, 2] = 1m;
if (wpr >= wprLevels[4] && wpr < wprLevels[5])
{
var part = (wpr - wprLevels[4]) / (wprLevels[5] - wprLevels[4]);
rang[1, 2] = 1m - part;
rang[1, 3] = 1m - rang[1, 2];
}
if (wpr >= wprLevels[5] && wpr < wprLevels[6])
rang[1, 3] = 1m;
if (wpr >= wprLevels[6] && wpr < wprLevels[7])
{
var part = (wpr - wprLevels[6]) / (wprLevels[7] - wprLevels[6]);
rang[1, 3] = 1m - part;
rang[1, 4] = 1m - rang[1, 3];
}
if (wpr >= wprLevels[7])
rang[1, 4] = 1m;
// 3) Acceleration/Deceleration oscillator sequences.
var tempAcBuy = 0m;
if (_acHistory[0] < _acHistory[1] && _acHistory[0] < 0m && _acHistory[1] < 0m)
tempAcBuy = 2m;
if (_acHistory[0] < _acHistory[1] && _acHistory[1] < _acHistory[2] &&
_acHistory[0] < 0m && _acHistory[1] < 0m && _acHistory[2] < 0m)
tempAcBuy = 3m;
if (_acHistory[0] < _acHistory[1] && _acHistory[1] < _acHistory[2] &&
_acHistory[2] < _acHistory[3] && _acHistory[0] < 0m && _acHistory[1] < 0m &&
_acHistory[2] < 0m && _acHistory[3] < 0m)
tempAcBuy = 4m;
if (_acHistory[0] < _acHistory[1] && _acHistory[1] < _acHistory[2] &&
_acHistory[2] < _acHistory[3] && _acHistory[3] < _acHistory[4] &&
_acHistory[0] < 0m && _acHistory[1] < 0m && _acHistory[2] < 0m &&
_acHistory[3] < 0m && _acHistory[4] < 0m)
tempAcBuy = 5m;
var tempAcSell = 0m;
if (_acHistory[0] > _acHistory[1] && _acHistory[0] > 0m && _acHistory[1] > 0m)
tempAcSell = 2m;
if (_acHistory[0] > _acHistory[1] && _acHistory[1] > _acHistory[2] &&
_acHistory[0] > 0m && _acHistory[1] > 0m && _acHistory[2] > 0m)
tempAcSell = 3m;
if (_acHistory[0] > _acHistory[1] && _acHistory[1] > _acHistory[2] &&
_acHistory[2] > _acHistory[3] && _acHistory[0] > 0m && _acHistory[1] > 0m &&
_acHistory[2] > 0m && _acHistory[3] > 0m)
tempAcSell = 4m;
if (_acHistory[0] > _acHistory[1] && _acHistory[1] > _acHistory[2] &&
_acHistory[2] > _acHistory[3] && _acHistory[3] > _acHistory[4] &&
_acHistory[0] > 0m && _acHistory[1] > 0m && _acHistory[2] > 0m &&
_acHistory[3] > 0m && _acHistory[4] > 0m)
tempAcSell = 5m;
if (tempAcBuy == acLevels[0] || tempAcBuy == acLevels[1])
rang[2, 0] = 1m;
if (tempAcBuy == acLevels[2] || tempAcBuy == acLevels[3])
rang[2, 1] = 1m;
if (tempAcSell == acLevels[4] || tempAcSell == acLevels[5])
rang[2, 3] = 1m;
if (tempAcSell == acLevels[6] || tempAcSell == acLevels[7])
rang[2, 4] = 1m;
if (rang[2, 0] == 0m && rang[2, 1] == 0m && rang[2, 3] == 0m && rang[2, 4] == 0m)
rang[2, 2] = 1m;
// 4) DeMarker membership.
if (deMarker < deMarkerLevels[0])
rang[3, 0] = 1m;
if (deMarker >= deMarkerLevels[0] && deMarker < deMarkerLevels[1])
{
var part = (deMarker - deMarkerLevels[0]) / (deMarkerLevels[1] - deMarkerLevels[0]);
rang[3, 0] = 1m - part;
rang[3, 1] = 1m - rang[3, 0];
}
if (deMarker >= deMarkerLevels[1] && deMarker < deMarkerLevels[2])
rang[3, 1] = 1m;
if (deMarker >= deMarkerLevels[2] && deMarker < deMarkerLevels[3])
{
var part = (deMarker - deMarkerLevels[2]) / (deMarkerLevels[3] - deMarkerLevels[2]);
rang[3, 1] = 1m - part;
rang[3, 2] = 1m - rang[3, 1];
}
if (deMarker >= deMarkerLevels[3] && deMarker < deMarkerLevels[4])
rang[3, 2] = 1m;
if (deMarker >= deMarkerLevels[4] && deMarker < deMarkerLevels[5])
{
var part = (deMarker - deMarkerLevels[4]) / (deMarkerLevels[5] - deMarkerLevels[4]);
rang[3, 2] = 1m - part;
rang[3, 3] = 1m - rang[3, 2];
}
if (deMarker >= deMarkerLevels[5] && deMarker < deMarkerLevels[6])
rang[3, 3] = 1m;
if (deMarker >= deMarkerLevels[6] && deMarker < deMarkerLevels[7])
{
var part = (deMarker - deMarkerLevels[6]) / (deMarkerLevels[7] - deMarkerLevels[6]);
rang[3, 3] = 1m - part;
rang[3, 4] = 1m - rang[3, 3];
}
if (deMarker >= deMarkerLevels[7])
rang[3, 4] = 1m;
// 5) RSI membership.
if (rsi < rsiLevels[0])
rang[4, 0] = 1m;
if (rsi >= rsiLevels[0] && rsi < rsiLevels[1])
{
var part = (rsi - rsiLevels[0]) / (rsiLevels[1] - rsiLevels[0]);
rang[4, 0] = 1m - part;
rang[4, 1] = 1m - rang[4, 0];
}
if (rsi >= rsiLevels[1] && rsi < rsiLevels[2])
rang[4, 1] = 1m;
if (rsi >= rsiLevels[2] && rsi < rsiLevels[3])
{
var part = (rsi - rsiLevels[2]) / (rsiLevels[3] - rsiLevels[2]);
rang[4, 1] = 1m - part;
rang[4, 2] = 1m - rang[4, 1];
}
if (rsi >= rsiLevels[3] && rsi < rsiLevels[4])
rang[4, 2] = 1m;
if (rsi >= rsiLevels[4] && rsi < rsiLevels[5])
{
var part = (rsi - rsiLevels[4]) / (rsiLevels[5] - rsiLevels[4]);
rang[4, 2] = 1m - part;
rang[4, 3] = 1m - rang[4, 2];
}
if (rsi >= rsiLevels[5] && rsi < rsiLevels[6])
rang[4, 3] = 1m;
if (rsi >= rsiLevels[6] && rsi < rsiLevels[7])
{
var part = (rsi - rsiLevels[6]) / (rsiLevels[7] - rsiLevels[6]);
rang[4, 3] = 1m - part;
rang[4, 4] = 1m - rang[4, 3];
}
if (rsi >= rsiLevels[7])
rang[4, 4] = 1m;
for (var x = 0; x < 4; x++)
{
for (var y = 0; y < 4; y++)
summary[x] += rang[y, x] * weights[x];
}
var decision = 0m;
for (var x = 0; x < 4; x++)
decision += summary[x] * (0.2m * (x + 1) - 0.1m);
return decision;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import WilliamsR, RelativeStrengthIndex, SmoothedMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class fuzzy_logic_strategy(Strategy):
def __init__(self):
super(fuzzy_logic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._buy_threshold = self.Param("BuyThreshold", 0.15)
self._sell_threshold = self.Param("SellThreshold", 0.85)
self._stop_loss_points = self.Param("StopLossPoints", 60.0)
self._take_profit_points = self.Param("TakeProfitPoints", 40.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 0.0)
self._williams_period = self.Param("WilliamsPeriod", 14)
self._rsi_period = self.Param("RsiPeriod", 14)
self._demarker_period = self.Param("DeMarkerPeriod", 14)
self._jaw_buffer = [None] * 9
self._teeth_buffer = [None] * 6
self._lips_buffer = [None] * 4
self._jaw_count = 0
self._teeth_count = 0
self._lips_count = 0
self._ac_history = [0.0] * 5
self._ac_count = 0
self._de_max_queue = []
self._de_min_queue = []
self._de_max_sum = 0.0
self._de_min_sum = 0.0
self._previous_high = None
self._previous_low = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BuyThreshold(self):
return self._buy_threshold.Value
@BuyThreshold.setter
def BuyThreshold(self, value):
self._buy_threshold.Value = value
@property
def SellThreshold(self):
return self._sell_threshold.Value
@SellThreshold.setter
def SellThreshold(self, value):
self._sell_threshold.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@TrailingStopPoints.setter
def TrailingStopPoints(self, value):
self._trailing_stop_points.Value = value
@property
def WilliamsPeriod(self):
return self._williams_period.Value
@WilliamsPeriod.setter
def WilliamsPeriod(self, value):
self._williams_period.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def DeMarkerPeriod(self):
return self._demarker_period.Value
@DeMarkerPeriod.setter
def DeMarkerPeriod(self, value):
self._demarker_period.Value = value
def OnStarted2(self, time):
super(fuzzy_logic_strategy, self).OnStarted2(time)
self._jaw_buffer = [None] * 9
self._teeth_buffer = [None] * 6
self._lips_buffer = [None] * 4
self._jaw_count = 0
self._teeth_count = 0
self._lips_count = 0
self._ac_history = [0.0] * 5
self._ac_count = 0
self._de_max_queue = []
self._de_min_queue = []
self._de_max_sum = 0.0
self._de_min_sum = 0.0
self._previous_high = None
self._previous_low = None
self._jaw = SmoothedMovingAverage()
self._jaw.Length = 13
self._teeth = SmoothedMovingAverage()
self._teeth.Length = 8
self._lips = SmoothedMovingAverage()
self._lips.Length = 5
self._ao_fast = SimpleMovingAverage()
self._ao_fast.Length = 5
self._ao_slow = SimpleMovingAverage()
self._ao_slow.Length = 34
self._ac_average = SimpleMovingAverage()
self._ac_average.Length = 5
self._williams = WilliamsR()
self._williams.Length = self.WilliamsPeriod
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._williams, self._rsi, self.ProcessCandle).Start()
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
stop_distance = float(self.TrailingStopPoints) if float(self.TrailingStopPoints) > 0.0 else float(self.StopLossPoints)
sl_unit = Unit(stop_distance * step, UnitTypes.Absolute) if stop_distance > 0.0 else Unit()
tp_unit = Unit(float(self.TakeProfitPoints) * step, UnitTypes.Absolute) if float(self.TakeProfitPoints) > 0.0 else Unit()
self.StartProtection(sl_unit, tp_unit)
def ProcessCandle(self, candle, wpr_value, rsi_value):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
hl2 = (high + low) / 2.0
jaw_result = process_float(self._jaw, Decimal(hl2), candle.OpenTime, True)
teeth_result = process_float(self._teeth, Decimal(hl2), candle.OpenTime, True)
lips_result = process_float(self._lips, Decimal(hl2), candle.OpenTime, True)
ao_fast_result = process_float(self._ao_fast, Decimal(hl2), candle.OpenTime, True)
ao_slow_result = process_float(self._ao_slow, Decimal(hl2), candle.OpenTime, True)
if not jaw_result.IsFinal or not teeth_result.IsFinal or not lips_result.IsFinal or not ao_fast_result.IsFinal or not ao_slow_result.IsFinal:
self._update_demarker(candle)
return
jaw_shifted = self._update_shift_buffer_jaw(float(jaw_result))
teeth_shifted = self._update_shift_buffer_teeth(float(teeth_result))
lips_shifted = self._update_shift_buffer_lips(float(lips_result))
if jaw_shifted is None or teeth_shifted is None or lips_shifted is None:
self._update_demarker(candle)
return
ao = float(ao_fast_result) - float(ao_slow_result)
ac_avg_result = process_float(self._ac_average, Decimal(ao), candle.OpenTime, True)
if not ac_avg_result.IsFinal:
self._update_demarker(candle)
return
ac = ao - float(ac_avg_result)
demarker = self._update_demarker(candle)
if demarker is None:
self._update_ac_history(ac)
return
if not wpr_value.IsFinal or not rsi_value.IsFinal:
self._update_ac_history(ac)
return
if self._ac_count < 5:
self._update_ac_history(ac)
return
sum_gator = abs(jaw_shifted - teeth_shifted) + abs(teeth_shifted - lips_shifted)
wpr = float(wpr_value)
rsi = float(rsi_value)
decision = self._calculate_decision(sum_gator, wpr, demarker, rsi)
if self.Position == 0:
if decision > float(self.SellThreshold):
self.SellMarket()
elif decision < float(self.BuyThreshold):
self.BuyMarket()
self._update_ac_history(ac)
def _update_shift_buffer_jaw(self, value):
shift = 8
for i in range(shift):
self._jaw_buffer[i] = self._jaw_buffer[i + 1]
self._jaw_buffer[shift] = value
if self._jaw_count >= shift:
return self._jaw_buffer[0]
self._jaw_count += 1
return None
def _update_shift_buffer_teeth(self, value):
shift = 5
for i in range(shift):
self._teeth_buffer[i] = self._teeth_buffer[i + 1]
self._teeth_buffer[shift] = value
if self._teeth_count >= shift:
return self._teeth_buffer[0]
self._teeth_count += 1
return None
def _update_shift_buffer_lips(self, value):
shift = 3
for i in range(shift):
self._lips_buffer[i] = self._lips_buffer[i + 1]
self._lips_buffer[shift] = value
if self._lips_count >= shift:
return self._lips_buffer[0]
self._lips_count += 1
return None
def _update_demarker(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self._previous_high is None or self._previous_low is None:
self._previous_high = high
self._previous_low = low
return None
de_max = max(high - self._previous_high, 0.0)
de_min = max(self._previous_low - low, 0.0)
self._previous_high = high
self._previous_low = low
period = int(self.DeMarkerPeriod)
if len(self._de_max_queue) == period:
self._de_max_sum -= self._de_max_queue.pop(0)
self._de_min_sum -= self._de_min_queue.pop(0)
self._de_max_queue.append(de_max)
self._de_min_queue.append(de_min)
self._de_max_sum += de_max
self._de_min_sum += de_min
if len(self._de_max_queue) < period:
return None
denominator = self._de_max_sum + self._de_min_sum
if denominator == 0.0:
return 0.0
return self._de_max_sum / denominator
def _update_ac_history(self, ac):
for i in range(len(self._ac_history) - 1, 0, -1):
self._ac_history[i] = self._ac_history[i - 1]
self._ac_history[0] = ac
if self._ac_count < len(self._ac_history):
self._ac_count += 1
def _calculate_decision(self, sum_gator, wpr, demarker, rsi):
rang = [[0.0] * 5 for _ in range(5)]
summary = [0.0] * 5
gator_levels = [0.010, 0.020, 0.030, 0.040, 0.040, 0.030, 0.020, 0.010]
wpr_levels = [-95.0, -90.0, -80.0, -75.0, -25.0, -20.0, -10.0, -5.0]
ac_levels = [5.0, 4.0, 3.0, 2.0, 2.0, 3.0, 4.0, 5.0]
demarker_levels = [0.15, 0.20, 0.25, 0.30, 0.70, 0.75, 0.80, 0.85]
rsi_levels = [25.0, 30.0, 35.0, 40.0, 60.0, 65.0, 70.0, 75.0]
weights = [0.133, 0.133, 0.133, 0.268, 0.333]
# 1) Gator oscillator membership
if sum_gator < gator_levels[0]:
rang[0][0] = 0.5
rang[0][4] = 0.5
if sum_gator >= gator_levels[0] and sum_gator < gator_levels[1]:
part = (sum_gator - gator_levels[0]) / (gator_levels[1] - gator_levels[0])
rang[0][0] = (1.0 - part) / 2.0
rang[0][1] = (1.0 - rang[0][0] * 2.0) / 2.0
rang[0][4] = rang[0][0]
rang[0][3] = rang[0][1]
if sum_gator >= gator_levels[1] and sum_gator < gator_levels[2]:
rang[0][1] = 0.5
rang[0][3] = 0.5
if sum_gator >= gator_levels[2] and sum_gator < gator_levels[3]:
part = (sum_gator - gator_levels[2]) / (gator_levels[3] - gator_levels[2])
rang[0][1] = (1.0 - part) / 2.0
rang[0][2] = 1.0 - rang[0][1] * 2.0
rang[0][3] = rang[0][1]
if sum_gator >= gator_levels[3]:
rang[0][2] = 1.0
# 2) Williams %R membership
if wpr < wpr_levels[0]:
rang[1][0] = 1.0
if wpr >= wpr_levels[0] and wpr < wpr_levels[1]:
part = (wpr - wpr_levels[0]) / (wpr_levels[1] - wpr_levels[0])
rang[1][0] = 1.0 - part
rang[1][1] = 1.0 - rang[1][0]
if wpr >= wpr_levels[1] and wpr < wpr_levels[2]:
rang[1][1] = 1.0
if wpr >= wpr_levels[2] and wpr < wpr_levels[3]:
part = (wpr - wpr_levels[2]) / (wpr_levels[3] - wpr_levels[2])
rang[1][1] = 1.0 - part
rang[1][2] = 1.0 - rang[1][1]
if wpr >= wpr_levels[3] and wpr < wpr_levels[4]:
rang[1][2] = 1.0
if wpr >= wpr_levels[4] and wpr < wpr_levels[5]:
part = (wpr - wpr_levels[4]) / (wpr_levels[5] - wpr_levels[4])
rang[1][2] = 1.0 - part
rang[1][3] = 1.0 - rang[1][2]
if wpr >= wpr_levels[5] and wpr < wpr_levels[6]:
rang[1][3] = 1.0
if wpr >= wpr_levels[6] and wpr < wpr_levels[7]:
part = (wpr - wpr_levels[6]) / (wpr_levels[7] - wpr_levels[6])
rang[1][3] = 1.0 - part
rang[1][4] = 1.0 - rang[1][3]
if wpr >= wpr_levels[7]:
rang[1][4] = 1.0
# 3) Acceleration/Deceleration oscillator sequences
h = self._ac_history
temp_ac_buy = 0.0
if h[0] < h[1] and h[0] < 0.0 and h[1] < 0.0:
temp_ac_buy = 2.0
if h[0] < h[1] and h[1] < h[2] and h[0] < 0.0 and h[1] < 0.0 and h[2] < 0.0:
temp_ac_buy = 3.0
if h[0] < h[1] and h[1] < h[2] and h[2] < h[3] and h[0] < 0.0 and h[1] < 0.0 and h[2] < 0.0 and h[3] < 0.0:
temp_ac_buy = 4.0
if h[0] < h[1] and h[1] < h[2] and h[2] < h[3] and h[3] < h[4] and h[0] < 0.0 and h[1] < 0.0 and h[2] < 0.0 and h[3] < 0.0 and h[4] < 0.0:
temp_ac_buy = 5.0
temp_ac_sell = 0.0
if h[0] > h[1] and h[0] > 0.0 and h[1] > 0.0:
temp_ac_sell = 2.0
if h[0] > h[1] and h[1] > h[2] and h[0] > 0.0 and h[1] > 0.0 and h[2] > 0.0:
temp_ac_sell = 3.0
if h[0] > h[1] and h[1] > h[2] and h[2] > h[3] and h[0] > 0.0 and h[1] > 0.0 and h[2] > 0.0 and h[3] > 0.0:
temp_ac_sell = 4.0
if h[0] > h[1] and h[1] > h[2] and h[2] > h[3] and h[3] > h[4] and h[0] > 0.0 and h[1] > 0.0 and h[2] > 0.0 and h[3] > 0.0 and h[4] > 0.0:
temp_ac_sell = 5.0
if temp_ac_buy == ac_levels[0] or temp_ac_buy == ac_levels[1]:
rang[2][0] = 1.0
if temp_ac_buy == ac_levels[2] or temp_ac_buy == ac_levels[3]:
rang[2][1] = 1.0
if temp_ac_sell == ac_levels[4] or temp_ac_sell == ac_levels[5]:
rang[2][3] = 1.0
if temp_ac_sell == ac_levels[6] or temp_ac_sell == ac_levels[7]:
rang[2][4] = 1.0
if rang[2][0] == 0.0 and rang[2][1] == 0.0 and rang[2][3] == 0.0 and rang[2][4] == 0.0:
rang[2][2] = 1.0
# 4) DeMarker membership
if demarker < demarker_levels[0]:
rang[3][0] = 1.0
if demarker >= demarker_levels[0] and demarker < demarker_levels[1]:
part = (demarker - demarker_levels[0]) / (demarker_levels[1] - demarker_levels[0])
rang[3][0] = 1.0 - part
rang[3][1] = 1.0 - rang[3][0]
if demarker >= demarker_levels[1] and demarker < demarker_levels[2]:
rang[3][1] = 1.0
if demarker >= demarker_levels[2] and demarker < demarker_levels[3]:
part = (demarker - demarker_levels[2]) / (demarker_levels[3] - demarker_levels[2])
rang[3][1] = 1.0 - part
rang[3][2] = 1.0 - rang[3][1]
if demarker >= demarker_levels[3] and demarker < demarker_levels[4]:
rang[3][2] = 1.0
if demarker >= demarker_levels[4] and demarker < demarker_levels[5]:
part = (demarker - demarker_levels[4]) / (demarker_levels[5] - demarker_levels[4])
rang[3][2] = 1.0 - part
rang[3][3] = 1.0 - rang[3][2]
if demarker >= demarker_levels[5] and demarker < demarker_levels[6]:
rang[3][3] = 1.0
if demarker >= demarker_levels[6] and demarker < demarker_levels[7]:
part = (demarker - demarker_levels[6]) / (demarker_levels[7] - demarker_levels[6])
rang[3][3] = 1.0 - part
rang[3][4] = 1.0 - rang[3][3]
if demarker >= demarker_levels[7]:
rang[3][4] = 1.0
# 5) RSI membership
if rsi < rsi_levels[0]:
rang[4][0] = 1.0
if rsi >= rsi_levels[0] and rsi < rsi_levels[1]:
part = (rsi - rsi_levels[0]) / (rsi_levels[1] - rsi_levels[0])
rang[4][0] = 1.0 - part
rang[4][1] = 1.0 - rang[4][0]
if rsi >= rsi_levels[1] and rsi < rsi_levels[2]:
rang[4][1] = 1.0
if rsi >= rsi_levels[2] and rsi < rsi_levels[3]:
part = (rsi - rsi_levels[2]) / (rsi_levels[3] - rsi_levels[2])
rang[4][1] = 1.0 - part
rang[4][2] = 1.0 - rang[4][1]
if rsi >= rsi_levels[3] and rsi < rsi_levels[4]:
rang[4][2] = 1.0
if rsi >= rsi_levels[4] and rsi < rsi_levels[5]:
part = (rsi - rsi_levels[4]) / (rsi_levels[5] - rsi_levels[4])
rang[4][2] = 1.0 - part
rang[4][3] = 1.0 - rang[4][2]
if rsi >= rsi_levels[5] and rsi < rsi_levels[6]:
rang[4][3] = 1.0
if rsi >= rsi_levels[6] and rsi < rsi_levels[7]:
part = (rsi - rsi_levels[6]) / (rsi_levels[7] - rsi_levels[6])
rang[4][3] = 1.0 - part
rang[4][4] = 1.0 - rang[4][3]
if rsi >= rsi_levels[7]:
rang[4][4] = 1.0
for x in range(4):
for y in range(4):
summary[x] += rang[y][x] * weights[x]
decision = 0.0
for x in range(4):
decision += summary[x] * (0.2 * (x + 1) - 0.1)
return decision
def OnReseted(self):
super(fuzzy_logic_strategy, self).OnReseted()
self._jaw_buffer = [None] * 9
self._teeth_buffer = [None] * 6
self._lips_buffer = [None] * 4
self._jaw_count = 0
self._teeth_count = 0
self._lips_count = 0
self._ac_history = [0.0] * 5
self._ac_count = 0
self._de_max_queue = []
self._de_min_queue = []
self._de_max_sum = 0.0
self._de_min_sum = 0.0
self._previous_high = None
self._previous_low = None
def CreateClone(self):
return fuzzy_logic_strategy()