Fuzzy Logic Legacy 策略
该策略在 StockSharp 中重现 2007 年发布的 MetaTrader "Fuzzy logic" 专家顾问。它把比尔·威廉姆斯的工具与动量振荡指标结 合,并使用模糊评分表来综合判断市场方向。只有当综合得分显示明显的多头或空头共识时,系统才会开仓。固定止损与可选 的移动止损复刻了原始策略的仓位管理方式。
交易逻辑
- 使用平滑移动平均线构建威廉姆斯鳄鱼指标(下颌、牙齿、嘴唇),并计算三条线之间距离绝对值之和作为 Gator 指标。
- 在同一组K线数据上计算 Williams %R(14)、DeMarker(14)和 RSI(14)。
- 由 Awesome Oscillator 派生加速指标(AC),追踪最多五根连续柱来识别动量加速序列。
- 将每个指标映射到五级的模糊隶属度表,阈值完全沿用原始代码。
- 对所有隶属度进行加权求和,得到 0 到 1 之间的决策值:
- > 0.75 代表多头共识,触发买入。
- < 0.25 代表空头共识,触发卖出。
- 同一时间只允许一笔持仓,入场后立即附加保护性止损。
仓位管理
- 止损:按价格跳动距离设置 (
Stop Loss (points)参数)。 - 移动止损:可选,如启用则按设定的跳动距离跟随。
- 资金管理:可选,使用 MetaTrader 原公式
Volume = (Balance * (PercentMM + DeltaMM) - InitialBalance * DeltaMM) / 10000计算下单量。
参数
| 参数 | 说明 |
|---|---|
Candle Type |
用于分析的K线类型。 |
Long Threshold |
开多仓所需超过的决策阈值。 |
Short Threshold |
开空仓所需低于的决策阈值。 |
Stop Loss (points) |
初始止损的价格跳动数。 |
Trailing Stop (points) |
移动止损的价格跳动数;填 0 表示禁用。 |
Fixed Volume |
关闭资金管理时的固定下单量。 |
Use Money Management |
是否启用 MetaTrader 式的资金管理。 |
Percent MM |
资金管理公式中使用的余额百分比。 |
Delta MM |
资金管理公式中的附加百分比。 |
Initial Balance |
资金管理公式所参考的初始余额。 |
备注
- 策略仅在蜡烛收盘后(
CandleStates.Finished)处理数据,以避免重新绘制。 - 所有阈值与权重均与原版一致,从而保持原有行为模式。
- 如果在日内环境中使用,请根据波动性调整时间框架和阈值。
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 that reproduces the original MetaTrader logic
/// using Bill Williams indicators, RSI and DeMarker.
/// </summary>
public class FuzzyLogicLegacyStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _longThreshold;
private readonly StrategyParam<decimal> _shortThreshold;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _fixedVolume;
private readonly StrategyParam<bool> _useMoneyManagement;
private readonly StrategyParam<decimal> _percentMoneyManagement;
private readonly StrategyParam<decimal> _deltaMoneyManagement;
private readonly StrategyParam<decimal> _initialBalance;
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 level to open long trades.
/// </summary>
public decimal LongThreshold
{
get => _longThreshold.Value;
set => _longThreshold.Value = value;
}
/// <summary>
/// Decision level to open short trades.
/// </summary>
public decimal ShortThreshold
{
get => _shortThreshold.Value;
set => _shortThreshold.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Fixed volume used when money management is disabled.
/// </summary>
public decimal FixedVolume
{
get => _fixedVolume.Value;
set => _fixedVolume.Value = value;
}
/// <summary>
/// Enables balance based position sizing.
/// </summary>
public bool UseMoneyManagement
{
get => _useMoneyManagement.Value;
set => _useMoneyManagement.Value = value;
}
/// <summary>
/// Percentage of account balance used for money management.
/// </summary>
public decimal PercentMoneyManagement
{
get => _percentMoneyManagement.Value;
set => _percentMoneyManagement.Value = value;
}
/// <summary>
/// Additional percentage offset used in the money management formula.
/// </summary>
public decimal DeltaMoneyManagement
{
get => _deltaMoneyManagement.Value;
set => _deltaMoneyManagement.Value = value;
}
/// <summary>
/// Initial balance reference for the money management formula.
/// </summary>
public decimal InitialBalance
{
get => _initialBalance.Value;
set => _initialBalance.Value = value;
}
public FuzzyLogicLegacyStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "Data");
_longThreshold = Param(nameof(LongThreshold), 0.75m)
.SetDisplay("Long Threshold", "Decision level for long entries", "Trading")
.SetRange(0.5m, 0.9m)
;
_shortThreshold = Param(nameof(ShortThreshold), 0.25m)
.SetDisplay("Short Threshold", "Decision level for short entries", "Trading")
.SetRange(0.1m, 0.5m)
;
_stopLossPoints = Param(nameof(StopLossPoints), 60m)
.SetDisplay("Stop Loss (points)", "Stop loss distance in price steps", "Risk Management");
_trailingStopPoints = Param(nameof(TrailingStopPoints), 35m)
.SetDisplay("Trailing Stop (points)", "Trailing stop distance in price steps", "Risk Management");
_fixedVolume = Param(nameof(FixedVolume), 0.1m)
.SetDisplay("Fixed Volume", "Volume per trade when MM is disabled", "Trading");
_useMoneyManagement = Param(nameof(UseMoneyManagement), false)
.SetDisplay("Use Money Management", "Enable balance based sizing", "Trading");
_percentMoneyManagement = Param(nameof(PercentMoneyManagement), 8m)
.SetDisplay("Percent MM", "Percent of balance used in MM", "Trading");
_deltaMoneyManagement = Param(nameof(DeltaMoneyManagement), 0m)
.SetDisplay("Delta MM", "Additional percent offset in MM", "Trading");
_initialBalance = Param(nameof(InitialBalance), 10000m)
.SetDisplay("Initial Balance", "Reference balance for MM", "Trading");
}
/// <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 = 14 };
_rsiIndicator = new RelativeStrengthIndex { Length = 14 };
var subscription = SubscribeCandles(CandleType);
// Bind high-level indicator pipeline to incoming candles.
subscription
.BindEx(_williamsIndicator, _rsiIndicator, ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 1m;
var stopLoss = StopLossPoints > 0m ? new Unit(StopLossPoints * step, UnitTypes.Absolute) : null;
// Mirror the MetaTrader risk rules via the built-in protection helper.
StartProtection(
takeProfit: null,
stopLoss: stopLoss,
isStopTrailing: TrailingStopPoints > 0m,
useMarketOrders: true
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _williamsIndicator);
DrawIndicator(area, _rsiIndicator);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue williamsValue, IIndicatorValue rsiValue)
{
// Work only with finished candles to match the original implementation.
if (candle.State != CandleStates.Finished)
return;
// Awesome Oscillator uses the median price of the candle.
var hl2 = (candle.HighPrice + candle.LowPrice) / 2m;
var jawValue = _jaw.Process(hl2, candle.OpenTime, true);
var teethValue = _teeth.Process(hl2, candle.OpenTime, true);
var lipsValue = _lips.Process(hl2, candle.OpenTime, true);
var aoFastValue = _aoFast.Process(hl2, candle.OpenTime, true);
var aoSlowValue = _aoSlow.Process(hl2, candle.OpenTime, true);
if (!jawValue.IsFinal || !teethValue.IsFinal || !lipsValue.IsFinal || !aoFastValue.IsFinal || !aoSlowValue.IsFinal)
{
UpdateDeMarker(candle);
return;
}
// Shift buffers emulate the built-in offsets of the Alligator indicator.
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(ao, candle.OpenTime, true);
if (!acAverageValue.IsFinal)
{
UpdateDeMarker(candle);
return;
}
// Accelerator Oscillator equals AO minus its moving average.
var ac = ao - acAverageValue.GetValue<decimal>();
var deMarker = UpdateDeMarker(candle);
if (deMarker is null)
{
UpdateAcHistory(ac);
return;
}
if (!williamsValue.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 = williamsValue.ToDecimal();
var rsi = rsiValue.ToDecimal();
var decision = CalculateDecision(sumGator, wpr, deMarker.Value, rsi);
if (IsFormedAndOnlineAndAllowTrading() && Position == 0)
{
var volume = GetTradeVolume();
if (decision < ShortThreshold)
{
SellMarket(volume);
LogInfo($"Decision {decision:F2} below threshold triggered short entry.");
}
else if (decision > LongThreshold)
{
BuyMarket(volume);
LogInfo($"Decision {decision:F2} above threshold triggered long entry.");
}
}
UpdateAcHistory(ac);
}
private decimal? UpdateShiftBuffer(decimal?[] buffer, ref int filled, int shift, decimal value)
{
// Maintain a rolling window with the indicator's visualization shift.
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)
{
// Recreate the original DeMarker implementation using rolling sums.
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 == 14)
{
_deMaxSum -= _deMaxQueue.Dequeue();
_deMinSum -= _deMinQueue.Dequeue();
}
_deMaxQueue.Enqueue(deMax);
_deMinQueue.Enqueue(deMin);
_deMaxSum += deMax;
_deMinSum += deMin;
if (_deMaxQueue.Count < 14)
return null;
var denominator = _deMaxSum + _deMinSum;
return denominator == 0m ? 0m : _deMaxSum / denominator;
}
private void UpdateAcHistory(decimal ac)
{
// Store the last five AC values to detect momentum streaks.
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)
{
// Membership matrix that mirrors the MetaTrader fuzzy rules.
var rang = new decimal[5, 5];
var summary = new decimal[5];
var gatorLevels = new[] { 100m, 200m, 300m, 400m, 400m, 300m, 200m, 100m };
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 };
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;
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;
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;
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;
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;
}
private decimal GetTradeVolume()
{
var volume = FixedVolume;
if (UseMoneyManagement)
{
// Apply the original MetaTrader balance formula.
var balance = Portfolio?.CurrentValue ?? 0m;
var mmVolume = (balance * (PercentMoneyManagement + DeltaMoneyManagement) - InitialBalance * DeltaMoneyManagement) / 10000m;
if (mmVolume > 0m)
volume = mmVolume;
}
var minVolume = Security?.MinVolume ?? 0m;
if (minVolume > 0m && volume < minVolume)
volume = minVolume;
var maxVolume = Security?.MaxVolume;
if (maxVolume != null && volume > maxVolume.Value)
volume = maxVolume.Value;
return volume;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from collections import deque
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
WilliamsR,
RelativeStrengthIndex,
SmoothedMovingAverage,
SimpleMovingAverage,
)
from indicator_extensions import *
class fuzzy_logic_legacy_strategy(Strategy):
def __init__(self):
super(fuzzy_logic_legacy_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "Data")
self._long_threshold = self.Param("LongThreshold", 0.75) \
.SetDisplay("Long Threshold", "Decision level for long entries", "Trading")
self._short_threshold = self.Param("ShortThreshold", 0.25) \
.SetDisplay("Short Threshold", "Decision level for short entries", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 60.0) \
.SetDisplay("Stop Loss (points)", "Stop loss distance in price steps", "Risk Management")
self._trailing_stop_points = self.Param("TrailingStopPoints", 35.0) \
.SetDisplay("Trailing Stop (points)", "Trailing stop distance in price steps", "Risk Management")
self._fixed_volume = self.Param("FixedVolume", 0.1) \
.SetDisplay("Fixed Volume", "Volume per trade when MM is disabled", "Trading")
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._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 = deque()
self._de_min_queue = deque()
self._de_max_sum = 0.0
self._de_min_sum = 0.0
self._previous_high = None
self._previous_low = None
self._williams_indicator = None
self._rsi_indicator = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def LongThreshold(self):
return self._long_threshold.Value
@property
def ShortThreshold(self):
return self._short_threshold.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def FixedVolume(self):
return self._fixed_volume.Value
def OnStarted2(self, time):
super(fuzzy_logic_legacy_strategy, self).OnStarted2(time)
self._williams_indicator = WilliamsR()
self._williams_indicator.Length = 14
self._rsi_indicator = RelativeStrengthIndex()
self._rsi_indicator.Length = 14
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._williams_indicator, self._rsi_indicator, self.ProcessCandle).Start()
ps = self.Security.PriceStep if self.Security is not None else None
step = float(ps) if ps is not None else 1.0
sl_pts = float(self.StopLossPoints)
sl = Unit(sl_pts * step, UnitTypes.Absolute) if sl_pts > 0 else None
trailing = float(self.TrailingStopPoints) > 0
self.StartProtection(None, sl, isStopTrailing=trailing, useMarketOrders=True)
def ProcessCandle(self, candle, williams_value, rsi_value):
if candle.State != CandleStates.Finished:
return
hl2 = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
jaw_value = process_float(self._jaw, hl2, candle.OpenTime, True)
teeth_value = process_float(self._teeth, hl2, candle.OpenTime, True)
lips_value = process_float(self._lips, hl2, candle.OpenTime, True)
ao_fast_value = process_float(self._ao_fast, hl2, candle.OpenTime, True)
ao_slow_value = process_float(self._ao_slow, hl2, candle.OpenTime, True)
if (not jaw_value.IsFinal or not teeth_value.IsFinal or not lips_value.IsFinal
or not ao_fast_value.IsFinal or not ao_slow_value.IsFinal):
self._update_demarker(candle)
return
jaw_shifted = self._update_shift_buffer(self._jaw_buffer, 8, float(jaw_value), 'jaw')
teeth_shifted = self._update_shift_buffer(self._teeth_buffer, 5, float(teeth_value), 'teeth')
lips_shifted = self._update_shift_buffer(self._lips_buffer, 3, float(lips_value), 'lips')
if jaw_shifted is None or teeth_shifted is None or lips_shifted is None:
self._update_demarker(candle)
return
ao = float(ao_fast_value) - float(ao_slow_value)
ac_average_value = process_float(self._ac_average, ao, candle.OpenTime, True)
if not ac_average_value.IsFinal:
self._update_demarker(candle)
return
ac = ao - float(ac_average_value)
demarker = self._update_demarker(candle)
if demarker is None:
self._update_ac_history(ac)
return
if not williams_value.IsFinal or not rsi_value.IsFinal:
self._update_ac_history(ac)
return
if self._ac_count < len(self._ac_history):
self._update_ac_history(ac)
return
sum_gator = abs(jaw_shifted - teeth_shifted) + abs(teeth_shifted - lips_shifted)
wpr = float(williams_value)
rsi = float(rsi_value)
decision = self._calculate_decision(sum_gator, wpr, demarker, rsi)
if self.IsFormedAndOnlineAndAllowTrading() and self.Position == 0:
volume = float(self.FixedVolume)
if decision < float(self.ShortThreshold):
self.SellMarket(volume)
elif decision > float(self.LongThreshold):
self.BuyMarket(volume)
self._update_ac_history(ac)
def _update_shift_buffer(self, buffer, shift, value, name):
for i in range(shift):
buffer[i] = buffer[i + 1]
buffer[shift] = value
count_attr = '_' + name + '_count'
filled = getattr(self, count_attr)
if filled >= shift:
return buffer[0]
setattr(self, count_attr, filled + 1)
return None
def _update_demarker(self, candle):
if self._previous_high is None or self._previous_low is None:
self._previous_high = float(candle.HighPrice)
self._previous_low = float(candle.LowPrice)
return None
de_max = max(float(candle.HighPrice) - self._previous_high, 0.0)
de_min = max(self._previous_low - float(candle.LowPrice), 0.0)
self._previous_high = float(candle.HighPrice)
self._previous_low = float(candle.LowPrice)
if len(self._de_max_queue) == 14:
self._de_max_sum -= self._de_max_queue.popleft()
self._de_min_sum -= self._de_min_queue.popleft()
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) < 14:
return None
denominator = self._de_max_sum + self._de_min_sum
return 0.0 if denominator == 0 else 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 = [100, 200, 300, 400, 400, 300, 200, 100]
wpr_levels = [-95, -90, -80, -75, -25, -20, -10, -5]
demarker_levels = [0.15, 0.20, 0.25, 0.30, 0.70, 0.75, 0.80, 0.85]
rsi_levels = [25, 30, 35, 40, 60, 65, 70, 75]
weights = [0.133, 0.133, 0.133, 0.268, 0.333]
# Gator membership
if sum_gator < gator_levels[0]:
rang[0][0] = 0.5
rang[0][4] = 0.5
if gator_levels[0] <= 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 gator_levels[1] <= sum_gator < gator_levels[2]:
rang[0][1] = 0.5
rang[0][3] = 0.5
if gator_levels[2] <= 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
# WPR membership
if wpr < wpr_levels[0]:
rang[1][0] = 1.0
if wpr_levels[0] <= 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_levels[1] <= wpr < wpr_levels[2]:
rang[1][1] = 1.0
if wpr_levels[2] <= 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_levels[3] <= wpr < wpr_levels[4]:
rang[1][2] = 1.0
if wpr_levels[4] <= 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_levels[5] <= wpr < wpr_levels[6]:
rang[1][3] = 1.0
if wpr_levels[6] <= 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
# AC membership
ac = self._ac_history
temp_ac_buy = 0.0
if ac[0] < ac[1] and ac[0] < 0 and ac[1] < 0:
temp_ac_buy = 2.0
if ac[0] < ac[1] and ac[1] < ac[2] and ac[0] < 0 and ac[1] < 0 and ac[2] < 0:
temp_ac_buy = 3.0
if (ac[0] < ac[1] and ac[1] < ac[2] and ac[2] < ac[3]
and ac[0] < 0 and ac[1] < 0 and ac[2] < 0 and ac[3] < 0):
temp_ac_buy = 4.0
if (ac[0] < ac[1] and ac[1] < ac[2] and ac[2] < ac[3] and ac[3] < ac[4]
and ac[0] < 0 and ac[1] < 0 and ac[2] < 0 and ac[3] < 0 and ac[4] < 0):
temp_ac_buy = 5.0
temp_ac_sell = 0.0
if ac[0] > ac[1] and ac[0] > 0 and ac[1] > 0:
temp_ac_sell = 2.0
if ac[0] > ac[1] and ac[1] > ac[2] and ac[0] > 0 and ac[1] > 0 and ac[2] > 0:
temp_ac_sell = 3.0
if (ac[0] > ac[1] and ac[1] > ac[2] and ac[2] > ac[3]
and ac[0] > 0 and ac[1] > 0 and ac[2] > 0 and ac[3] > 0):
temp_ac_sell = 4.0
if (ac[0] > ac[1] and ac[1] > ac[2] and ac[2] > ac[3] and ac[3] > ac[4]
and ac[0] > 0 and ac[1] > 0 and ac[2] > 0 and ac[3] > 0 and ac[4] > 0):
temp_ac_sell = 5.0
ac_levels = [5, 4, 3, 2, 2, 3, 4, 5]
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 and rang[2][1] == 0 and rang[2][3] == 0 and rang[2][4] == 0:
rang[2][2] = 1.0
# DeMarker membership
if demarker < demarker_levels[0]:
rang[3][0] = 1.0
if demarker_levels[0] <= 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_levels[1] <= demarker < demarker_levels[2]:
rang[3][1] = 1.0
if demarker_levels[2] <= 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_levels[3] <= demarker < demarker_levels[4]:
rang[3][2] = 1.0
if demarker_levels[4] <= 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_levels[5] <= demarker < demarker_levels[6]:
rang[3][3] = 1.0
if demarker_levels[6] <= 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
# RSI membership
if rsi < rsi_levels[0]:
rang[4][0] = 1.0
if rsi_levels[0] <= 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_levels[1] <= rsi < rsi_levels[2]:
rang[4][1] = 1.0
if rsi_levels[2] <= 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_levels[3] <= rsi < rsi_levels[4]:
rang[4][2] = 1.0
if rsi_levels[4] <= 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_levels[5] <= rsi < rsi_levels[6]:
rang[4][3] = 1.0
if rsi_levels[6] <= 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_legacy_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.clear()
self._de_min_queue.clear()
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_legacy_strategy()