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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simple pivot-based strategy converted from the MetaTrader expert advisor in MQL/7610.
/// The strategy compares the current candle open with the previous candle range to decide
/// whether the next trade should be long or short.
/// </summary>
public class SimplePivotFlipStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal _previousHigh;
private decimal _previousLow;
private bool _hasPreviousCandle;
/// <summary>
/// Order volume used for market entries.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Candle type used for price subscription.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="SimplePivotFlipStrategy"/> class.
/// </summary>
public SimplePivotFlipStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Market order volume used for entries.", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for pivot calculation.", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousHigh = 0m;
_previousLow = 0m;
_hasPreviousCandle = false;
}
/// <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 void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPreviousCandle)
{
// Store the first completed candle to build the reference range.
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_hasPreviousCandle = true;
return;
}
// Calculate the pivot as the midpoint of the previous candle range.
var pivot = (_previousHigh + _previousLow) / 2m;
var desiredSide = Sides.Buy;
// If the new candle opens inside the upper half of the previous range we go short.
if (candle.OpenPrice < _previousHigh && candle.OpenPrice > pivot)
desiredSide = Sides.Sell;
// Skip re-entry if we already hold a position in the desired direction.
if ((desiredSide == Sides.Buy && Position > 0) || (desiredSide == Sides.Sell && Position < 0))
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
return;
}
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
if (desiredSide == Sides.Buy)
{
BuyMarket();
}
else
{
SellMarket();
}
// Update reference range for the next candle.
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
}
}
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 simple_pivot_flip_strategy(Strategy):
"""Simple pivot-based strategy. Compares current candle open with the previous
candle range midpoint. If open is in the upper half of the previous range, sell;
otherwise buy. Closes existing position before reversing."""
def __init__(self):
super(simple_pivot_flip_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Market order volume used for entries", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1))) \
.SetDisplay("Candle Type", "Type of candles used for pivot calculation", "Data")
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous_candle = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def OrderVolume(self):
return self._order_volume.Value
def OnReseted(self):
super(simple_pivot_flip_strategy, self).OnReseted()
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous_candle = False
def OnStarted2(self, time):
super(simple_pivot_flip_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_price = float(candle.OpenPrice)
if not self._has_previous_candle:
self._previous_high = high
self._previous_low = low
self._has_previous_candle = True
return
# Calculate the pivot as the midpoint of the previous candle range
pivot = (self._previous_high + self._previous_low) / 2.0
# Default to buy; if open is in upper half of previous range, sell
desired_buy = True
if open_price < self._previous_high and open_price > pivot:
desired_buy = False
# Skip re-entry if already holding position in desired direction
if desired_buy and self.Position > 0:
self._previous_high = high
self._previous_low = low
return
if not desired_buy and self.Position < 0:
self._previous_high = high
self._previous_low = low
return
# Close existing position
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
# Open new position
if desired_buy:
self.BuyMarket()
else:
self.SellMarket()
# Update reference range for next candle
self._previous_high = high
self._previous_low = low
def CreateClone(self):
return simple_pivot_flip_strategy()