GitHub で見る

JB戦略

概要

JB戦略はfxDreemaのエキスパートアドバイザーを元にしており、長期トレンドフィルター、モメンタム確認、ボラティリティブレイクアウトを組み合わせます。

  • トレンドフィルター: 直前のローソク足終値が100期間単純移動平均より上(ロング)または下(ショート)にあることを求めます。
  • モメンタムフィルター: 100期間Force Indexで方向を確認します(ロングは正、ショートは負)。
  • ボラティリティトリガー: 直前終値が対応するボリンジャーバンド(20期間、偏差2.0)を突き抜けたときにエントリーします。
  • ポジション管理: 損失サイクル後はマーチンゲール風の乗数で注文数量を増やし、利益サイクル後は基本数量へ戻します。
  • 決済ルール: 1契約あたりの平均含み益が設定した金額目標に達すると、すべてのオープンポジションを閉じます。

パラメーター

名前 説明
SmaPeriod SMAトレンドフィルターの長さ。既定値: 100。
ForcePeriod Force Index指標の長さ。既定値: 100。
BollingerPeriod ボリンジャーバンドの長さ。既定値: 20。
BollingerDeviation ボリンジャーバンドの標準偏差乗数。既定値: 2.0。
BaseVolume マーチンゲール調整前の初期注文数量。既定値: 0.1。
LossMultiplier 損失サイクル後の次回注文数量に適用する乗数。既定値: 1.55。
AverageProfitTarget すべてのポジションを閉じるために必要な1契約あたりの平均含み益。既定値: 2.8。
CandleType 計算に使うローソク足タイプ(既定は1分足)。

シグナル

ロングエントリー

  1. 直前ローソク足の終値が下側ボリンジャーバンド以下である。
  2. 直前終値が100期間SMAより高い(上向きトレンド)。
  3. Force Indexの値が正である。

ショートエントリー

  1. 直前ローソク足の終値が上側ボリンジャーバンド以上である。
  2. 直前終値が100期間SMAより低い(下向きトレンド)。
  3. Force Indexの値が負である。

決済

  • すべてのオープンポジション全体で1契約あたりの平均含み益がAverageProfitTargetに達すると、全ポジションを成行で閉じます。
  • ポジションがフラットになるたびに、次回注文数量を調整します。損失サイクル後はLossMultiplierを掛け、利益サイクル後はBaseVolumeへ戻します。

注記

  • マーチンゲール調整は実現PnLを使って損失連続の発生を判断します。数量増加が許容される銘柄でのみ使用してください。
  • StockSharp戦略はネットポジションで動作するため、MQL版のヘッジ(ロングとショートのバスケット同時保有)は集約ポジションで近似されます。
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;

public class JbStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public JbStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}