Ozymandias Trend
Стратегия использует индикатор Ozymandias. Индикатор сочетает ATR и скользящие средние максимумов и минимумов, формируя динамический канал. Переключение с медвежьего направления на бычье открывает покупку и закрывает короткие позиции. Переключение на медвежье — продажу и закрытие длинных. Параметры тейк-профита и стоп-лосса помогают управлять риском.
Детали
- Критерий входа: изменение направления индикатора Ozymandias.
- Длинные/короткие: обе стороны.
- Критерий выхода: противоположный сигнал или заданные стопы.
- Стопы: тейк-профит и стоп-лосс.
- Значения по умолчанию:
Length= 2CandleType= TimeSpan.FromHours(4)TakeProfitPoints= 2000StopLossPoints= 1000BuyEntry= trueSellEntry= trueBuyExit= trueSellExit= true
- Фильтры:
- Категория: Тренд
- Направление: Обе
- Индикаторы: Ozymandias (ATR + MA)
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: 4 часа
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Strategy based on the Ozymandias indicator.
/// Opens a position when the indicator changes its direction and closes the opposite one.
/// </summary>
public class OzymandiasStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<DataType> _candleType;
private int _trend;
private int _nextTrend;
private decimal _maxl;
private decimal _minh;
private decimal _baseLine;
private decimal _prevHigh;
private decimal _prevLow;
private int? _prevDirection;
private static readonly object _sync = new();
// Rolling window for high prices (for highest calculation)
private readonly Queue<decimal> _highWindow = new();
// Rolling window for low prices (for lowest calculation)
private readonly Queue<decimal> _lowWindow = new();
// Rolling window for hh values (for SMA of highest)
private readonly Queue<decimal> _hhQueue = new();
private decimal _hhSum;
// Rolling window for ll values (for SMA of lowest)
private readonly Queue<decimal> _llQueue = new();
private decimal _llSum;
// ATR manual calculation
private readonly Queue<decimal> _trQueue = new();
private decimal _trSum;
private decimal _prevClose;
private bool _hasPrevClose;
private int _candleCount;
/// <summary>
/// Lookback length for calculations.
/// </summary>
public int Length { get => _length.Value; set => _length.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initialize the strategy parameters.
/// </summary>
public OzymandiasStrategy()
{
_length = Param(nameof(Length), 8)
.SetDisplay("Length", "Lookback period", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
lock (_sync)
{
_trend = 0;
_nextTrend = 0;
_maxl = 0m;
_minh = decimal.MaxValue;
_baseLine = 0m;
_prevHigh = 0m;
_prevLow = 0m;
_prevDirection = null;
_candleCount = 0;
_highWindow.Clear();
_lowWindow.Clear();
_hhQueue.Clear();
_hhSum = 0m;
_llQueue.Clear();
_llSum = 0m;
_trQueue.Clear();
_trSum = 0m;
_prevClose = 0m;
_hasPrevClose = false;
}
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
lock (_sync)
{
var high = candle.HighPrice;
var low = candle.LowPrice;
var close = candle.ClosePrice;
var len = Length;
var atrLen = 14;
_candleCount++;
_highWindow.Enqueue(high);
if (_highWindow.Count > len)
_highWindow.Dequeue();
_lowWindow.Enqueue(low);
if (_lowWindow.Count > len)
_lowWindow.Dequeue();
decimal tr;
if (_hasPrevClose)
tr = Math.Max(high - low, Math.Max(Math.Abs(high - _prevClose), Math.Abs(low - _prevClose)));
else
tr = high - low;
_trQueue.Enqueue(tr);
_trSum += tr;
if (_trQueue.Count > atrLen)
_trSum -= _trQueue.Dequeue();
_prevClose = close;
_hasPrevClose = true;
if (_highWindow.Count < len)
return;
var hh = GetMax(_highWindow);
var ll = GetMin(_lowWindow);
_hhQueue.Enqueue(hh);
_hhSum += hh;
if (_hhQueue.Count > len)
_hhSum -= _hhQueue.Dequeue();
_llQueue.Enqueue(ll);
_llSum += ll;
if (_llQueue.Count > len)
_llSum -= _llQueue.Dequeue();
if (_hhQueue.Count < len || _trQueue.Count < atrLen)
return;
var hma = _hhSum / _hhQueue.Count;
var lma = _llSum / _llQueue.Count;
if (_prevHigh == 0m && _prevLow == 0m)
{
_prevHigh = high;
_prevLow = low;
_baseLine = close;
return;
}
var trend0 = _trend;
if (_nextTrend == 1)
{
_maxl = Math.Max(ll, _maxl);
if (hma < _maxl && close < _prevLow)
{
trend0 = 1;
_nextTrend = 0;
_minh = hh;
}
}
if (_nextTrend == 0)
{
_minh = Math.Min(hh, _minh);
if (lma > _minh && close > _prevHigh)
{
trend0 = 0;
_nextTrend = 1;
_maxl = ll;
}
}
int direction;
if (trend0 == 0)
{
_baseLine = _trend != 0 ? _baseLine : Math.Max(_maxl, _baseLine);
direction = 1;
}
else
{
_baseLine = _trend != 1 ? _baseLine : Math.Min(_minh, _baseLine);
direction = 0;
}
if (_prevDirection is int prevDir && direction != prevDir)
{
if (direction == 1 && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (direction == 0 && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
_prevDirection = direction;
_trend = trend0;
_prevHigh = high;
_prevLow = low;
}
}
private static decimal GetMax(IEnumerable<decimal> values)
{
var hasValue = false;
var result = 0m;
foreach (var value in values)
{
if (!hasValue || value > result)
{
result = value;
hasValue = true;
}
}
return result;
}
private static decimal GetMin(IEnumerable<decimal> values)
{
var hasValue = false;
var result = 0m;
foreach (var value in values)
{
if (!hasValue || value < result)
{
result = value;
hasValue = true;
}
}
return result;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class ozymandias_strategy(Strategy):
def __init__(self):
super(ozymandias_strategy, self).__init__()
self._length = self.Param("Length", 8) \
.SetDisplay("Length", "Lookback period", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for candles", "General")
self._trend = 0
self._next_trend = 0
self._maxl = 0.0
self._minh = float('inf')
self._base_line = 0.0
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_direction = None
self._candle_count = 0
self._high_window = deque()
self._low_window = deque()
self._hh_queue = deque()
self._hh_sum = 0.0
self._ll_queue = deque()
self._ll_sum = 0.0
self._tr_queue = deque()
self._tr_sum = 0.0
self._prev_close = 0.0
self._has_prev_close = False
@property
def Length(self):
return self._length.Value
@Length.setter
def Length(self, value):
self._length.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(ozymandias_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
length = self.Length
atr_len = 14
self._candle_count += 1
self._high_window.append(high)
if len(self._high_window) > length:
self._high_window.popleft()
self._low_window.append(low)
if len(self._low_window) > length:
self._low_window.popleft()
if self._has_prev_close:
tr = max(high - low, max(abs(high - self._prev_close), abs(low - self._prev_close)))
else:
tr = high - low
self._tr_queue.append(tr)
self._tr_sum += tr
if len(self._tr_queue) > atr_len:
self._tr_sum -= self._tr_queue.popleft()
self._prev_close = close
self._has_prev_close = True
if len(self._high_window) < length:
return
hh = max(self._high_window)
ll = min(self._low_window)
self._hh_queue.append(hh)
self._hh_sum += hh
if len(self._hh_queue) > length:
self._hh_sum -= self._hh_queue.popleft()
self._ll_queue.append(ll)
self._ll_sum += ll
if len(self._ll_queue) > length:
self._ll_sum -= self._ll_queue.popleft()
if len(self._hh_queue) < length or len(self._tr_queue) < atr_len:
return
hma = self._hh_sum / len(self._hh_queue)
lma = self._ll_sum / len(self._ll_queue)
if self._prev_high == 0.0 and self._prev_low == 0.0:
self._prev_high = high
self._prev_low = low
self._base_line = close
return
trend0 = self._trend
if self._next_trend == 1:
self._maxl = max(ll, self._maxl)
if hma < self._maxl and close < self._prev_low:
trend0 = 1
self._next_trend = 0
self._minh = hh
if self._next_trend == 0:
self._minh = min(hh, self._minh)
if lma > self._minh and close > self._prev_high:
trend0 = 0
self._next_trend = 1
self._maxl = ll
if trend0 == 0:
if self._trend != 0:
pass
else:
self._base_line = max(self._maxl, self._base_line)
direction = 1
else:
if self._trend != 1:
pass
else:
self._base_line = min(self._minh, self._base_line)
direction = 0
if self._prev_direction is not None and direction != self._prev_direction:
pos = self.Position
if direction == 1 and pos <= 0:
self.BuyMarket(self.Volume + abs(pos))
elif direction == 0 and pos >= 0:
self.SellMarket(self.Volume + abs(pos))
self._prev_direction = direction
self._trend = trend0
self._prev_high = high
self._prev_low = low
def OnReseted(self):
super(ozymandias_strategy, self).OnReseted()
self._trend = 0
self._next_trend = 0
self._maxl = 0.0
self._minh = float('inf')
self._base_line = 0.0
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_direction = None
self._candle_count = 0
self._high_window = deque()
self._low_window = deque()
self._hh_queue = deque()
self._hh_sum = 0.0
self._ll_queue = deque()
self._ll_sum = 0.0
self._tr_queue = deque()
self._tr_sum = 0.0
self._prev_close = 0.0
self._has_prev_close = False
def CreateClone(self):
return ozymandias_strategy()