Стратегия Fractal Weight Oscillator
Обзор
Стратегия является конверсией советника "Exp_Fractal_WeightOscillator".
Она объединяет четыре осциллятора (RSI, Money Flow Index, Williams %R и
DeMarker) в единый сглаженный сигнал и сравнивает его с уровнями
HighLevel / LowLevel. В зависимости от выбранного режима TrendMode
стратегия работает по тренду или против него. Все расчёты выполняются на
указанном таймфрейме свечей через высокоуровневый API StockSharp.
Набор индикаторов
- RSI – рассчитывается по указанному источнику цены.
- MFI – использует выбранную цену и объём свечи.
- Williams %R – строится по максимумам/минимумам и закрытию.
- DeMarker – пересобран вручную на основе максимумов/минимумов с простым сглаживанием.
- Сглаживающая скользящая средняя – необязательный постфильтр (SMA, EMA, SMMA или LWMA).
Композитный осциллятор представляет собой взвешенное среднее четырёх
компонентов. Уровни HighLevel и LowLevel задают зоны перекупленности и
перепроданности, а параметр SignalBar позволяет смотреть сигнал на более
старых закрытых барах.
Торговая логика
TrendMode = Direct (по тренду)
- Покупка / закрытие шорта – когда осциллятор опускается от значений выше
LowLevelдо уровняLowLevelили ниже (при включённыхBuyOpenEnabledиSellCloseEnabled). - Продажа / закрытие лонга – когда осциллятор поднимается от значений ниже
HighLevelдо уровняHighLevelили выше (требуютсяSellOpenEnabledиBuyCloseEnabled).
TrendMode = Counter (против тренда)
- Покупка / закрытие шорта – при пробое вверх уровня
HighLevel. - Продажа / закрытие лонга – при пробое вниз уровня
LowLevel.
Сигналы анализируются на баре, заданном SignalBar. При развороте позиции
покупается/продаётся объём Volume + |Position|, чтобы сначала закрыть текущую
позицию.
Управление рисками
После открытия позиции рассчитываются стоп-лосс и тейк-профит по параметрам
StopLossPoints и TakeProfitPoints. Значения умножаются на MinPriceStep.
На каждой завершённой свече проверяются минимум/максимум; при достижении
стопа или цели позиция закрывается и внутренние уровни риска сбрасываются.
Параметры
| Параметр | Описание |
|---|---|
TrendMode |
Режим торговли: по тренду или против тренда. |
SignalBar |
Сколько закрытых баров назад анализируется сигнал. |
Period |
Базовый период для RSI, MFI, Williams %R и DeMarker. |
SmoothingLength |
Длина окна сглаживающей скользящей средней. |
SmoothingMethod |
Тип сглаживания (None, Sma, Ema, Smma, Lwma). |
RsiPrice, MfiPrice |
Источники цены для компонентов. |
MfiVolume |
Тип объёма для MFI (tick/real используют объём свечи). |
RsiWeight, MfiWeight, WprWeight, DeMarkerWeight |
Вес каждого индикатора в суммарном осцилляторе. |
HighLevel, LowLevel |
Верхний и нижний порог для поиска сигналов. |
BuyOpenEnabled, SellOpenEnabled |
Разрешение на открытие лонгов/шортов. |
BuyCloseEnabled, SellCloseEnabled |
Разрешение закрывать позиции при обратном сигнале. |
StopLossPoints, TakeProfitPoints |
Расстояние до стоп-лосса и тейк-профита (в шагах цены, 0 отключает уровень). |
CandleType |
Тип/таймфрейм обрабатываемых свечей. |
Volume (свойство стратегии) |
Объём сделки; при развороте добавляется абсолютное значение текущей позиции. |
Особенности использования
- Значение
SignalBar = 1соответствует оригинальному советнику и использует последний полностью сформированный бар. Увеличение значения добавляет задержку для подтверждения сигналов. SmoothingMethodпозволяет отключить сглаживание (None) или подобрать тип скользящей средней, аналогичный MQL-версии.- Рассчёт MFI всегда использует совокупный объём свечи из потока данных.
Отдельный счётчик тиков в стандартных свечах StockSharp недоступен, поэтому
варианты
TickиRealработают с одним и тем же значением. - Все комментарии в исходнике на английском языке согласно требованиям.
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;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on the Fractal Weight Oscillator indicator.
/// Combines RSI, MFI, Williams %R and DeMarker into a smoothed oscillator
/// and trades level crossings in direct or counter-trend mode.
/// </summary>
public class FractalWeightOscillatorStrategy : Strategy
{
/// <summary>
/// Volume source used for the MFI component.
/// </summary>
public enum MfiVolumeTypes
{
/// <summary>
/// Tick volume.
/// </summary>
Tick,
/// <summary>
/// Real traded volume.
/// </summary>
Real
}
public enum TrendModes
{
/// <summary>
/// Follow the trend.
/// </summary>
Direct,
/// <summary>
/// Trade against the trend.
/// </summary>
Counter
}
public enum SmoothingMethods
{
/// <summary>
/// No smoothing.
/// </summary>
None,
/// <summary>
/// Simple Moving Average.
/// </summary>
Sma,
/// <summary>
/// Exponential Moving Average.
/// </summary>
Ema,
/// <summary>
/// Smoothed Moving Average.
/// </summary>
Smma,
/// <summary>
/// Linear Weighted Moving Average.
/// </summary>
Lwma
}
public enum AppliedPrice
{
None,
Open,
High,
Low,
Close,
Median,
Typical,
Weighted,
Simple,
Quarter,
TrendFollow0,
TrendFollow1,
DeMark
}
private readonly StrategyParam<TrendModes> _trendMode;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<int> _period;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<SmoothingMethods> _smoothingMethod;
private readonly StrategyParam<AppliedPrice> _rsiPrice;
private readonly StrategyParam<AppliedPrice> _mfiPrice;
private readonly StrategyParam<MfiVolumeTypes> _mfiVolumeType;
private readonly StrategyParam<decimal> _rsiWeight;
private readonly StrategyParam<decimal> _mfiWeight;
private readonly StrategyParam<decimal> _wprWeight;
private readonly StrategyParam<decimal> _deMarkerWeight;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<bool> _buyOpenEnabled;
private readonly StrategyParam<bool> _sellOpenEnabled;
private readonly StrategyParam<bool> _buyCloseEnabled;
private readonly StrategyParam<bool> _sellCloseEnabled;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi = null!;
private RelativeStrengthIndex _williams = null!;
private DecimalLengthIndicator _smoother;
private SimpleMovingAverage _deMaxSma = null!;
private SimpleMovingAverage _deMinSma = null!;
private readonly List<decimal> _oscillatorHistory = new();
private decimal _previousHigh;
private decimal _previousLow;
private bool _hasPreviousCandle;
private readonly Queue<decimal> _positiveFlow = new();
private readonly Queue<decimal> _negativeFlow = new();
private decimal _previousTypical;
private bool _hasPreviousTypical;
private decimal _positiveSum;
private decimal _negativeSum;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private DateTime _currentTime;
/// <summary>
/// Trading direction mode.
/// </summary>
public TrendModes TrendMode
{
get => _trendMode.Value;
set => _trendMode.Value = value;
}
/// <summary>
/// Number of closed bars used for signal evaluation.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Base period for all component oscillators.
/// </summary>
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// Smoothing window applied to the combined oscillator.
/// </summary>
public int SmoothingLength
{
get => _smoothingLength.Value;
set => _smoothingLength.Value = value;
}
/// <summary>
/// Type of moving average used for smoothing.
/// </summary>
public SmoothingMethods SmoothingMethod
{
get => _smoothingMethod.Value;
set => _smoothingMethod.Value = value;
}
/// <summary>
/// Applied price source for RSI calculation.
/// </summary>
public AppliedPrice RsiPrice
{
get => _rsiPrice.Value;
set => _rsiPrice.Value = value;
}
/// <summary>
/// Applied price source for MFI calculation.
/// </summary>
public AppliedPrice MfiPrice
{
get => _mfiPrice.Value;
set => _mfiPrice.Value = value;
}
/// <summary>
/// Volume type used by the MFI component.
/// </summary>
public MfiVolumeTypes MfiVolume
{
get => _mfiVolumeType.Value;
set => _mfiVolumeType.Value = value;
}
/// <summary>
/// Weight of the RSI contribution.
/// </summary>
public decimal RsiWeight
{
get => _rsiWeight.Value;
set => _rsiWeight.Value = value;
}
/// <summary>
/// Weight of the MFI contribution.
/// </summary>
public decimal MfiWeight
{
get => _mfiWeight.Value;
set => _mfiWeight.Value = value;
}
/// <summary>
/// Weight of the Williams %R contribution.
/// </summary>
public decimal WprWeight
{
get => _wprWeight.Value;
set => _wprWeight.Value = value;
}
/// <summary>
/// Weight of the DeMarker contribution.
/// </summary>
public decimal DeMarkerWeight
{
get => _deMarkerWeight.Value;
set => _deMarkerWeight.Value = value;
}
/// <summary>
/// Upper threshold of the oscillator.
/// </summary>
public decimal HighLevel
{
get => _highLevel.Value;
set => _highLevel.Value = value;
}
/// <summary>
/// Lower threshold of the oscillator.
/// </summary>
public decimal LowLevel
{
get => _lowLevel.Value;
set => _lowLevel.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyOpenEnabled
{
get => _buyOpenEnabled.Value;
set => _buyOpenEnabled.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellOpenEnabled
{
get => _sellOpenEnabled.Value;
set => _sellOpenEnabled.Value = value;
}
/// <summary>
/// Allow closing long positions on opposite signals.
/// </summary>
public bool BuyCloseEnabled
{
get => _buyCloseEnabled.Value;
set => _buyCloseEnabled.Value = value;
}
/// <summary>
/// Allow closing short positions on opposite signals.
/// </summary>
public bool SellCloseEnabled
{
get => _sellCloseEnabled.Value;
set => _sellCloseEnabled.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in instrument points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in instrument points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public FractalWeightOscillatorStrategy()
{
_trendMode = Param(nameof(TrendMode), TrendModes.Direct)
.SetDisplay("Trend Mode", "Follow trend or counter-trend", "Trading");
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Offset for signal evaluation", "Trading");
_period = Param(nameof(Period), 30)
.SetGreaterThanZero()
.SetDisplay("Period", "Length for component oscillators", "Indicators");
_smoothingLength = Param(nameof(SmoothingLength), 30)
.SetGreaterThanZero()
.SetDisplay("Smoothing Length", "Window for smoothing", "Indicators");
_smoothingMethod = Param(nameof(SmoothingMethod), SmoothingMethods.Smma)
.SetDisplay("Smoothing Method", "Moving average type for smoothing", "Indicators");
_rsiPrice = Param(nameof(RsiPrice), AppliedPrice.Close)
.SetDisplay("RSI Price", "Applied price for RSI", "Indicators");
_mfiPrice = Param(nameof(MfiPrice), AppliedPrice.Typical)
.SetDisplay("MFI Price", "Applied price for MFI", "Indicators");
_mfiVolumeType = Param(nameof(MfiVolume), MfiVolumeTypes.Tick)
.SetDisplay("MFI Volume", "Volume source for MFI", "Indicators");
_rsiWeight = Param(nameof(RsiWeight), 1m)
.SetGreaterThanZero()
.SetDisplay("RSI Weight", "Weight of RSI component", "Weights");
_mfiWeight = Param(nameof(MfiWeight), 1m)
.SetGreaterThanZero()
.SetDisplay("MFI Weight", "Weight of MFI component", "Weights");
_wprWeight = Param(nameof(WprWeight), 1m)
.SetGreaterThanZero()
.SetDisplay("WPR Weight", "Weight of Williams %R component", "Weights");
_deMarkerWeight = Param(nameof(DeMarkerWeight), 1m)
.SetGreaterThanZero()
.SetDisplay("DeMarker Weight", "Weight of DeMarker component", "Weights");
_highLevel = Param(nameof(HighLevel), 60m)
.SetDisplay("High Level", "Upper oscillator threshold", "Trading");
_lowLevel = Param(nameof(LowLevel), 40m)
.SetDisplay("Low Level", "Lower oscillator threshold", "Trading");
_buyOpenEnabled = Param(nameof(BuyOpenEnabled), true)
.SetDisplay("Enable Long Entries", "Allow buying", "Trading");
_sellOpenEnabled = Param(nameof(SellOpenEnabled), true)
.SetDisplay("Enable Short Entries", "Allow selling", "Trading");
_buyCloseEnabled = Param(nameof(BuyCloseEnabled), true)
.SetDisplay("Close Long", "Allow long exit on signals", "Trading");
_sellCloseEnabled = Param(nameof(SellCloseEnabled), true)
.SetDisplay("Close Short", "Allow short exit on signals", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss (pts)", "Stop-loss distance in points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit (pts)", "Take-profit distance in points", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for processing", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_oscillatorHistory.Clear();
_previousHigh = 0m;
_previousLow = 0m;
_hasPreviousCandle = false;
_positiveFlow.Clear();
_negativeFlow.Clear();
_previousTypical = 0m;
_hasPreviousTypical = false;
_positiveSum = 0m;
_negativeSum = 0m;
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_currentTime = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = Period };
_williams = new RelativeStrengthIndex { Length = Period };
_deMaxSma = new SMA { Length = Period };
_deMinSma = new SMA { Length = Period };
_smoother = CreateSmoother(SmoothingMethod, SmoothingLength);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_currentTime = candle.OpenTime;
// indicators are processed manually below
var rsiInput = GetPrice(candle, RsiPrice);
var rsiValue = _rsi.Process(new DecimalIndicatorValue(_rsi, rsiInput, candle.OpenTime) { IsFinal = true });
var mfiInput = GetPrice(candle, MfiPrice);
var wprValue = _williams.Process(new DecimalIndicatorValue(_williams, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
if (!rsiValue.IsFinal || !wprValue.IsFinal)
return;
var mfi = CalculateMfi(candle, mfiInput);
if (mfi is null)
return;
var deMarker = CalculateDeMarker(candle);
if (deMarker is null)
return;
var rsi = rsiValue.GetValue<decimal>();
var mfiValue = mfi.Value;
var wpr = wprValue.GetValue<decimal>();
var totalWeight = RsiWeight + MfiWeight + WprWeight + DeMarkerWeight;
if (totalWeight <= 0m)
return;
var weighted = (RsiWeight * rsi
+ MfiWeight * mfiValue
+ WprWeight * wpr
+ DeMarkerWeight * (deMarker.Value * 100m)) / totalWeight;
var smoothed = ApplySmoothing(weighted);
if (smoothed is null)
return;
_oscillatorHistory.Add(smoothed.Value);
TrimHistory();
if (_oscillatorHistory.Count < SignalBar + 2)
return;
var currentIndex = _oscillatorHistory.Count - 1 - SignalBar;
if (currentIndex <= 0)
return;
var current = _oscillatorHistory[currentIndex];
var previous = _oscillatorHistory[currentIndex - 1];
CheckRisk(candle);
var crossBelowLow = previous > LowLevel && current <= LowLevel;
var crossAboveHigh = previous < HighLevel && current >= HighLevel;
var openBuy = false;
var closeBuy = false;
var openSell = false;
var closeSell = false;
if (TrendMode == TrendModes.Direct)
{
if (crossBelowLow)
{
openBuy = BuyOpenEnabled;
closeSell = SellCloseEnabled;
}
if (crossAboveHigh)
{
openSell = SellOpenEnabled;
closeBuy = BuyCloseEnabled;
}
}
else
{
if (crossBelowLow)
{
openSell = SellOpenEnabled;
closeBuy = BuyCloseEnabled;
}
if (crossAboveHigh)
{
openBuy = BuyOpenEnabled;
closeSell = SellCloseEnabled;
}
}
if (closeBuy && Position > 0)
{
SellMarket();
ResetRisk();
}
if (closeSell && Position < 0)
{
BuyMarket();
ResetRisk();
}
if (openBuy && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket();
SetRiskLevels(candle, Sides.Buy);
}
else if (openSell && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket();
SetRiskLevels(candle, Sides.Sell);
}
}
private decimal? ApplySmoothing(decimal value)
{
if (_smoother is null)
return value;
var smoothed = _smoother.Process(new DecimalIndicatorValue(_smoother, value, _currentTime) { IsFinal = true });
return smoothed.IsFinal ? smoothed.GetValue<decimal>() : null;
}
private decimal? CalculateDeMarker(ICandleMessage candle)
{
if (!_hasPreviousCandle)
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_hasPreviousCandle = true;
return null;
}
var deMax = Math.Max(candle.HighPrice - _previousHigh, 0m);
var deMin = Math.Max(_previousLow - candle.LowPrice, 0m);
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
var deMaxValue = _deMaxSma.Process(new DecimalIndicatorValue(_deMaxSma, deMax, _currentTime) { IsFinal = true });
var deMinValue = _deMinSma.Process(new DecimalIndicatorValue(_deMinSma, deMin, _currentTime) { IsFinal = true });
if (!deMaxValue.IsFinal || !deMinValue.IsFinal)
return null;
var maxAvg = deMaxValue.GetValue<decimal>();
var minAvg = deMinValue.GetValue<decimal>();
var denom = maxAvg + minAvg;
if (denom == 0m)
return 0.5m;
return maxAvg / denom;
}
private decimal? CalculateMfi(ICandleMessage candle, decimal price)
{
var volume = GetVolume(candle);
if (!_hasPreviousTypical)
{
_previousTypical = price;
_hasPreviousTypical = true;
_positiveFlow.Clear();
_negativeFlow.Clear();
_positiveSum = 0m;
_negativeSum = 0m;
return null;
}
var flow = price * volume;
var positive = price > _previousTypical ? flow : 0m;
var negative = price < _previousTypical ? flow : 0m;
_previousTypical = price;
_positiveSum += positive;
_negativeSum += negative;
_positiveFlow.Enqueue(positive);
_negativeFlow.Enqueue(negative);
if (_positiveFlow.Count > Period)
{
_positiveSum -= _positiveFlow.Dequeue();
_negativeSum -= _negativeFlow.Dequeue();
}
if (_positiveFlow.Count < Period)
return null;
if (_negativeSum == 0m)
return 100m;
var ratio = _positiveSum / _negativeSum;
return 100m - 100m / (1m + ratio);
}
private void TrimHistory()
{
var maxSize = SignalBar + Math.Max(Period, SmoothingLength) + 5;
if (_oscillatorHistory.Count <= maxSize)
return;
var remove = _oscillatorHistory.Count - maxSize;
_oscillatorHistory.RemoveRange(0, remove);
}
private void CheckRisk(ICandleMessage candle)
{
if (Position > 0)
{
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
ResetRisk();
return;
}
if (_takePrice is decimal take && candle.HighPrice >= take)
{
SellMarket();
ResetRisk();
}
}
else if (Position < 0)
{
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
ResetRisk();
return;
}
if (_takePrice is decimal take && candle.LowPrice <= take)
{
BuyMarket();
ResetRisk();
}
}
}
private void SetRiskLevels(ICandleMessage candle, Sides side)
{
_entryPrice = candle.ClosePrice;
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
{
_stopPrice = null;
_takePrice = null;
return;
}
_stopPrice = StopLossPoints > 0
? side == Sides.Buy
? _entryPrice - step * StopLossPoints
: _entryPrice + step * StopLossPoints
: null;
_takePrice = TakeProfitPoints > 0
? side == Sides.Buy
? _entryPrice + step * TakeProfitPoints
: _entryPrice - step * TakeProfitPoints
: null;
}
private void ResetRisk()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
private decimal GetPrice(ICandleMessage candle, AppliedPrice price)
{
return price switch
{
AppliedPrice.Open => candle.OpenPrice,
AppliedPrice.High => candle.HighPrice,
AppliedPrice.Low => candle.LowPrice,
AppliedPrice.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPrice.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPrice.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
AppliedPrice.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
AppliedPrice.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPrice.TrendFollow0 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice
: candle.ClosePrice < candle.OpenPrice ? candle.LowPrice
: candle.ClosePrice,
AppliedPrice.TrendFollow1 => candle.ClosePrice > candle.OpenPrice
? (candle.HighPrice + candle.ClosePrice) / 2m
: candle.ClosePrice < candle.OpenPrice
? (candle.LowPrice + candle.ClosePrice) / 2m
: candle.ClosePrice,
AppliedPrice.DeMark => CalculateDeMarkPrice(candle),
_ => candle.ClosePrice,
};
}
private static decimal CalculateDeMarkPrice(ICandleMessage candle)
{
var res = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
if (candle.ClosePrice < candle.OpenPrice)
res = (res + candle.LowPrice) / 2m;
else if (candle.ClosePrice > candle.OpenPrice)
res = (res + candle.HighPrice) / 2m;
else
res = (res + candle.ClosePrice) / 2m;
return ((res - candle.LowPrice) + (res - candle.HighPrice)) / 2m;
}
private decimal GetVolume(ICandleMessage candle)
{
return candle.TotalVolume;
}
private static DecimalLengthIndicator CreateSmoother(SmoothingMethods method, int length)
{
return method switch
{
SmoothingMethods.None => null,
SmoothingMethods.Sma => new SMA { Length = length },
SmoothingMethods.Ema => new EMA { Length = length },
SmoothingMethods.Smma => new SmoothedMovingAverage { Length = length },
SmoothingMethods.Lwma => new WeightedMovingAverage { Length = length },
_ => new SmoothedMovingAverage { Length = length }
};
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from System import Decimal
from StockSharp.Algo.Indicators import RelativeStrengthIndex, SimpleMovingAverage, ExponentialMovingAverage, SmoothedMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
# Trend mode constants
TREND_DIRECT = 0
TREND_COUNTER = 1
class fractal_weight_oscillator_strategy(Strategy):
def __init__(self):
super(fractal_weight_oscillator_strategy, self).__init__()
self._trend_mode = self.Param("TrendMode", TREND_DIRECT)
self._signal_bar = self.Param("SignalBar", 1)
self._period = self.Param("Period", 30)
self._smoothing_length = self.Param("SmoothingLength", 30)
self._smoothing_method = self.Param("SmoothingMethod", 3) # 0=None,1=SMA,2=EMA,3=SMMA,4=LWMA
self._rsi_weight = self.Param("RsiWeight", 1.0)
self._mfi_weight = self.Param("MfiWeight", 1.0)
self._wpr_weight = self.Param("WprWeight", 1.0)
self._de_marker_weight = self.Param("DeMarkerWeight", 1.0)
self._high_level = self.Param("HighLevel", 60.0)
self._low_level = self.Param("LowLevel", 40.0)
self._buy_open_enabled = self.Param("BuyOpenEnabled", True)
self._sell_open_enabled = self.Param("SellOpenEnabled", True)
self._buy_close_enabled = self.Param("BuyCloseEnabled", True)
self._sell_close_enabled = self.Param("SellCloseEnabled", True)
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._oscillator_history = []
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous_candle = False
self._positive_flow = []
self._negative_flow = []
self._previous_typical = 0.0
self._has_previous_typical = False
self._positive_sum = 0.0
self._negative_sum = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
@property
def TrendMode(self):
return self._trend_mode.Value
@TrendMode.setter
def TrendMode(self, value):
self._trend_mode.Value = value
@property
def SignalBar(self):
return self._signal_bar.Value
@SignalBar.setter
def SignalBar(self, value):
self._signal_bar.Value = value
@property
def Period(self):
return self._period.Value
@Period.setter
def Period(self, value):
self._period.Value = value
@property
def RsiWeight(self):
return self._rsi_weight.Value
@RsiWeight.setter
def RsiWeight(self, value):
self._rsi_weight.Value = value
@property
def MfiWeight(self):
return self._mfi_weight.Value
@MfiWeight.setter
def MfiWeight(self, value):
self._mfi_weight.Value = value
@property
def WprWeight(self):
return self._wpr_weight.Value
@WprWeight.setter
def WprWeight(self, value):
self._wpr_weight.Value = value
@property
def DeMarkerWeight(self):
return self._de_marker_weight.Value
@DeMarkerWeight.setter
def DeMarkerWeight(self, value):
self._de_marker_weight.Value = value
@property
def HighLevel(self):
return self._high_level.Value
@HighLevel.setter
def HighLevel(self, value):
self._high_level.Value = value
@property
def LowLevel(self):
return self._low_level.Value
@LowLevel.setter
def LowLevel(self, value):
self._low_level.Value = value
@property
def BuyOpenEnabled(self):
return self._buy_open_enabled.Value
@BuyOpenEnabled.setter
def BuyOpenEnabled(self, value):
self._buy_open_enabled.Value = value
@property
def SellOpenEnabled(self):
return self._sell_open_enabled.Value
@SellOpenEnabled.setter
def SellOpenEnabled(self, value):
self._sell_open_enabled.Value = value
@property
def BuyCloseEnabled(self):
return self._buy_close_enabled.Value
@BuyCloseEnabled.setter
def BuyCloseEnabled(self, value):
self._buy_close_enabled.Value = value
@property
def SellCloseEnabled(self):
return self._sell_close_enabled.Value
@SellCloseEnabled.setter
def SellCloseEnabled(self, value):
self._sell_close_enabled.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 CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(fractal_weight_oscillator_strategy, self).OnStarted2(time)
self._oscillator_history = []
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous_candle = False
self._positive_flow = []
self._negative_flow = []
self._previous_typical = 0.0
self._has_previous_typical = False
self._positive_sum = 0.0
self._negative_sum = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.Period
self._williams_rsi = RelativeStrengthIndex()
self._williams_rsi.Length = self.Period
self._de_max_sma = SimpleMovingAverage()
self._de_max_sma.Length = self.Period
self._de_min_sma = SimpleMovingAverage()
self._de_min_sma.Length = self.Period
self._smoother = self._create_smoother(int(self._smoothing_method.Value), int(self._smoothing_length.Value))
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_price = float(candle.OpenPrice)
typical = (high + low + close) / 3.0
volume = float(candle.TotalVolume)
rsi_result = process_float(self._rsi, candle.ClosePrice, candle.OpenTime, True)
wpr_result = process_float(self._williams_rsi, candle.ClosePrice, candle.OpenTime, True)
if not rsi_result.IsFinal or not wpr_result.IsFinal:
return
rsi_val = float(rsi_result)
wpr_val = float(wpr_result)
mfi = self._calculate_mfi(typical, volume)
demarker = self._calculate_demarker(candle)
if mfi is None or demarker is None:
return
total_weight = float(self.RsiWeight) + float(self.MfiWeight) + float(self.WprWeight) + float(self.DeMarkerWeight)
if total_weight <= 0.0:
return
weighted = (float(self.RsiWeight) * rsi_val +
float(self.MfiWeight) * mfi +
float(self.WprWeight) * wpr_val +
float(self.DeMarkerWeight) * (demarker * 100.0)) / total_weight
smoothed = self._apply_smoothing(weighted, candle.OpenTime)
if smoothed is None:
return
self._oscillator_history.append(smoothed)
self._trim_history()
signal_bar = int(self.SignalBar)
if len(self._oscillator_history) < signal_bar + 2:
return
current_index = len(self._oscillator_history) - 1 - signal_bar
if current_index <= 0:
return
current = self._oscillator_history[current_index]
previous = self._oscillator_history[current_index - 1]
self._check_risk(candle)
high_lvl = float(self.HighLevel)
low_lvl = float(self.LowLevel)
cross_below_low = previous > low_lvl and current <= low_lvl
cross_above_high = previous < high_lvl and current >= high_lvl
open_buy = False
close_buy = False
open_sell = False
close_sell = False
if self.TrendMode == TREND_DIRECT:
if cross_below_low:
open_buy = self.BuyOpenEnabled
close_sell = self.SellCloseEnabled
if cross_above_high:
open_sell = self.SellOpenEnabled
close_buy = self.BuyCloseEnabled
else:
if cross_below_low:
open_sell = self.SellOpenEnabled
close_buy = self.BuyCloseEnabled
if cross_above_high:
open_buy = self.BuyOpenEnabled
close_sell = self.SellCloseEnabled
if close_buy and self.Position > 0:
self.SellMarket()
self._reset_risk()
if close_sell and self.Position < 0:
self.BuyMarket()
self._reset_risk()
if open_buy and self.Position <= 0:
self.BuyMarket()
self._set_risk_levels(close, True)
elif open_sell and self.Position >= 0:
self.SellMarket()
self._set_risk_levels(close, False)
def _calculate_mfi(self, typical, volume):
if not self._has_previous_typical:
self._previous_typical = typical
self._has_previous_typical = True
self._positive_flow = []
self._negative_flow = []
self._positive_sum = 0.0
self._negative_sum = 0.0
return None
flow = typical * volume
positive = flow if typical > self._previous_typical else 0.0
negative = flow if typical < self._previous_typical else 0.0
self._previous_typical = typical
self._positive_sum += positive
self._negative_sum += negative
self._positive_flow.append(positive)
self._negative_flow.append(negative)
period = int(self.Period)
if len(self._positive_flow) > period:
self._positive_sum -= self._positive_flow.pop(0)
self._negative_sum -= self._negative_flow.pop(0)
if len(self._positive_flow) < period:
return None
if self._negative_sum == 0.0:
return 100.0
ratio = self._positive_sum / self._negative_sum
return 100.0 - 100.0 / (1.0 + ratio)
def _calculate_demarker(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if not self._has_previous_candle:
self._previous_high = high
self._previous_low = low
self._has_previous_candle = True
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
de_max_result = process_float(self._de_max_sma, Decimal(de_max), candle.OpenTime, True)
de_min_result = process_float(self._de_min_sma, Decimal(de_min), candle.OpenTime, True)
if not de_max_result.IsFinal or not de_min_result.IsFinal:
return None
max_avg = float(de_max_result)
min_avg = float(de_min_result)
denom = max_avg + min_avg
if denom == 0.0:
return 0.5
return max_avg / denom
def _trim_history(self):
period = int(self.Period)
smoothing_len = int(self._smoothing_length.Value)
signal_bar = int(self.SignalBar)
max_size = signal_bar + max(period, smoothing_len) + 5
if len(self._oscillator_history) > max_size:
remove = len(self._oscillator_history) - max_size
self._oscillator_history = self._oscillator_history[remove:]
def _check_risk(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._stop_price is not None and low <= self._stop_price:
self.SellMarket()
self._reset_risk()
return
if self._take_price is not None and high >= self._take_price:
self.SellMarket()
self._reset_risk()
elif self.Position < 0:
if self._stop_price is not None and high >= self._stop_price:
self.BuyMarket()
self._reset_risk()
return
if self._take_price is not None and low <= self._take_price:
self.BuyMarket()
self._reset_risk()
def _set_risk_levels(self, close, is_long):
self._entry_price = close
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0
if step <= 0.0:
self._stop_price = None
self._take_price = None
return
sl_pts = int(self.StopLossPoints)
tp_pts = int(self.TakeProfitPoints)
if sl_pts > 0:
if is_long:
self._stop_price = close - step * sl_pts
else:
self._stop_price = close + step * sl_pts
else:
self._stop_price = None
if tp_pts > 0:
if is_long:
self._take_price = close + step * tp_pts
else:
self._take_price = close - step * tp_pts
else:
self._take_price = None
def _create_smoother(self, method, length):
if method == 0:
return None
elif method == 1:
ind = SimpleMovingAverage()
ind.Length = length
return ind
elif method == 2:
ind = ExponentialMovingAverage()
ind.Length = length
return ind
elif method == 3:
ind = SmoothedMovingAverage()
ind.Length = length
return ind
elif method == 4:
ind = WeightedMovingAverage()
ind.Length = length
return ind
else:
ind = SmoothedMovingAverage()
ind.Length = length
return ind
def _apply_smoothing(self, value, time):
if self._smoother is None:
return value
result = process_float(self._smoother, Decimal(value), time, True)
if result.IsFinal:
return float(result)
return None
def _reset_risk(self):
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(fractal_weight_oscillator_strategy, self).OnReseted()
self._oscillator_history = []
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous_candle = False
self._positive_flow = []
self._negative_flow = []
self._previous_typical = 0.0
self._has_previous_typical = False
self._positive_sum = 0.0
self._negative_sum = 0.0
self._reset_risk()
def CreateClone(self):
return fractal_weight_oscillator_strategy()