条件持仓开启策略
概述
条件持仓开启策略 复刻了原始的 MetaTrader 脚本“当没有持仓时开多单”的思路。策略的核心非常直接:当手动开关允许做多或做空时,只有在对应方向上没有敞口时才发送市价单,从而避免重复建仓,让仓位始终符合选择的方向。
StockSharp 版本依靠框架的高级蜡烛订阅以及内置的风控助手实现,经纪商无关。止损与止盈距离以点值(价格步长)表示,因此可以适配任意品种。
策略逻辑
- 订阅配置好的蜡烛序列,作为周期性触发心跳。
- 在每根完成的蜡烛上检查当前净头寸。
- 如果多头开关开启且仓位为空或净空,则发送买入市价单。
- 如果空头开关开启且仓位为空或净多,则发送卖出市价单。
- 通过
StartProtection自动处理保护性订单,将点值距离转换为实际价格偏移。
由于 StockSharp 使用净持仓模型,同时开启两个方向时会先尝试建立多头,再在成交后若仍为空仓时尝试建立空头,这与原始脚本避免每个方向重复下单的意图一致。
参数
| 名称 | 默认值 | 说明 |
|---|---|---|
Volume |
1 |
每次入场的下单数量。 |
StopLossPips |
100 |
以价格步长表示的止损距离,设为 0 可关闭。 |
TakeProfitPips |
200 |
以价格步长表示的止盈距离,设为 0 可关闭。 |
EnableBuy |
false |
当为 true 时,若没有多头敞口可开多单。 |
EnableSell |
false |
当为 true 时,若没有空头敞口可开空单。 |
CandleType |
1 分钟周期 |
用于驱动周期性检查的蜡烛序列。 |
备注
- 距离值会使用合约的
PriceStep转换为实际价格偏移;若交易所未提供步长,则直接使用原始点值作为绝对距离。 StartProtection会在成交后自动附加止损与止盈,无需额外的手工订单管理。- 策略专注于参数化的手动触发流程,可作为主观交易模板使用。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Conditional Position Opener" MetaTrader expert.
/// Uses Momentum indicator to conditionally open long or short positions.
/// Opens long when momentum is positive, short when negative.
/// </summary>
public class ConditionalPositionOpenerStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _momentumPeriod;
private Momentum _momentum;
private decimal? _prevMomentum;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MomentumPeriod
{
get => _momentumPeriod.Value;
set => _momentumPeriod.Value = value;
}
public ConditionalPositionOpenerStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal generation", "General");
_momentumPeriod = Param(nameof(MomentumPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Momentum Period", "Momentum indicator period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMomentum = null;
_momentum = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_momentum, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal momentumValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_momentum.IsFormed)
{
_prevMomentum = momentumValue;
return;
}
if (_prevMomentum is null)
{
_prevMomentum = momentumValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Cross above 101 (positive momentum)
var crossUp = _prevMomentum.Value <= 101m && momentumValue > 101m;
// Cross below 99 (negative momentum)
var crossDown = _prevMomentum.Value >= 99m && momentumValue < 99m;
if (crossUp)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossDown)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevMomentum = momentumValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_momentum = null;
_prevMomentum = null;
base.OnReseted();
}
}
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 Momentum
from StockSharp.Algo.Strategies import Strategy
class conditional_position_opener_strategy(Strategy):
def __init__(self):
super(conditional_position_opener_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._momentum_period = self.Param("MomentumPeriod", 20)
self._prev_momentum = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MomentumPeriod(self):
return self._momentum_period.Value
@MomentumPeriod.setter
def MomentumPeriod(self, value):
self._momentum_period.Value = value
def OnReseted(self):
super(conditional_position_opener_strategy, self).OnReseted()
self._prev_momentum = None
def OnStarted2(self, time):
super(conditional_position_opener_strategy, self).OnStarted2(time)
self._prev_momentum = None
momentum = Momentum()
momentum.Length = self.MomentumPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(momentum, self._process_candle).Start()
def _process_candle(self, candle, momentum_value):
if candle.State != CandleStates.Finished:
return
mom_val = float(momentum_value)
if self._prev_momentum is None:
self._prev_momentum = mom_val
return
# Cross above 101 (positive momentum)
cross_up = self._prev_momentum <= 101.0 and mom_val > 101.0
# Cross below 99 (negative momentum)
cross_down = self._prev_momentum >= 99.0 and mom_val < 99.0
if cross_up:
if self.Position <= 0:
self.BuyMarket()
elif cross_down:
if self.Position >= 0:
self.SellMarket()
self._prev_momentum = mom_val
def CreateClone(self):
return conditional_position_opener_strategy()