Стратегия "Печати даркпула"
Стратегия отслеживает крупные внебиржевые сделки, которые нередко предвосхищают резкие движения после их появления в открытых данных. Необычно высокий объём может указывать на институциональные позиции, ещё не отражённые на основном рынке.
Тестирование показывает среднегодичную доходность около 46%. Стратегию лучше запускать на фондовом рынке.
Вход осуществляется в том же направлении, что и крупные покупки или продажи в даркпуле, ожидая продолжения, когда рынок отреагирует.
Небольшой процентный стоп ограничивает риск, а позиция закрывается, если ожидаемый импульс не развивается.
Детали
- Условия входа: сигнал индикатора
- Длинная/короткая: обе
- Условия выхода: стоп-лосс или противоположный сигнал
- Стопы: да, процентные
- Значения по умолчанию:
CandleType= 15 minuteStopLoss= 2%
- Фильтры:
- Категория: Объём
- Направление: обе
- Индикаторы: объём
- Стопы: да
- Сложность: средняя
- Таймфрейм: внутридневной
- Сезонность: нет
- Нейронные сети: нет
- Дивергенция: нет
- Уровень риска: средний
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>
/// Dark Pool Prints strategy.
/// Detects unusually high volume candles and trades in the direction of the candle
/// when confirmed by SMA trend direction.
/// Uses cooldown to control trade frequency.
/// </summary>
public class DarkPoolPrintsStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _volumeLookback;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _volumeHistory = new();
private int _cooldown;
/// <summary>
/// MA period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Volume lookback.
/// </summary>
public int VolumeLookback
{
get => _volumeLookback.Value;
set => _volumeLookback.Value = value;
}
/// <summary>
/// Volume multiplier threshold.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DarkPoolPrintsStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 20)
.SetRange(5, 50)
.SetDisplay("MA Period", "Period for trend SMA", "Indicators");
_volumeLookback = Param(nameof(VolumeLookback), 20)
.SetRange(5, 50)
.SetDisplay("Volume Lookback", "Bars for volume average", "Volume");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.5m)
.SetRange(1.2m, 5m)
.SetDisplay("Volume Multiplier", "Threshold multiplier for high volume", "Volume");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumeHistory.Clear();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumeHistory.Clear();
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Track volume history
_volumeHistory.Add(candle.TotalVolume);
if (_volumeHistory.Count > VolumeLookback)
_volumeHistory.RemoveAt(0);
if (_volumeHistory.Count < VolumeLookback)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Calculate average volume
decimal avgVolume = 0;
for (int i = 0; i < _volumeHistory.Count - 1; i++)
avgVolume += _volumeHistory[i];
avgVolume /= (_volumeHistory.Count - 1);
var isHighVolume = candle.TotalVolume > avgVolume * VolumeMultiplier;
if (!isHighVolume)
return;
var isBullish = candle.ClosePrice > candle.OpenPrice;
var isBearish = candle.ClosePrice < candle.OpenPrice;
var isAboveSma = candle.ClosePrice > smaValue;
var isBelowSma = candle.ClosePrice < smaValue;
if (Position == 0 && isBullish && isAboveSma)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && isBearish && isBelowSma)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && isBelowSma)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && isAboveSma)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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 dark_pool_prints_strategy(Strategy):
"""
Dark Pool Prints strategy.
Detects unusually high volume candles and trades in the direction of the candle
when confirmed by SMA trend direction.
Uses cooldown to control trade frequency.
"""
def __init__(self):
super(dark_pool_prints_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "Period for trend SMA", "Indicators")
self._volume_lookback = self.Param("VolumeLookback", 20).SetDisplay("Volume Lookback", "Bars for volume average", "Volume")
self._volume_multiplier = self.Param("VolumeMultiplier", 1.5).SetDisplay("Volume Multiplier", "Threshold multiplier for high volume", "Volume")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._volume_history = []
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dark_pool_prints_strategy, self).OnReseted()
self._volume_history = []
self._cooldown = 0
def OnStarted2(self, time):
super(dark_pool_prints_strategy, self).OnStarted2(time)
self._volume_history = []
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
lookback = self._volume_lookback.Value
# Track volume history
self._volume_history.append(float(candle.TotalVolume))
if len(self._volume_history) > lookback:
self._volume_history.pop(0)
if len(self._volume_history) < lookback:
return
if self._cooldown > 0:
self._cooldown -= 1
return
# Calculate average volume (excluding last entry)
avg_volume = sum(self._volume_history[:-1]) / (len(self._volume_history) - 1)
is_high_volume = float(candle.TotalVolume) > avg_volume * self._volume_multiplier.Value
if not is_high_volume:
return
sv = float(sma_val)
cd = self._cooldown_bars.Value
is_bullish = candle.ClosePrice > candle.OpenPrice
is_bearish = candle.ClosePrice < candle.OpenPrice
is_above_sma = float(candle.ClosePrice) > sv
is_below_sma = float(candle.ClosePrice) < sv
if self.Position == 0 and is_bullish and is_above_sma:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and is_bearish and is_below_sma:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and is_below_sma:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and is_above_sma:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return dark_pool_prints_strategy()