バーチャル・トレーリングストップ戦略
この戦略は、ロングおよびショートポジションのバーチャル・トレーリングストップをエミュレートします。エントリーシグナルは生成しません。注文は外部またはマニュアルで開く必要があります。ポジションが存在すると、戦略は価格が有利な方向に動くにつれてトレーリングストップを追従させます。価格がストップレベルに達した場合、ポジションは成行で決済されます。
パラメーター
StopLoss– 価格ステップ単位の固定ストップロス距離。TakeProfit– 価格ステップ単位の固定テイクプロフィット距離。TrailingStop– 現在価格からトレーリングストップまでの距離。TrailingStart– トレーリング開始前の価格ステップ単位の最小利益。TrailingStep– トレーリングレベルを移動させるために必要な最小追加利益。CandleType– 価格データ処理に使用するローソク足シリーズ。
注記
この戦略は指定されたタイプのローソク足を購読し、閉じたローソク足のみでトレーリングロジックを評価します。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Virtual risk manager. Never opens a position. Tracks externally executed
/// position average price and closes it if a finished candle reaches the
/// fixed stop, target, or a monotonically tightening virtual trailing stop.
/// All configured distances are security price steps.
/// </summary>
public class VirtualTrailingStopStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLoss, _takeProfit, _trailingStop, _trailingStart, _trailingStep;
private readonly StrategyParam<DataType> _candleType;
private decimal? _entryPrice, _bestPrice, _virtualStop;
private decimal _trackedPosition;
private Order _pending;
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal TrailingStop { get => _trailingStop.Value; set => _trailingStop.Value = value; }
public decimal TrailingStart { get => _trailingStart.Value; set => _trailingStart.Value = value; }
public decimal TrailingStep { get => _trailingStep.Value; set => _trailingStep.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VirtualTrailingStopStrategy()
{
_stopLoss = Param(nameof(StopLoss), 0m).SetNotNegative()
.SetDisplay("Stop Loss", "Fixed distance from filled entry in price steps", "Risk");
_takeProfit = Param(nameof(TakeProfit), 0m).SetNotNegative()
.SetDisplay("Take Profit", "Fixed target distance in price steps", "Risk");
_trailingStop = Param(nameof(TrailingStop), 30m).SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing distance in price steps", "Risk");
_trailingStart = Param(nameof(TrailingStart), 10m).SetNotNegative()
.SetDisplay("Trailing Start", "Required favorable movement in price steps", "Risk");
_trailingStep = Param(nameof(TrailingStep), 1m).SetGreaterThanZero()
.SetDisplay("Trailing Step", "Minimum improvement before tightening", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Processed finished candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
private void ResetTracking()
{
_entryPrice = _bestPrice = _virtualStop = null;
_trackedPosition = 0m;
_pending = null;
}
protected override void OnReseted()
{
base.OnReseted();
ResetTracking();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetTracking();
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
}
protected override void OnNewPosition(Position position)
{
base.OnNewPosition(position);
ObservePosition(position);
}
protected override void OnPositionChanged(Position position)
{
base.OnPositionChanged(position);
ObservePosition(position);
}
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
ObservePosition(position);
}
private void ObservePosition(Position position)
{
if (position.Security != Security || position.Portfolio != Portfolio)
return;
var volume = position.CurrentValue ?? 0m;
if (volume == 0m)
{
_entryPrice = _bestPrice = _virtualStop = null;
_trackedPosition = 0m;
return;
}
if (position.AveragePrice is not decimal price || price <= 0m)
return;
if (_entryPrice == price && _trackedPosition * volume > 0m)
{
_trackedPosition = volume;
return;
}
_entryPrice = _bestPrice = price;
_trackedPosition = volume;
_virtualStop = null;
}
private void Tighten(bool isLong, decimal candidate)
=> _virtualStop = _virtualStop is decimal old
? isLong ? Math.Max(old, candidate) : Math.Min(old, candidate)
: candidate;
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || Position == 0m || _entryPrice is not decimal entry ||
!IsFormedAndOnlineAndAllowTrading() ||
(_pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed)))
return;
var isLong = Position > 0m;
var price = candle.ClosePrice;
var step = Security.PriceStep is decimal positive && positive > 0m ? positive : 1m;
var best = _bestPrice ?? entry;
best = isLong ? Math.Max(best, price) : Math.Min(best, price);
_bestPrice = best;
if (TrailingStop > 0m)
{
var profit = isLong ? best - entry : entry - best;
if (profit >= TrailingStart * step)
{
var candidate = isLong ? best - TrailingStop * step : best + TrailingStop * step;
if (_virtualStop is not decimal existing ||
(isLong ? candidate >= existing + TrailingStep * step
: candidate <= existing - TrailingStep * step))
Tighten(isLong, candidate);
}
}
var fixedStop = StopLoss > 0m && (isLong ? price <= entry - StopLoss * step
: price >= entry + StopLoss * step);
var target = TakeProfit > 0m && (isLong ? price >= entry + TakeProfit * step
: price <= entry - TakeProfit * step);
var trailing = _virtualStop is decimal stop && (isLong ? price <= stop : price >= stop);
if (fixedStop || target || trailing)
_pending = isLong ? SellMarket(Math.Abs(Position)) : BuyMarket(Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, OrderStates
from StockSharp.Algo.Strategies import Strategy
class virtual_trailing_stop_strategy(Strategy):
"""External-position-only virtual close-based stop, target and trailing manager."""
def __init__(self):
super(virtual_trailing_stop_strategy, self).__init__()
self._stop_loss = self.Param("StopLoss", 0.0).SetNotNegative().SetDisplay(
"Stop Loss", "Fixed distance from filled entry in price steps", "Risk")
self._take_profit = self.Param("TakeProfit", 0.0).SetNotNegative().SetDisplay(
"Take Profit", "Fixed target distance in price steps", "Risk")
self._trailing_stop = self.Param("TrailingStop", 30.0).SetNotNegative().SetDisplay(
"Trailing Stop", "Trailing distance in price steps", "Risk")
self._trailing_start = self.Param("TrailingStart", 10.0).SetNotNegative().SetDisplay(
"Trailing Start", "Required favorable movement in price steps", "Risk")
self._trailing_step = self.Param("TrailingStep", 1.0).SetGreaterThanZero().SetDisplay(
"Trailing Step", "Minimum improvement before tightening", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))).SetDisplay(
"Candle Type", "Processed finished candles", "General")
self._reset_tracking()
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _reset_tracking(self):
self._entry_price = self._best_price = self._virtual_stop = None
self._tracked_position = 0.0
self._pending = None
def OnReseted(self):
super(virtual_trailing_stop_strategy, self).OnReseted()
self._reset_tracking()
def OnStarted2(self, time):
super(virtual_trailing_stop_strategy, self).OnStarted2(time)
self._reset_tracking()
self.SubscribeCandles(self.CandleType).Bind(self.ProcessCandle).Start()
def OnNewPosition(self, position):
super(virtual_trailing_stop_strategy, self).OnNewPosition(position)
self._observe_position(position)
def OnPositionChanged(self, position):
super(virtual_trailing_stop_strategy, self).OnPositionChanged(position)
self._observe_position(position)
def OnPositionReceived(self, position):
super(virtual_trailing_stop_strategy, self).OnPositionReceived(position)
self._observe_position(position)
def _observe_position(self, position):
if position.Security != self.Security or position.Portfolio != self.Portfolio:
return
volume = float(position.CurrentValue or 0)
if volume == 0:
self._entry_price = self._best_price = self._virtual_stop = None
self._tracked_position = 0.0
return
if position.AveragePrice is None or position.AveragePrice <= 0:
return
price = float(position.AveragePrice)
if self._entry_price == price and self._tracked_position * volume > 0:
self._tracked_position = volume
return
self._entry_price = self._best_price = price
self._virtual_stop = None
self._tracked_position = volume
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished or self.Position == 0 or self._entry_price is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed):
return
is_long = self.Position > 0
price = float(candle.ClosePrice)
entry = self._entry_price
step = float(self.Security.PriceStep or 1)
if step <= 0:
step = 1.0
best = self._best_price if self._best_price is not None else entry
best = max(best, price) if is_long else min(best, price)
self._best_price = best
distance = float(self._trailing_stop.Value) * step
if distance > 0:
profit = best - entry if is_long else entry - best
if profit >= float(self._trailing_start.Value) * step:
candidate = best - distance if is_long else best + distance
move = float(self._trailing_step.Value) * step
current = self._virtual_stop
if current is None or (candidate >= current + move if is_long else candidate <= current - move):
self._virtual_stop = (max(current, candidate) if is_long else min(current, candidate)) if current is not None else candidate
fixed_dist = float(self._stop_loss.Value) * step
take_dist = float(self._take_profit.Value) * step
fixed_stop = fixed_dist > 0 and (price <= entry - fixed_dist if is_long else price >= entry + fixed_dist)
target = take_dist > 0 and (price >= entry + take_dist if is_long else price <= entry - take_dist)
trail_hit = self._virtual_stop is not None and (price <= self._virtual_stop if is_long else price >= self._virtual_stop)
if fixed_stop or target or trail_hit:
self._pending = self.SellMarket(abs(self.Position)) if is_long else self.BuyMarket(abs(self.Position))
def CreateClone(self):
return virtual_trailing_stop_strategy()