按账户货币的盈利或亏损平仓
该策略移植自 MetaTrader 专家顾问 Close_on_PROFIT_or_LOSS_inAccont_Currency。它持续监控所连接投资组合的当前权益值,一旦达到设定的盈利目标或跌破亏损底线,就会取消所有挂单并平掉全部持仓。实现基于 StockSharp 的高级 API:蜡烛订阅提供“心跳”,CancelActiveOrders() 撤销挂单,而 ClosePosition() 通过市价单完成平仓。
工作流程
- 每当心跳蜡烛收盘时,读取
Portfolio.CurrentValue。 - 若权益值大于或等于 Positive Closure,触发完整的退出流程。
- 若权益值小于或等于 Negative Closure,执行同样的流程以限制亏损。
- 在退出过程中,策略会撤销挂单、提交反向市价单并停止自身运行(对应原版中的
ExpertRemove())。
注意: 阈值以账户货币表示。为了避免启动后立即触发,请将 Positive Closure 设置在当前权益之上,并将 Negative Closure 设置在其下方。
参数
| 名称 | 说明 | 默认值 |
|---|---|---|
PositiveClosureInAccountCurrency |
当权益达到或超过该值时立即平仓。 | 0 |
NegativeClosureInAccountCurrency |
当权益跌至该值或更低时立即平仓。 | 0 |
CandleType |
提供心跳的蜡烛周期。若需更快响应,可选择更短的时间框。 | 1 分钟 |
说明
- 启动时会调用
StartProtection(),以保持原策略的防护逻辑。 - 策略仅处理自身管理的仓位和订单,请将其附加到需要保护的投资组合。
- 未单独提供点差或滑点参数,因为 StockSharp 的市价单会根据连接器自动处理这些因素。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Closes all positions when portfolio equity reaches configured profit or loss thresholds.
/// Pending orders are cancelled before liquidating the exposure.
/// </summary>
public class CloseOnProfitOrLossInAccountCurrencyStrategy : Strategy
{
private readonly StrategyParam<decimal> _positiveClosure;
private readonly StrategyParam<decimal> _negativeClosure;
private readonly StrategyParam<DataType> _candleType;
private bool _closeRequested;
private SimpleMovingAverage _smaFast;
private SimpleMovingAverage _smaSlow;
/// <summary>
/// Equity level in account currency that triggers closing all positions when exceeded.
/// </summary>
public decimal PositiveClosureInAccountCurrency
{
get => _positiveClosure.Value;
set => _positiveClosure.Value = value;
}
/// <summary>
/// Equity level in account currency that triggers closing all positions when reached on drawdown.
/// </summary>
public decimal NegativeClosureInAccountCurrency
{
get => _negativeClosure.Value;
set => _negativeClosure.Value = value;
}
/// <summary>
/// Candle type used as a heartbeat to evaluate portfolio equity.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CloseOnProfitOrLossInAccountCurrencyStrategy"/> class.
/// </summary>
public CloseOnProfitOrLossInAccountCurrencyStrategy()
{
_positiveClosure = Param(nameof(PositiveClosureInAccountCurrency), 0m)
.SetDisplay("Positive Closure", "Equity level that triggers full liquidation", "Risk");
_negativeClosure = Param(nameof(NegativeClosureInAccountCurrency), 0m)
.SetDisplay("Negative Closure", "Equity floor that forces liquidation", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Heartbeat Candle", "Candle type that triggers equity checks", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security == null)
return Array.Empty<(Security, DataType)>();
return new (Security, DataType)[] { (Security, CandleType) };
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Portfolio == null)
throw new InvalidOperationException("Portfolio cannot be null.");
if (Security == null)
throw new InvalidOperationException("Security must be set to subscribe for candles.");
_smaFast = new SimpleMovingAverage { Length = 10 };
_smaSlow = new SimpleMovingAverage { Length = 30 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_smaFast, _smaSlow, ProcessCandleWithIndicators)
.Start();
}
private void ProcessCandleWithIndicators(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (fast > slow && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (fast < slow && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
private void RequestCloseAll(string reason)
{
if (_closeRequested)
return;
_closeRequested = true;
LogInfo(reason);
// Cancel any pending orders to avoid unexpected executions during liquidation.
CancelActiveOrders();
foreach (var position in Positions.ToArray())
{
var value = GetPositionValue(position.Security, Portfolio) ?? 0m;
if (value == 0m)
continue;
// Submit a market order opposite to the current exposure.
ClosePosition(position.Security);
}
// Stop the strategy after sending exit orders, mirroring ExpertRemove behavior.
Stop();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class close_on_profit_or_loss_in_account_currency_strategy(Strategy):
"""
SMA crossover strategy with equity-based closure.
Uses fast/slow SMA crossover for entries.
"""
def __init__(self):
super(close_on_profit_or_loss_in_account_currency_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Heartbeat Candle", "Candle type that triggers equity checks", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(close_on_profit_or_loss_in_account_currency_strategy, self).OnStarted2(time)
sma_fast = SimpleMovingAverage()
sma_fast.Length = 10
sma_slow = SimpleMovingAverage()
sma_slow.Length = 30
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma_fast, sma_slow, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma_fast)
self.DrawIndicator(area, sma_slow)
self.DrawOwnTrades(area)
def on_process(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return close_on_profit_or_loss_in_account_currency_strategy()