Zahorchak Measure Strategy
Calculates a weighted score using multiple moving averages. Buys when the score turns positive and sells when it turns negative.
Details
- Entry Criteria: Score crosses above zero
- Long/Short: Both
- Exit Criteria: Opposite signal
- Stops: No
- Default Values:
Points= 1EmaLength= 10
- Filters:
- Category: Breadth
- Direction: Both
- Indicators: SMA, EMA
- Stops: No
- Complexity: Basic
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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>
/// Breadth score based on moving averages.
/// Buys when score turns positive and sells when negative.
/// Uses short, medium, and long SMAs to compute a composite score.
/// </summary>
public class ZahorchakMeasureStrategy : Strategy
{
private readonly StrategyParam<decimal> _points;
private readonly StrategyParam<int> _emaSmoothing;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevScore;
private bool _hasPrev;
private decimal _emaMeasure;
public decimal Points { get => _points.Value; set => _points.Value = value; }
public int EmaSmoothing { get => _emaSmoothing.Value; set => _emaSmoothing.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ZahorchakMeasureStrategy()
{
_points = Param(nameof(Points), 1m)
.SetGreaterThanZero()
.SetDisplay("Point Value", "Score per condition", "Scoring");
_emaSmoothing = Param(nameof(EmaSmoothing), 10)
.SetGreaterThanZero()
.SetDisplay("EMA Smoothing", "Smoothing length", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_prevScore = 0;
_hasPrev = false;
_emaMeasure = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var smaShort = new SimpleMovingAverage { Length = 25 };
var smaMedium = new SimpleMovingAverage { Length = 75 };
var smaLong = new SimpleMovingAverage { Length = 200 };
_prevScore = 0;
_hasPrev = false;
_emaMeasure = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(smaShort, smaMedium, smaLong, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, smaShort);
DrawIndicator(area, smaMedium);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal shortVal, decimal mediumVal, decimal longVal)
{
if (candle.State != CandleStates.Finished)
return;
// Compute breadth score
var score = 0m;
score += candle.ClosePrice > shortVal ? Points : -Points;
score += candle.ClosePrice > mediumVal ? Points : -Points;
score += candle.ClosePrice > longVal ? Points : -Points;
score += shortVal > mediumVal ? Points : -Points;
score += mediumVal > longVal ? Points : -Points;
score += shortVal > longVal ? Points : -Points;
var maxScore = Points * 6m;
var normalized = maxScore != 0 ? 10m * score / maxScore : 0;
// EMA smoothing
if (!_hasPrev)
{
_emaMeasure = normalized;
_prevScore = normalized;
_hasPrev = true;
return;
}
var k = 2m / (EmaSmoothing + 1);
_emaMeasure = normalized * k + _emaMeasure * (1 - k);
var measure = _emaMeasure;
// Trade on zero-line cross
if (_prevScore <= 0 && measure > 0 && Position <= 0)
{
BuyMarket();
}
else if (_prevScore >= 0 && measure < 0 && Position >= 0)
{
SellMarket();
}
_prevScore = measure;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class zahorchak_measure_strategy(Strategy):
def __init__(self):
super(zahorchak_measure_strategy, self).__init__()
self._points = self.Param("Points", 1) \
.SetDisplay("Point Value", "Score per condition", "Scoring")
self._ema_smoothing = self.Param("EmaSmoothing", 10) \
.SetDisplay("EMA Smoothing", "Smoothing length", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_score = 0.0
self._has_prev = False
self._ema_measure = 0.0
@property
def points(self):
return self._points.Value
@property
def ema_smoothing(self):
return self._ema_smoothing.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(zahorchak_measure_strategy, self).OnReseted()
self._prev_score = 0.0
self._has_prev = False
self._ema_measure = 0.0
def OnStarted2(self, time):
super(zahorchak_measure_strategy, self).OnStarted2(time)
sma_short = SimpleMovingAverage()
sma_short.Length = 25
sma_medium = SimpleMovingAverage()
sma_medium.Length = 75
sma_long = SimpleMovingAverage()
sma_long.Length = 200
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma_short, sma_medium, sma_long, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma_short)
self.DrawIndicator(area, sma_medium)
self.DrawOwnTrades(area)
def on_process(self, candle, short_val, medium_val, long_val):
if candle.State != CandleStates.Finished:
return
# Compute breadth score
close = float(candle.ClosePrice)
sv = float(short_val)
mv = float(medium_val)
lv = float(long_val)
pts = float(self.points)
score = 0.0
score += pts if close > sv else -pts
score += pts if close > mv else -pts
score += pts if close > lv else -pts
score += pts if sv > mv else -pts
score += pts if mv > lv else -pts
score += pts if sv > lv else -pts
max_score = self.points * 6
normalized = (10 * score / max_score if max_score != 0 else 0)
# EMA smoothing
if not self._has_prev:
self._ema_measure = normalized
self._prev_score = normalized
self._has_prev = True
return
k = 2 / (self.ema_smoothing + 1)
self._ema_measure = normalized * k + self._ema_measure * (1 - k)
measure = self._ema_measure
# Trade on zero-line cross
if self._prev_score <= 0 and measure > 0 and self.Position <= 0:
self.BuyMarket()
elif self._prev_score >= 0 and measure < 0 and self.Position >= 0:
self.SellMarket()
self._prev_score = measure
def CreateClone(self):
return zahorchak_measure_strategy()