Genie Pivot Strategy
This strategy implements the Genie Pivot reversal scalping idea originally written in MQL4. It waits for a pivot pattern formed by seven consecutive candles and manages the open position with a fixed take profit and trailing stop.
Strategy Logic
- Pattern detection – a long signal appears when seven previous lows are strictly decreasing and the last completed candle makes a higher low with a close above the prior high. A short signal is generated by the mirrored condition on highs.
- Order execution – once a signal is confirmed the strategy opens a market order with the volume calculated from account equity and the configured risk parameters.
- Trade management – after entry a take-profit and trailing stop are set. The trailing stop activates only once the profit exceeds the trailing distance. If price reverses on the following candle (bearish for long, bullish for short) the position is closed immediately.
- Volume reduction – consecutive losing trades reduce the traded volume according to the
Decrease Factorparameter.
Parameters
| Name | Description |
|---|---|
TakeProfit |
Profit target in price steps from the entry price. |
TrailingStop |
Trailing stop distance in price steps. |
MaximumRisk |
Fraction of account value used to size the position. |
DecreaseFactor |
Reduces volume after consecutive losses. |
BaseVolume |
Fallback volume when portfolio value is unknown. |
CandleType |
Timeframe of candles to analyze. |
Notes
The strategy processes only finished candles. No Python version is provided yet.
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()