Color Bears Gap Strategy
Implements a strategy based on the Color Bears Gap indicator. The indicator compares two smoothed gaps between the high price and smoothed open/close values. When the difference crosses zero, positions are opened in the new direction and opposite positions are closed.
Details
- Entry Criteria: Indicator crosses below zero -> buy, crosses above zero -> sell.
- Long/Short: Configurable via parameters.
- Exit Criteria: Opposite zero crossing.
- Stops: None.
- Default Values:
Length1= 12Length2= 5BuyOpen= trueSellOpen= trueBuyClose= trueSellClose= trueCandleType= 8-hour timeframe
- Filters:
- Category: Momentum
- Direction: Both
- Indicators: Color Bears Gap
- Stops: No
- Complexity: Medium
- Timeframe: 8-hour
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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>
/// Color Bears Gap zero crossing using smoothed high-close and high-open gaps.
/// Long when the difference crosses downward through zero; short on upward crossover.
/// </summary>
public class ColorBearsGapStrategy : Strategy
{
private readonly StrategyParam<int> _length1;
private readonly StrategyParam<int> _length2;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _sellClose;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _emaClose;
private ExponentialMovingAverage _emaOpen;
private ExponentialMovingAverage _gapClose;
private ExponentialMovingAverage _gapOpen;
private decimal? _previousGap;
private Order _pendingOrder;
public int Length1 { get => _length1.Value; set => _length1.Value = value; }
public int Length2 { get => _length2.Value; set => _length2.Value = value; }
public bool BuyOpen { get => _buyOpen.Value; set => _buyOpen.Value = value; }
public bool BuyClose { get => _buyClose.Value; set => _buyClose.Value = value; }
public bool SellOpen { get => _sellOpen.Value; set => _sellOpen.Value = value; }
public bool SellClose { get => _sellClose.Value; set => _sellClose.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ColorBearsGapStrategy()
{
_length1 = Param(nameof(Length1), 12).SetGreaterThanZero()
.SetDisplay("Length 1", "Open/close first smoothing", "Indicator");
_length2 = Param(nameof(Length2), 5).SetGreaterThanZero()
.SetDisplay("Length 2", "Gap second smoothing", "Indicator");
_buyOpen = Param(nameof(BuyOpen), true).SetDisplay("Buy Open", "Allow long entries", "Permissions");
_buyClose = Param(nameof(BuyClose), true).SetDisplay("Buy Close", "Allow closing longs", "Permissions");
_sellOpen = Param(nameof(SellOpen), true).SetDisplay("Sell Open", "Allow short entries", "Permissions");
_sellClose = Param(nameof(SellClose), true).SetDisplay("Sell Close", "Allow closing shorts", "Permissions");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candle subscription", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_previousGap = null;
_pendingOrder = null;
_emaClose = _emaOpen = _gapClose = _gapOpen = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousGap = null;
_pendingOrder = null;
_emaClose = new ExponentialMovingAverage { Length = Length1 };
_emaOpen = new ExponentialMovingAverage { Length = Length1 };
_gapClose = new ExponentialMovingAverage { Length = Length2 };
_gapOpen = new ExponentialMovingAverage { Length = Length2 };
Indicators.Add(_emaClose);
Indicators.Add(_emaOpen);
Indicators.Add(_gapClose);
Indicators.Add(_gapOpen);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var time = candle.OpenTime;
var close = _emaClose.Process(candle.ClosePrice, time, true).GetValue<decimal>();
var open = _emaOpen.Process(candle.OpenPrice, time, true).GetValue<decimal>();
var gapC = _gapClose.Process(candle.HighPrice - close, time, true).GetValue<decimal>();
var gapO = _gapOpen.Process(candle.HighPrice - open, time, true).GetValue<decimal>();
if (!_emaClose.IsFormed || !_emaOpen.IsFormed || !_gapClose.IsFormed || !_gapOpen.IsFormed)
return;
// Both smoothed gap values must belong to the same finished candle.
var diff = gapO - gapC;
var previous = _previousGap;
_previousGap = diff;
if (previous is null || !IsFormedAndOnlineAndAllowTrading() ||
(_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed)))
return;
if (previous > 0m && diff < 0m)
{
if (Position < 0m)
{
if (SellClose)
_pendingOrder = BuyMarket(Math.Abs(Position) + (BuyOpen ? Volume : 0m));
}
else if (Position == 0m && BuyOpen)
_pendingOrder = BuyMarket(Volume);
}
else if (previous < 0m && diff > 0m)
{
if (Position > 0m)
{
if (BuyClose)
_pendingOrder = SellMarket(Math.Abs(Position) + (SellOpen ? Volume : 0m));
}
else if (Position == 0m && SellOpen)
_pendingOrder = SellMarket(Volume);
}
}
}
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, OrderStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import process_float
class color_bears_gap_strategy(Strategy):
"""Same-bar smoothed high/open versus high/close gap, with independent side permissions."""
def __init__(self):
super(color_bears_gap_strategy, self).__init__()
self._length1 = self.Param("Length1", 12).SetDisplay("Length 1", "First smoothing", "Indicator")
self._length2 = self.Param("Length2", 5).SetDisplay("Length 2", "Second smoothing", "Indicator")
self._buy_open = self.Param("BuyOpen", True).SetDisplay("Buy Open", "Allow long entries", "Permissions")
self._buy_close = self.Param("BuyClose", True).SetDisplay("Buy Close", "Allow closing longs", "Permissions")
self._sell_open = self.Param("SellOpen", True).SetDisplay("Sell Open", "Allow short entries", "Permissions")
self._sell_close = self.Param("SellClose", True).SetDisplay("Sell Close", "Allow closing shorts", "Permissions")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))).SetDisplay(
"Candle Type", "Timeframe", "General")
self._reset_state()
@property
def length1(self): return self._length1.Value
@property
def length2(self): return self._length2.Value
@property
def candle_type(self): return self._candle_type.Value
@property
def buy_open(self): return self._buy_open.Value
@property
def buy_close(self): return self._buy_close.Value
@property
def sell_open(self): return self._sell_open.Value
@property
def sell_close(self): return self._sell_close.Value
def _reset_state(self):
self._ema_close = self._ema_open = None
self._gap_close = self._gap_open = None
self._previous_gap = None
self._pending_order = None
def OnReseted(self):
super(color_bears_gap_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(color_bears_gap_strategy, self).OnStarted2(time)
self._reset_state()
self._ema_close = ExponentialMovingAverage()
self._ema_close.Length = self.length1
self._ema_open = ExponentialMovingAverage()
self._ema_open.Length = self.length1
self._gap_close = ExponentialMovingAverage()
self._gap_close.Length = self.length2
self._gap_open = ExponentialMovingAverage()
self._gap_open.Length = self.length2
for indicator in (self._ema_close, self._ema_open, self._gap_close, self._gap_open):
self.Indicators.Add(indicator)
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
time = candle.OpenTime
close = process_float(self._ema_close, candle.ClosePrice, time, True)
open_price = process_float(self._ema_open, candle.OpenPrice, time, True)
gap_c = process_float(self._gap_close, float(candle.HighPrice) - float(close), time, True)
gap_o = process_float(self._gap_open, float(candle.HighPrice) - float(open_price), time, True)
if not (self._ema_close.IsFormed and self._ema_open.IsFormed and self._gap_close.IsFormed and self._gap_open.IsFormed):
return
diff = float(gap_o) - float(gap_c)
previous = self._previous_gap
self._previous_gap = diff
if previous is None or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
if previous > 0 and diff < 0:
if self.Position < 0:
if self.sell_close:
self._pending_order = self.BuyMarket(abs(self.Position) + (self.Volume if self.buy_open else 0))
elif self.Position == 0 and self.buy_open:
self._pending_order = self.BuyMarket(self.Volume)
elif previous < 0 and diff > 0:
if self.Position > 0:
if self.buy_close:
self._pending_order = self.SellMarket(abs(self.Position) + (self.Volume if self.sell_open else 0))
elif self.Position == 0 and self.sell_open:
self._pending_order = self.SellMarket(self.Volume)
def CreateClone(self):
return color_bears_gap_strategy()