Super Take戦略
この戦略はロングとショートのポジションを交互に切り替え、負け取引のたびにマーチンゲール乗数を使ってテイクプロフィットを増加させます。ストップロスは固定で、テイクプロフィットは勝ち取引の後に基本値にリセットされます。常に方向を切り替えて損失後の目標を調整することで、この戦略は以前のドローダウンを回復しようとします。
新しいポジションは、アクティブなポジションがない場合にのみ開かれます。最初の取引はデフォルトでロングです。それ以降の各取引は、最後に決済されたポジションの反対方向で開かれます。
詳細
- エントリー条件:
- ロング: アクティブなポジションなし、かつ最後に決済されたポジションがショートまたは存在しない。
- ショート: アクティブなポジションなし、かつ最後に決済されたポジションがロング。
- ロング/ショート: 両方。
- エグジット条件:
- 価格が動的テイクプロフィットまたは固定ストップロスに達したときにポジションを決済。
- ストップ: 固定ストップロス、負け取引後のマーチンゲールによる動的テイクプロフィット。
- デフォルト値:
TakeProfit= 10StopLoss= 15MartinFactor= 1.8
- フィルター:
- カテゴリ: リバーサル
- 方向: 両方
- インジケーター: なし
- ストップ: はい
- 複雑さ: シンプル
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 高
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Alternates between long and short entries after actual fills, with a
/// fill-anchored stop and a target increased after a realized losing trade.
/// Entry scheduling uses finished candles; protective exits use fresh bid/ask quotes.
/// </summary>
public class SuperTakeStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _martinFactor;
private readonly StrategyParam<DataType> _candleType;
private Order _entryOrder;
private Order _exitOrder;
private Order _pendingOrder;
private bool _submittingEntry;
private bool _submittingExit;
private bool _entryLong;
private decimal _entryVolume;
private decimal _entryValue;
private decimal _remainingVolume;
private decimal _realizedPnl;
private decimal _currentTakeProfit;
private decimal _lastTakeProfitDistance;
private bool? _lastClosedWasBuy;
private bool _lastTradeWasLoss;
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal MartinFactor { get => _martinFactor.Value; set => _martinFactor.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public SuperTakeStrategy()
{
_takeProfit = Param(nameof(TakeProfit), 10m).SetGreaterThanZero()
.SetDisplay("Take Profit", "Base price-distance target", "Risk");
_stopLoss = Param(nameof(StopLoss), 15m).SetGreaterThanZero()
.SetDisplay("Stop Loss", "Price-distance stop", "Risk");
_martinFactor = Param(nameof(MartinFactor), 1.8m).SetGreaterThanZero()
.SetDisplay("Martingale Factor", "Take-profit distance multiplier after loss", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
// The registration event can precede a synchronous fill, in which case
// storing only BuyMarket/SellMarket's return value would miss the trade.
OrderRegistering += order =>
{
if (_submittingEntry) _entryOrder = order;
else if (_submittingExit) _exitOrder = order;
_pendingOrder = order;
};
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
private bool HasPendingOrder => _pendingOrder is not null &&
_pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
protected override void OnReseted()
{
base.OnReseted();
ResetLocalState();
}
private void ResetLocalState()
{
_entryOrder = _exitOrder = _pendingOrder = null;
_submittingEntry = _submittingExit = false;
_entryLong = true;
_entryVolume = _entryValue = _remainingVolume = _realizedPnl = 0m;
_currentTakeProfit = _lastTakeProfitDistance = 0m;
_lastClosedWasBuy = null;
_lastTradeWasLoss = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetLocalState();
_lastTakeProfitDistance = TakeProfit;
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ProcessQuote).Start();
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || Position != 0m ||
_remainingVolume != 0m || HasPendingOrder || !IsFormedAndOnlineAndAllowTrading())
return;
_entryLong = _lastClosedWasBuy is not true;
// Decimal arithmetic is finite; clamp an unreachable martingale target
// rather than crashing after repeated realized losses.
if (_lastTradeWasLoss)
{
var factor = MartinFactor;
_currentTakeProfit = factor > 1m &&
_lastTakeProfitDistance > decimal.MaxValue / factor
? decimal.MaxValue
: _lastTakeProfitDistance * factor;
}
else
_currentTakeProfit = TakeProfit;
_entryVolume = _entryValue = _realizedPnl = 0m;
_exitOrder = null;
_submittingEntry = true;
try
{
_entryOrder = _entryLong ? BuyMarket(Volume) : SellMarket(Volume);
_pendingOrder = _entryOrder;
}
finally
{
_submittingEntry = false;
}
}
/// <summary>Recognize executions only; a submitted order never creates a hypothetical fill.</summary>
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Order is null || trade.Trade is null)
return;
var volume = trade.Trade.Volume;
var price = trade.Trade.Price;
if (trade.Order == _entryOrder)
{
_entryVolume += volume;
_entryValue += volume * price;
_remainingVolume += volume;
}
else if (trade.Order == _exitOrder && _entryVolume > 0m)
{
var entry = _entryValue / _entryVolume;
_realizedPnl += (_entryLong ? price - entry : entry - price) * volume;
_remainingVolume = Math.Max(0m, _remainingVolume - volume);
if (_remainingVolume == 0m)
{
_lastClosedWasBuy = _entryLong;
_lastTradeWasLoss = _realizedPnl < 0m;
_lastTakeProfitDistance = _currentTakeProfit;
}
}
}
private void ProcessQuote(Level1ChangeMessage quote)
{
if (Position == 0m || _remainingVolume <= 0m || _entryVolume <= 0m ||
HasPendingOrder || !IsFormedAndOnlineAndAllowTrading())
return;
var field = Position > 0m ? Level1Fields.BestBidPrice : Level1Fields.BestAskPrice;
if (!quote.Changes.TryGetValue(field, out var raw) || raw is not decimal price || price <= 0m)
return;
var entry = _entryValue / _entryVolume;
var profit = Position > 0m ? price - entry : entry - price;
if (profit < _currentTakeProfit && profit > -StopLoss)
return;
_submittingExit = true;
try
{
_exitOrder = Position > 0m ? SellMarket(Math.Abs(Position)) : BuyMarket(Math.Abs(Position));
_pendingOrder = _exitOrder;
}
finally
{
_submittingExit = false;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Convert, Decimal
from StockSharp.Messages import DataType, CandleStates, OrderStates, Level1Fields, Sides
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class super_take_strategy(Strategy):
"""Alternating trades with fill-based Labouchere-free take-profit martingale.
Candle closes schedule entries ONLY. Protection triggers on fresh executable
bid/ask quotes after a real fill; pending or partially filled orders do not
create imaginary closes or permit duplicate entries/exits.
"""
def __init__(self):
super(super_take_strategy, self).__init__()
self._take_profit = self.Param("TakeProfit", 10.0).SetDisplay(
"Take Profit", "Base price-distance target", "Risk")
self._stop_loss = self.Param("StopLoss", 15.0).SetDisplay(
"Stop Loss", "Price-distance stop", "Risk")
self._martin_factor = self.Param("MartinFactor", 1.8).SetDisplay(
"Martingale Factor", "Take-profit distance multiplier after loss", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay(
"Candle Type", "Type of candles", "General")
self._reset_state()
self.OrderRegistering += self._track_order
def _track_order(self, order):
if self._submitting_entry:
self._entry_order = order
elif self._submitting_exit:
self._exit_order = order
self._pending_order = order
@property
def TakeProfit(self): return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value): self._take_profit.Value = value
@property
def StopLoss(self): return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value): self._stop_loss.Value = value
@property
def MartinFactor(self): return self._martin_factor.Value
@MartinFactor.setter
def MartinFactor(self, value): self._martin_factor.Value = value
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def _reset_state(self):
zero = Convert.ToDecimal(0)
self._submitting_entry = False
self._submitting_exit = False
self._entry_order = None
self._exit_order = None
self._pending_order = None
self._entry_long = True
self._entry_volume = zero
self._entry_value = zero
self._remaining_volume = zero
self._realized_pnl = zero
self._current_take_profit = zero
self._last_take_profit_distance = zero
self._last_closed_was_buy = None
self._last_trade_was_loss = False
def OnReseted(self):
super(super_take_strategy, self).OnReseted()
self._reset_state()
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, DataType.Level1)]
def OnStarted2(self, time):
super(super_take_strategy, self).OnStarted2(time)
self._reset_state()
self._last_take_profit_distance = Convert.ToDecimal(self.TakeProfit)
self.SubscribeCandles(self.CandleType).Bind(self.ProcessCandle).Start()
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
sub = Subscription(DataType.Level1, self.Security)
sub.MarketData.BuildField = field
self.SubscribeLevel1(sub).Bind(self._process_quote).Start()
def _has_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 or
self._remaining_volume != 0 or self._has_pending() or
not self.IsFormedAndOnlineAndAllowTrading()):
return
self._entry_long = self._last_closed_was_buy is not True
base = Convert.ToDecimal(self.TakeProfit)
factor = Convert.ToDecimal(self.MartinFactor)
if self._last_trade_was_loss:
limit = Decimal.MaxValue / factor if factor > 1 else Decimal.MaxValue
self._current_take_profit = (Decimal.MaxValue if self._last_take_profit_distance > limit
else self._last_take_profit_distance * factor)
else:
self._current_take_profit = base
self._entry_volume = Convert.ToDecimal(0)
self._entry_value = Convert.ToDecimal(0)
self._realized_pnl = Convert.ToDecimal(0)
self._exit_order = None
self._submitting_entry = True
try:
self._entry_order = self.BuyMarket(self.Volume) if self._entry_long else self.SellMarket(self.Volume)
self._pending_order = self._entry_order
finally:
self._submitting_entry = False
def OnOwnTradeReceived(self, trade):
super(super_take_strategy, self).OnOwnTradeReceived(trade)
if trade.Order is None or trade.Trade is None:
return
volume = Convert.ToDecimal(trade.Trade.TradeVolume)
price = Convert.ToDecimal(trade.Trade.TradePrice)
if trade.Order == self._entry_order:
self._entry_volume += volume
self._entry_value += volume * price
self._remaining_volume += volume
elif trade.Order == self._exit_order and self._entry_volume > 0:
entry = self._entry_value / self._entry_volume
self._realized_pnl += (price - entry if self._entry_long else entry - price) * volume
self._remaining_volume = max(Convert.ToDecimal(0), self._remaining_volume - volume)
if self._remaining_volume == 0:
self._last_closed_was_buy = self._entry_long
self._last_trade_was_loss = self._realized_pnl < 0
self._last_take_profit_distance = self._current_take_profit
def _process_quote(self, quote):
if (self.Position == 0 or self._remaining_volume <= 0 or self._entry_volume <= 0 or
self._has_pending() or not self.IsFormedAndOnlineAndAllowTrading()):
return
field = Level1Fields.BestBidPrice if self.Position > 0 else Level1Fields.BestAskPrice
if not quote.Changes.ContainsKey(field):
return
price = Convert.ToDecimal(quote.Changes[field])
if price <= 0:
return
entry = self._entry_value / self._entry_volume
profit = price - entry if self.Position > 0 else entry - price
if profit >= self._current_take_profit or profit <= -Convert.ToDecimal(self.StopLoss):
volume = abs(self.Position)
self._submitting_exit = True
try:
self._exit_order = self.SellMarket(volume) if self.Position > 0 else self.BuyMarket(volume)
self._pending_order = self._exit_order
finally:
self._submitting_exit = False
def CreateClone(self):
return super_take_strategy()