Стратегия Genie Pivot
Стратегия Genie Pivot основана на идее разворота по пивотам из исходного советника MQL4. Она отслеживает формирование разворотного паттерна из семи последовательных свечей и управляет позицией при помощи фиксированного тейк‑профита и трейлинг‑стопа.
Логика стратегии
- Поиск паттерна – сигнал на покупку появляется, когда семь предыдущих минимумов строго убывают, а последняя завершённая свеча делает более высокий минимум и закрывается выше предыдущего максимума. Сигнал на продажу формируется зеркальными условиями по максимумам.
- Открытие позиции – после подтверждения сигнала стратегия открывает рыночный ордер с объёмом, рассчитанным из величины капитала и параметров риска.
- Сопровождение сделки – после входа выставляются тейк‑профит и трейлинг‑стоп. Трейлинг активируется только после достижения прибыли, превышающей расстояние трейлинга. Если следующая свеча разворачивается (медвежья для лонга, бычья для шорта), позиция закрывается.
- Снижение объёма – серия убыточных сделок уменьшает объём согласно параметру
Decrease Factor.
Параметры
| Имя | Описание |
|---|---|
TakeProfit |
Цель по прибыли в шагах цены от цены входа. |
TrailingStop |
Дистанция трейлинг‑стопа в шагах цены. |
MaximumRisk |
Доля капитала, используемая для расчёта объёма. |
DecreaseFactor |
Снижение объёма после серии убыточных сделок. |
BaseVolume |
Резервный объём, когда стоимость портфеля неизвестна. |
CandleType |
Таймфрейм свечей. |
Примечания
Стратегия обрабатывает только завершённые свечи. Версия на Python пока отсутствует.
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>
/// Pivot point reversal scalping strategy.
/// </summary>
public class GeniePivotStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<decimal> _maximumRisk;
private readonly StrategyParam<decimal> _decreaseFactor;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly decimal[] _lows = new decimal[8];
private readonly decimal[] _highs = new decimal[8];
private int _filled;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _targetPrice;
private int _lossCount;
private int _cooldownRemaining;
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public decimal TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Maximum risk used to calculate volume.
/// </summary>
public decimal MaximumRisk
{
get => _maximumRisk.Value;
set => _maximumRisk.Value = value;
}
/// <summary>
/// Factor to decrease volume after consecutive losses.
/// </summary>
public decimal DecreaseFactor
{
get => _decreaseFactor.Value;
set => _decreaseFactor.Value = value;
}
/// <summary>
/// Base volume used when account value is unknown.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Candle type for subscription.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of completed candles to wait after closing a position.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="GeniePivotStrategy"/>.
/// </summary>
public GeniePivotStrategy()
{
_takeProfit = Param(nameof(TakeProfit), 500m).SetDisplay("Take Profit", "Profit target in points", "Risk");
_trailingStop =
Param(nameof(TrailingStop), 200m).SetDisplay("Trailing Stop", "Trailing distance in points", "Risk");
_maximumRisk = Param(nameof(MaximumRisk), 0.02m).SetDisplay("Maximum Risk", "Risk per trade", "Risk");
_decreaseFactor =
Param(nameof(DecreaseFactor), 3m).SetDisplay("Decrease Factor", "Volume reduction after losses", "Risk");
_baseVolume = Param(nameof(BaseVolume), 0.1m).SetDisplay("Base Volume", "Fallback volume", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_cooldownBars = Param(nameof(CooldownBars), 4)
.SetDisplay("Cooldown Bars", "Completed candles to wait after closing a position", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_filled = 0;
_entryPrice = default;
_stopPrice = default;
_targetPrice = default;
_lossCount = 0;
_cooldownRemaining = 0;
Array.Clear(_lows);
Array.Clear(_highs);
}
/// <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 decimal GetVolume()
{
var lot = BaseVolume;
if (MaximumRisk > 0m && Portfolio.CurrentValue is decimal value)
lot = Math.Round(value * MaximumRisk / 1000m, 1);
if (DecreaseFactor > 0m && _lossCount > 1)
lot -= lot * _lossCount / DecreaseFactor;
return lot < 0.1m ? 0.1m : lot;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var step = Security.PriceStep ?? 1m;
var close = candle.ClosePrice;
var open = candle.OpenPrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
// shift history
for (var i = _lows.Length - 1; i > 0; i--)
{
_lows[i] = _lows[i - 1];
_highs[i] = _highs[i - 1];
}
_lows[0] = low;
_highs[0] = high;
if (_filled < 4)
{
_filled++;
return;
}
if (Position == 0)
{
if (_cooldownRemaining > 0)
return;
// Buy when 3 consecutive declining lows followed by a higher low (reversal)
var buyCond = _lows[4] > _lows[3] && _lows[3] > _lows[2] && _lows[2] > _lows[1] &&
_lows[1] < _lows[0] && close > _highs[1] && close > open;
// Sell when 3 consecutive rising highs followed by a lower high (reversal)
var sellCond = _highs[4] < _highs[3] && _highs[3] < _highs[2] && _highs[2] < _highs[1] &&
_highs[1] > _highs[0] && close < _lows[1] && close < open;
if (buyCond)
{
BuyMarket();
_entryPrice = close;
_stopPrice = _entryPrice - TrailingStop * step;
_targetPrice = _entryPrice + TakeProfit * step;
}
else if (sellCond)
{
SellMarket();
_entryPrice = close;
_stopPrice = _entryPrice + TrailingStop * step;
_targetPrice = _entryPrice - TakeProfit * step;
}
}
else if (Position > 0)
{
if (close >= _targetPrice)
{
SellMarket();
_lossCount = 0;
_cooldownRemaining = CooldownBars;
}
else
{
if (close - _entryPrice > TrailingStop * step)
{
var newStop = close - TrailingStop * step;
if (newStop > _stopPrice)
_stopPrice = newStop;
}
if (low <= _stopPrice)
{
SellMarket();
_lossCount++;
_cooldownRemaining = CooldownBars;
}
}
}
else if (Position < 0)
{
if (close <= _targetPrice)
{
BuyMarket();
_lossCount = 0;
_cooldownRemaining = CooldownBars;
}
else
{
if (_entryPrice - close > TrailingStop * step)
{
var newStop = close + TrailingStop * step;
if (newStop < _stopPrice)
_stopPrice = newStop;
}
if (high >= _stopPrice)
{
BuyMarket();
_lossCount++;
_cooldownRemaining = 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.Strategies import Strategy
class genie_pivot_strategy(Strategy):
"""
Pivot point reversal scalping strategy.
Buys on 3 declining lows followed by higher low reversal.
Sells on 3 rising highs followed by lower high reversal.
Includes trailing stop and take profit management.
"""
def __init__(self):
super(genie_pivot_strategy, self).__init__()
self._take_profit = self.Param("TakeProfit", 500.0) \
.SetDisplay("Take Profit", "Profit target in points", "Risk")
self._trailing_stop = self.Param("TrailingStop", 200.0) \
.SetDisplay("Trailing Stop", "Trailing distance in points", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after closing", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._lows = [0.0] * 8
self._highs = [0.0] * 8
self._filled = 0
self._entry_price = 0.0
self._stop_price = 0.0
self._target_price = 0.0
self._loss_count = 0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(genie_pivot_strategy, self).OnReseted()
self._lows = [0.0] * 8
self._highs = [0.0] * 8
self._filled = 0
self._entry_price = 0.0
self._stop_price = 0.0
self._target_price = 0.0
self._loss_count = 0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(genie_pivot_strategy, self).OnStarted2(time)
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
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
for i in range(len(self._lows) - 1, 0, -1):
self._lows[i] = self._lows[i - 1]
self._highs[i] = self._highs[i - 1]
self._lows[0] = low
self._highs[0] = high
if self._filled < 4:
self._filled += 1
return
if self.Position == 0:
if self._cooldown_remaining > 0:
return
buy_cond = (self._lows[4] > self._lows[3] and self._lows[3] > self._lows[2]
and self._lows[2] > self._lows[1] and self._lows[1] < self._lows[0]
and close > self._highs[1] and close > open_p)
sell_cond = (self._highs[4] < self._highs[3] and self._highs[3] < self._highs[2]
and self._highs[2] < self._highs[1] and self._highs[1] > self._highs[0]
and close < self._lows[1] and close < open_p)
if buy_cond:
self.BuyMarket()
self._entry_price = close
self._stop_price = self._entry_price - self._trailing_stop.Value * step
self._target_price = self._entry_price + self._take_profit.Value * step
elif sell_cond:
self.SellMarket()
self._entry_price = close
self._stop_price = self._entry_price + self._trailing_stop.Value * step
self._target_price = self._entry_price - self._take_profit.Value * step
elif self.Position > 0:
if close >= self._target_price:
self.SellMarket()
self._loss_count = 0
self._cooldown_remaining = self._cooldown_bars.Value
else:
trailing = self._trailing_stop.Value * step
if close - self._entry_price > trailing:
new_stop = close - trailing
if new_stop > self._stop_price:
self._stop_price = new_stop
if low <= self._stop_price:
self.SellMarket()
self._loss_count += 1
self._cooldown_remaining = self._cooldown_bars.Value
elif self.Position < 0:
if close <= self._target_price:
self.BuyMarket()
self._loss_count = 0
self._cooldown_remaining = self._cooldown_bars.Value
else:
trailing = self._trailing_stop.Value * step
if self._entry_price - close > trailing:
new_stop = close + trailing
if new_stop < self._stop_price:
self._stop_price = new_stop
if high >= self._stop_price:
self.BuyMarket()
self._loss_count += 1
self._cooldown_remaining = self._cooldown_bars.Value
def CreateClone(self):
return genie_pivot_strategy()