Стратегия AfterEffects
Стратегия AfterEffects основана на идее, что цены могут иметь последействия.
Она рассчитывает сигнал на основе текущей цены закрытия и цен открытия p и 2p баров назад:
signal = Close - 2 * Open[p] + Open[2p]
Положительный сигнал открывает длинную позицию, отрицательный — короткую.
Параметр Random инвертирует сигнал.
После входа стратегия ставит стоп-лосс на расстоянии StopLoss пунктов.
Когда цена движется на 2 * StopLoss пунктов в благоприятном направлении:
- при смене знака сигнала позиция переворачивается двойным объёмом;
- иначе стоп-лосс переносится вслед за ценой.
Подробности
- Критерии входа:
signal > 0для long,signal < 0для short. - Длинные/короткие: оба направления.
- Критерии выхода: противоположный сигнал или стоп-лосс.
- Стопы: трейлинг-стоп.
- Значения по умолчанию:
StopLoss= 500Period= 3Random= falseVolume= 1CandleType= TimeSpan.FromMinutes(1)
- Фильтры:
- Категория: Тренд
- Направление: Оба
- Индикаторы: Пользовательская формула
- Стопы: Трейлинг
- Сложность: Базовая
- Таймфрейм: Внутридневной (1m)
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on aftereffects in price series.
/// It evaluates a custom signal from historical opens and the current close.
/// </summary>
public class AfterEffectsStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<int> _period;
private readonly StrategyParam<bool> _random;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _pQueue = new();
private readonly Queue<decimal> _twoPQueue = new();
private decimal _openP;
private decimal _open2P;
private decimal _stopPrice;
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public int Period { get => _period.Value; set => _period.Value = value; }
public bool Random { get => _random.Value; set => _random.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AfterEffectsStrategy()
{
_stopLoss = Param(nameof(StopLoss), 500m)
.SetDisplay("Stop Loss", "Stop Loss distance", "General");
_period = Param(nameof(Period), 8)
.SetDisplay("Bar Period", "Period of bars for signal", "General");
_random = Param(nameof(Random), false)
.SetDisplay("Random Range", "Invert signal", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle Type", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pQueue.Clear();
_twoPQueue.Clear();
_openP = 0m;
_open2P = 0m;
_stopPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pQueue.Clear();
_twoPQueue.Clear();
_openP = 0m;
_open2P = 0m;
_stopPrice = 0m;
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;
_pQueue.Enqueue(candle.OpenPrice);
if (_pQueue.Count > Period)
{
_openP = _pQueue.Dequeue();
_twoPQueue.Enqueue(_openP);
if (_twoPQueue.Count > Period)
_open2P = _twoPQueue.Dequeue();
}
if (_twoPQueue.Count < Period)
return;
var signal = candle.ClosePrice - 2m * _openP + _open2P;
if (Random)
signal = -signal;
if (Position == 0)
{
if (signal > 0m)
{
BuyMarket();
_stopPrice = candle.ClosePrice - StopLoss;
}
else
{
SellMarket();
_stopPrice = candle.ClosePrice + StopLoss;
}
return;
}
if (Position > 0)
{
if (candle.ClosePrice <= _stopPrice)
{
if (signal < 0m)
{
// Reverse to short
SellMarket();
SellMarket();
_stopPrice = candle.ClosePrice + StopLoss;
}
else
{
// Just exit
SellMarket();
}
}
else
{
_stopPrice = Math.Max(_stopPrice, candle.ClosePrice - StopLoss);
}
}
else if (Position < 0)
{
if (candle.ClosePrice >= _stopPrice)
{
if (signal > 0m)
{
// Reverse to long
BuyMarket();
BuyMarket();
_stopPrice = candle.ClosePrice - StopLoss;
}
else
{
// Just exit
BuyMarket();
}
}
else
{
_stopPrice = Math.Min(_stopPrice, candle.ClosePrice + StopLoss);
}
}
}
}
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 StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class after_effects_strategy(Strategy):
def __init__(self):
super(after_effects_strategy, self).__init__()
self._stop_loss = self.Param("StopLoss", 500.0) \
.SetDisplay("Stop Loss", "Stop Loss distance", "General")
self._period = self.Param("Period", 8) \
.SetDisplay("Bar Period", "Period of bars for signal", "General")
self._random = self.Param("Random", False) \
.SetDisplay("Random Range", "Invert signal", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle Type", "General")
self._p_queue = []
self._two_p_queue = []
self._open_p = 0.0
self._open_2p = 0.0
self._stop_price = 0.0
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def period(self):
return self._period.Value
@property
def random(self):
return self._random.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(after_effects_strategy, self).OnReseted()
self._p_queue = []
self._two_p_queue = []
self._open_p = 0.0
self._open_2p = 0.0
self._stop_price = 0.0
def OnStarted2(self, time):
super(after_effects_strategy, self).OnStarted2(time)
self._p_queue = []
self._two_p_queue = []
self._open_p = 0.0
self._open_2p = 0.0
self._stop_price = 0.0
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
per = int(self.period)
sl = float(self.stop_loss)
close = float(candle.ClosePrice)
self._p_queue.append(float(candle.OpenPrice))
if len(self._p_queue) > per:
self._open_p = self._p_queue.pop(0)
self._two_p_queue.append(self._open_p)
if len(self._two_p_queue) > per:
self._open_2p = self._two_p_queue.pop(0)
if len(self._two_p_queue) < per:
return
signal = close - 2.0 * self._open_p + self._open_2p
if self.random:
signal = -signal
if self.Position == 0:
if signal > 0.0:
self.BuyMarket()
self._stop_price = close - sl
else:
self.SellMarket()
self._stop_price = close + sl
return
if self.Position > 0:
if close <= self._stop_price:
if signal < 0.0:
self.SellMarket()
self.SellMarket()
self._stop_price = close + sl
else:
self.SellMarket()
else:
self._stop_price = max(self._stop_price, close - sl)
elif self.Position < 0:
if close >= self._stop_price:
if signal > 0.0:
self.BuyMarket()
self.BuyMarket()
self._stop_price = close - sl
else:
self.BuyMarket()
else:
self._stop_price = min(self._stop_price, close + sl)
def CreateClone(self):
return after_effects_strategy()