Estrategia Genie Pivot
Esta estrategia implementa la idea de scalping de reversión Genie Pivot originalmente escrita en MQL4. Espera un patrón de pivote formado por siete velas consecutivas y gestiona la posición abierta con un take profit fijo y un stop trailing.
Lógica de la Estrategia
- Detección del patrón – una señal larga aparece cuando los siete mínimos anteriores son estrictamente decrecientes y la última vela completada hace un mínimo más alto con un cierre por encima del máximo anterior. Una señal corta se genera por la condición espejo en los máximos.
- Ejecución de órdenes – una vez confirmada la señal, la estrategia abre una orden de mercado con el volumen calculado a partir del patrimonio de la cuenta y los parámetros de riesgo configurados.
- Gestión de operaciones – tras la entrada se establecen un take profit y un stop trailing. El stop trailing se activa solo una vez que el beneficio supera la distancia de trailing. Si el precio revierte en la siguiente vela (bajista para largo, alcista para corto), la posición se cierra inmediatamente.
- Reducción de volumen – las operaciones perdedoras consecutivas reducen el volumen negociado según el parámetro
Decrease Factor.
Parámetros
| Nombre | Descripción |
|---|---|
TakeProfit |
Objetivo de ganancia en pasos de precio desde el precio de entrada. |
TrailingStop |
Distancia del stop trailing en pasos de precio. |
MaximumRisk |
Fracción del valor de la cuenta utilizada para dimensionar la posición. |
DecreaseFactor |
Reduce el volumen tras pérdidas consecutivas. |
BaseVolume |
Volumen de respaldo cuando el valor del portafolio es desconocido. |
CandleType |
Marco temporal de las velas a analizar. |
Notas
La estrategia procesa únicamente velas completadas. No se ha proporcionado versión en Python todavía.
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()