Demo GPT - Day Trading Scalping Strategy
Enters long after two consecutive green candles when the second candle opens above the first one's close and volume exceeds its 20-bar average. Closes the position on two consecutive red candles where the second opens below the first one's close under the same volume condition. Calculates EMA(20), VWAP, and Camarilla levels for reference.
Executed conditions and defaults
Defaults: EmaLength=20, VolumeLookback=20, CandleType=1 minute. After at least 20 previous finished candles are stored, define volumeHigh = current.TotalVolume > average(previous 20 TotalVolumes) (current volume is excluded from its own average). From flat, enter long only when both previous and current candles satisfy Close > Open, current Open > previous Close, and volumeHigh. Close the entire actual long position when both candles satisfy Close < Open, current Open < previous Close, and volumeHigh. A pattern without its strict opening gap or volume confirmation is not a signal, and no short position is ever opened. For reference only (not signal gating), EMA20 uses completed closes; each day's VWAP is sum(typical price × volume) / sum(volume), resetting daily; prior-day Camarilla H3/L3 = previous close ± 1.1 × (previous high − previous low) / 4. These reference values are calculated but are not additional trading filters. No independent stop-loss is supplied.
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>
/// Two-candle scalp: long on two green candles with a gap up and a volume
/// spike; exit on two red candles with a gap down and a volume spike.
/// EMA20, session VWAP and prior-day Camarilla levels are reference-only.
/// </summary>
public class DemoGptDayTradingScalpingStrategy : Strategy
{
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _volumeLookback;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _ema;
private readonly List<decimal> _priorVolumes = [];
private decimal? _previousClose;
private bool _previousGreen;
private bool _previousRed;
private DateTime? _sessionDate;
private decimal _sessionHigh;
private decimal _sessionLow;
private decimal _sessionClose;
private decimal _sessionSumPriceVolume;
private decimal _sessionSumVolume;
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int VolumeLookback { get => _volumeLookback.Value; set => _volumeLookback.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>Reference-only EMA of close prices.</summary>
public decimal ReferenceEma { get; private set; }
/// <summary>Current session VWAP, weighted by total candle volume.</summary>
public decimal ReferenceVwap { get; private set; }
/// <summary>Prior-day Camarilla H3 level.</summary>
public decimal ReferenceCamarillaH3 { get; private set; }
/// <summary>Prior-day Camarilla L3 level.</summary>
public decimal ReferenceCamarillaL3 { get; private set; }
public DemoGptDayTradingScalpingStrategy()
{
_emaLength = Param(nameof(EmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "Reference-only exponential moving average", "Indicators");
_volumeLookback = Param(nameof(VolumeLookback), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Lookback", "Preceding candles in the volume confirmation", "Signals");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Scalping candle interval", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_priorVolumes.Clear();
_previousClose = null;
_previousGreen = false;
_previousRed = false;
_sessionDate = null;
_sessionHigh = 0m;
_sessionLow = 0m;
_sessionClose = 0m;
_sessionSumPriceVolume = 0m;
_sessionSumVolume = 0m;
ReferenceEma = 0m;
ReferenceVwap = 0m;
ReferenceCamarillaH3 = 0m;
ReferenceCamarillaL3 = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = EmaLength };
_priorVolumes.Clear();
_previousClose = null;
_previousGreen = false;
_previousRed = false;
_sessionDate = null;
_sessionSumPriceVolume = 0m;
_sessionSumVolume = 0m;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_ema, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
ReferenceEma = emaValue;
var date = candle.OpenTime.Date;
if (_sessionDate != date)
{
if (_sessionDate is not null)
{
var range = _sessionHigh - _sessionLow;
ReferenceCamarillaH3 = _sessionClose + range * 1.1m / 4m;
ReferenceCamarillaL3 = _sessionClose - range * 1.1m / 4m;
}
_sessionDate = date;
_sessionHigh = candle.HighPrice;
_sessionLow = candle.LowPrice;
_sessionSumPriceVolume = 0m;
_sessionSumVolume = 0m;
}
_sessionHigh = Math.Max(_sessionHigh, candle.HighPrice);
_sessionLow = Math.Min(_sessionLow, candle.LowPrice);
_sessionClose = candle.ClosePrice;
var volume = Math.Max(0m, candle.TotalVolume);
_sessionSumPriceVolume += ((candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m) * volume;
_sessionSumVolume += volume;
ReferenceVwap = _sessionSumVolume > 0m
? _sessionSumPriceVolume / _sessionSumVolume
: candle.ClosePrice;
decimal volumeSum = 0m;
foreach (var previousVolume in _priorVolumes)
volumeSum += previousVolume;
var volumeConfirmed = _priorVolumes.Count >= VolumeLookback &&
candle.TotalVolume > volumeSum / VolumeLookback;
var green = candle.ClosePrice > candle.OpenPrice;
var red = candle.ClosePrice < candle.OpenPrice;
var upGap = _previousClose is decimal lastCloseUp && candle.OpenPrice > lastCloseUp;
var downGap = _previousClose is decimal lastCloseDown && candle.OpenPrice < lastCloseDown;
// The previous-candle flags are updated only AFTER testing signals.
var buy = _previousGreen && green && upGap && volumeConfirmed;
var exit = _previousRed && red && downGap && volumeConfirmed;
_previousClose = candle.ClosePrice;
_previousGreen = green;
_previousRed = red;
_priorVolumes.Add(volume);
if (_priorVolumes.Count > VolumeLookback)
_priorVolumes.RemoveAt(0);
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position == 0m && buy)
BuyMarket();
else if (Position > 0m && exit)
SellMarket(Position);
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class demo_gpt_day_trading_scalping_strategy(Strategy):
"""Long-only two-candle volume-confirmed gap strategy, with reference levels."""
def __init__(self):
super(demo_gpt_day_trading_scalping_strategy, self).__init__()
self._ema_length = self.Param("EmaLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("EMA Length", "Reference EMA", "Indicators")
self._volume_lookback = self.Param("VolumeLookback", 20) \
.SetGreaterThanZero() \
.SetDisplay("Volume Lookback", "Preceding candles for volume confirmation", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Scalping candles", "General")
self._ema = None
self._prior_volumes = []
self._previous_close = None
self._previous_green = False
self._previous_red = False
self._session_date = None
self._session_high = 0.0
self._session_low = 0.0
self._session_close = 0.0
self._session_price_volume = 0.0
self._session_volume = 0.0
self.reference_ema = 0.0
self.reference_vwap = 0.0
self.reference_camarilla_h3 = 0.0
self.reference_camarilla_l3 = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(demo_gpt_day_trading_scalping_strategy, self).OnReseted()
self._ema = None
self._prior_volumes = []
self._previous_close = None
self._previous_green = False
self._previous_red = False
self._session_date = None
self._session_high = 0.0
self._session_low = 0.0
self._session_close = 0.0
self._session_price_volume = 0.0
self._session_volume = 0.0
self.reference_ema = 0.0
self.reference_vwap = 0.0
self.reference_camarilla_h3 = 0.0
self.reference_camarilla_l3 = 0.0
def OnStarted2(self, time):
super(demo_gpt_day_trading_scalping_strategy, self).OnStarted2(time)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_length.Value)
self._prior_volumes = []
self._previous_close = None
self._previous_green = False
self._previous_red = False
self._session_date = None
self._session_price_volume = 0.0
self._session_volume = 0.0
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
self.reference_ema = float(ema_value)
date = candle.OpenTime.Date
hi = float(candle.HighPrice)
lo = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._session_date != date:
if self._session_date is not None:
range_val = self._session_high - self._session_low
self.reference_camarilla_h3 = self._session_close + range_val * 1.1 / 4.0
self.reference_camarilla_l3 = self._session_close - range_val * 1.1 / 4.0
self._session_date = date
self._session_high = hi
self._session_low = lo
self._session_price_volume = 0.0
self._session_volume = 0.0
self._session_high = max(self._session_high, hi)
self._session_low = min(self._session_low, lo)
self._session_close = close
volume = max(0.0, float(candle.TotalVolume))
self._session_price_volume += ((hi + lo + close) / 3.0) * volume
self._session_volume += volume
self.reference_vwap = (
self._session_price_volume / self._session_volume
if self._session_volume > 0 else close
)
length = int(self._volume_lookback.Value)
volume_confirmed = (
len(self._prior_volumes) >= length and
float(candle.TotalVolume) > sum(self._prior_volumes) / length
)
green = close > float(candle.OpenPrice)
red = close < float(candle.OpenPrice)
up_gap = self._previous_close is not None and float(candle.OpenPrice) > self._previous_close
down_gap = self._previous_close is not None and float(candle.OpenPrice) < self._previous_close
buy = self._previous_green and green and up_gap and volume_confirmed
exit_signal = self._previous_red and red and down_gap and volume_confirmed
self._previous_close = close
self._previous_green = green
self._previous_red = red
self._prior_volumes.append(volume)
if len(self._prior_volumes) > length:
self._prior_volumes.pop(0)
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position == 0 and buy:
self.BuyMarket()
elif self.Position > 0 and exit_signal:
self.SellMarket(self.Position)
def CreateClone(self):
return demo_gpt_day_trading_scalping_strategy()