Lacust ストップと BE
この戦略は、オリジナルのMQLエキスパートアドバイザー lacuststopandbe にインスパイアされた基本的なポジション管理を示します。
最後に完成したローソク足の方向にポジションをエントリーした後、戦略はいくつかの保護ルールを適用します:
- 初期ストップロスとテイクプロフィットは固定価格距離に設定されます。
- 利益が
BreakevenGainに達すると、ストップがエントリー価格 +Breakevenに移動されます。 - 利益が
TrailingStartを超えると、ストップがTrailingStopの距離で価格を追跡します。 - ストップレベルまたはテイクプロフィットレベルに触れるとポジションが決済されます。
パラメーター:
CandleType– 処理に使用するローソク足シリーズ。StopLoss– 初期ストップロス距離。TakeProfit– 初期テイクプロフィット距離。TrailingStart– トレーリングストップを有効にするために必要な利益。TrailingStop– 現在価格からのトレーリングストップ距離。BreakevenGain– ストップをブレークイーブンに移動する前に必要な利益。Breakeven– ストップをブレークイーブンに移動した後に確定する利益。
このサンプルはStockSharpの高レベルAPIを使用しており、単純なMQL取引管理スクリプトを移植するためのテンプレートとして活用できます。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Enters on the last completed candle's direction; manages actual filled positions
/// through best bid/ask quotes with initial SL/TP, breakeven and monotonic trailing.
/// </summary>
public class LacustStopAndBeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _trailingStart;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<decimal> _breakevenGain;
private readonly StrategyParam<decimal> _breakeven;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal? _favorableExtreme;
private decimal _trackedVolume;
private Order _pendingOrder;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal TrailingStart { get => _trailingStart.Value; set => _trailingStart.Value = value; }
public decimal TrailingStop { get => _trailingStop.Value; set => _trailingStop.Value = value; }
public decimal BreakevenGain { get => _breakevenGain.Value; set => _breakevenGain.Value = value; }
public decimal Breakeven { get => _breakeven.Value; set => _breakeven.Value = value; }
public LacustStopAndBeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Signal candles", "General");
_stopLoss = Param(nameof(StopLoss), 400m).SetNotNegative()
.SetDisplay("Stop Loss", "Initial risk distance in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m).SetNotNegative()
.SetDisplay("Take Profit", "Target distance in price units", "Risk");
_trailingStart = Param(nameof(TrailingStart), 300m).SetNotNegative()
.SetDisplay("Trailing Start", "Profit at which trailing starts", "Risk");
_trailingStop = Param(nameof(TrailingStop), 200m).SetGreaterThanZero()
.SetDisplay("Trailing Stop", "Trailing distance from favorable quote", "Risk");
_breakevenGain = Param(nameof(BreakevenGain), 250m).SetNotNegative()
.SetDisplay("Breakeven Gain", "Profit at which breakeven activates", "Risk");
_breakeven = Param(nameof(Breakeven), 100m).SetNotNegative()
.SetDisplay("Breakeven", "Profit locked on breakeven", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearPositionTracking();
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearPositionTracking();
_pendingOrder = null;
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var subscription = new Subscription(DataType.Level1, Security);
subscription.MarketData.BuildField = field;
SubscribeLevel1(subscription).Bind(ProcessQuote).Start();
}
}
private void ClearPositionTracking()
{
_entryPrice = _stopPrice = _takePrice = _favorableExtreme = null;
_trackedVolume = 0m;
}
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)
{
ClearPositionTracking();
return;
}
if (position.AveragePrice is not decimal average || average <= 0m)
return;
if (_entryPrice == average && _trackedVolume == volume)
return;
// Retain an already tightened stop after a partial same-side reduction.
if (_entryPrice == average && _trackedVolume * volume > 0m
&& Math.Abs(volume) < Math.Abs(_trackedVolume))
{
_trackedVolume = volume;
return;
}
_entryPrice = average;
_trackedVolume = volume;
_favorableExtreme = average;
_stopPrice = StopLoss > 0m
? volume > 0m ? average - StopLoss : average + StopLoss
: null;
_takePrice = TakeProfit > 0m
? volume > 0m ? average + TakeProfit : average - TakeProfit
: null;
}
private bool HasPendingOrder()
=> _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || Position != 0m
|| !IsFormedAndOnlineAndAllowTrading() || HasPendingOrder())
return;
if (candle.ClosePrice > candle.OpenPrice)
_pendingOrder = BuyMarket(Volume);
else if (candle.ClosePrice < candle.OpenPrice)
_pendingOrder = SellMarket(Volume);
}
private void ProcessQuote(Level1ChangeMessage quote)
{
if (Position == 0m || _entryPrice is not decimal entry
|| !IsFormedAndOnlineAndAllowTrading() || HasPendingOrder())
return;
var isLong = Position > 0m;
var field = isLong ? Level1Fields.BestBidPrice : Level1Fields.BestAskPrice;
if (!quote.Changes.TryGetValue(field, out var raw) || raw is not decimal price || price <= 0m)
return;
var best = _favorableExtreme ?? entry;
_favorableExtreme = best = isLong ? Math.Max(best, price) : Math.Min(best, price);
var gain = isLong ? best - entry : entry - best;
if (gain >= BreakevenGain)
{
var candidate = isLong ? entry + Breakeven : entry - Breakeven;
TightenStop(isLong, candidate);
}
if (gain >= TrailingStart)
{
var candidate = isLong ? best - TrailingStop : best + TrailingStop;
TightenStop(isLong, candidate);
}
var hitStop = _stopPrice is decimal stop && (isLong ? price <= stop : price >= stop);
var hitTake = _takePrice is decimal take && (isLong ? price >= take : price <= take);
if (!hitStop && !hitTake)
return;
var exit = new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = isLong ? Sides.Sell : Sides.Buy,
Volume = Math.Abs(Position),
Comment = hitStop ? "Lacust stop / breakeven / trailing" : "Lacust take profit",
};
_pendingOrder = exit;
RegisterOrder(exit);
}
private void TightenStop(bool isLong, decimal candidate)
=> _stopPrice = _stopPrice is decimal old
? isLong ? Math.Max(old, candidate) : Math.Min(old, candidate)
: candidate;
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Level1Fields, OrderTypes, Sides, OrderStates
from StockSharp.BusinessEntities import Order, Subscription
from StockSharp.Algo.Strategies import Strategy
class lacust_stop_and_be_strategy(Strategy):
"""Filled-price SL/TP, breakeven and monotonic trailing on executable bid/ask."""
def __init__(self):
super(lacust_stop_and_be_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay(
"Candle Type", "Signal candles", "General")
self._stop_loss = self.Param("StopLoss", 400.0).SetNotNegative().SetDisplay(
"Stop Loss", "Initial distance in price units", "Risk")
self._take_profit = self.Param("TakeProfit", 2000.0).SetNotNegative().SetDisplay(
"Take Profit", "Target distance in price units", "Risk")
self._trailing_start = self.Param("TrailingStart", 300.0).SetNotNegative().SetDisplay(
"Trailing Start", "Profit at which trailing starts", "Risk")
self._trailing_stop = self.Param("TrailingStop", 200.0).SetGreaterThanZero().SetDisplay(
"Trailing Stop", "Trailing distance from favorable quote", "Risk")
self._breakeven_gain = self.Param("BreakevenGain", 250.0).SetNotNegative().SetDisplay(
"Breakeven Gain", "Profit at which breakeven activates", "Risk")
self._breakeven = self.Param("Breakeven", 100.0).SetNotNegative().SetDisplay(
"Breakeven", "Profit locked after breakeven", "Risk")
self._entry_price = None
self._stop_price = None
self._take_price = None
self._extreme = None
self._tracked_volume = Decimal(0)
self._pending_order = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _clear_tracking(self):
self._entry_price = self._stop_price = self._take_price = self._extreme = None
self._tracked_volume = Decimal(0)
def OnReseted(self):
super(lacust_stop_and_be_strategy, self).OnReseted()
self._clear_tracking()
self._pending_order = None
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, DataType.Level1)]
def OnStarted2(self, time):
super(lacust_stop_and_be_strategy, self).OnStarted2(time)
self._clear_tracking()
self._pending_order = None
self.SubscribeCandles(self.CandleType).Bind(self.ProcessCandle).Start()
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
subscription = Subscription(DataType.Level1, self.Security)
subscription.MarketData.BuildField = field
self.SubscribeLevel1(subscription).Bind(self.ProcessQuote).Start()
def OnNewPosition(self, position):
super(lacust_stop_and_be_strategy, self).OnNewPosition(position)
self._observe_position(position)
def OnPositionChanged(self, position):
super(lacust_stop_and_be_strategy, self).OnPositionChanged(position)
self._observe_position(position)
def OnPositionReceived(self, position):
super(lacust_stop_and_be_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 = position.CurrentValue if position.CurrentValue is not None else Decimal(0)
if volume == 0:
self._clear_tracking()
return
average = position.AveragePrice
if average is None or average <= 0:
return
if self._entry_price == average and self._tracked_volume == volume:
return
if (self._entry_price == average and self._tracked_volume * volume > 0
and abs(volume) < abs(self._tracked_volume)):
self._tracked_volume = volume
return
self._entry_price = average
self._tracked_volume = volume
self._extreme = average
stop = Decimal(self._stop_loss.Value)
take = Decimal(self._take_profit.Value)
self._stop_price = (average - stop if volume > 0 else average + stop) if stop > 0 else None
self._take_price = (average + take if volume > 0 else average - take) if take > 0 else None
def _pending(self):
return self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished or self.Position != 0:
return
if not self.IsFormedAndOnlineAndAllowTrading() or self._pending():
return
if candle.ClosePrice > candle.OpenPrice:
self._pending_order = self.BuyMarket(self.Volume)
elif candle.ClosePrice < candle.OpenPrice:
self._pending_order = self.SellMarket(self.Volume)
def _tighten_stop(self, is_long, candidate):
if self._stop_price is None:
self._stop_price = candidate
elif is_long:
self._stop_price = max(self._stop_price, candidate)
else:
self._stop_price = min(self._stop_price, candidate)
def ProcessQuote(self, quote):
if self.Position == 0 or self._entry_price is None:
return
if not self.IsFormedAndOnlineAndAllowTrading() or self._pending():
return
is_long = self.Position > 0
field = Level1Fields.BestBidPrice if is_long else Level1Fields.BestAskPrice
if not quote.Changes.ContainsKey(field):
return
price = Decimal(quote.Changes[field])
if price <= 0:
return
entry = self._entry_price
best = self._extreme if self._extreme is not None else entry
best = max(best, price) if is_long else min(best, price)
self._extreme = best
gain = (best - entry) if is_long else (entry - best)
if gain >= Decimal(self._breakeven_gain.Value):
candidate = entry + Decimal(self._breakeven.Value) if is_long else entry - Decimal(self._breakeven.Value)
self._tighten_stop(is_long, candidate)
if gain >= Decimal(self._trailing_start.Value):
trailing = Decimal(self._trailing_stop.Value)
self._tighten_stop(is_long, best - trailing if is_long else best + trailing)
hit_stop = self._stop_price is not None and (price <= self._stop_price if is_long else price >= self._stop_price)
hit_take = self._take_price is not None and (price >= self._take_price if is_long else price <= self._take_price)
if not (hit_stop or hit_take):
return
exit_order = Order()
exit_order.Security = self.Security
exit_order.Portfolio = self.Portfolio
exit_order.Type = OrderTypes.Market
exit_order.Side = Sides.Sell if is_long else Sides.Buy
exit_order.Volume = abs(self.Position)
exit_order.Comment = "Lacust stop / breakeven / trailing" if hit_stop else "Lacust take profit"
self._pending_order = exit_order
self.RegisterOrder(exit_order)
def CreateClone(self):
return lacust_stop_and_be_strategy()