GitHub で見る

Demo GPT - デイトレードスキャルピング戦略

2本連続の陽線が現れ、2本目が1本目の終値より高く始まり、出来高が20本平均を超えたときにロングエントリーします。2本連続の陰線が現れ、2本目が1本目の終値より低く始まり、同じ出来高条件を満たしたときにポジションを決済します。参考として EMA(20)、VWAP、Camarilla レベルを計算します。

実際の条件と既定値

既定値: EmaLength=20、VolumeLookback=20、CandleType=1分足。直前20本の確定足を蓄積後、volumeHigh = 現在のTotalVolume > 直前20本の平均TotalVolume(現在の足は平均に含めない)。ノーポジションで前後2本が陽線(Close > Open)、現在足に**Open > 前足のCloseのギャップアップがあり、出来高条件も満たす場合にロングのみ建てます。2本連続陰線(Close < Open)、現在の Open < 前足のClose、高出来高なら実際のロング残高を全決済**。厳密な始値ギャップや出来高の確認がなければ信号ではありません。ショートは行いません。参考情報のみ: 終値EMA20、日ごとにリセットするVWAP sum(典型価格 × 出来高)/sum(出来高)、前日Camarilla H3/L3 = 前日終値 ± 1.1 × (前日高値 − 安値) / 4。参考値は売買フィルターではなく、独立した損切りもありません。

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);
    }
}