固定保证金资金管理策略
该策略将 MetaTrader 中的 “Money Fixed Margin” 示例移植到 StockSharp 高级 API。它演示如何按照账户权益的固定百分比来确定持仓手数,并把以点数表示的止损距离换算成绝对价格偏移。策略仅做多,重点在于阐释资金管理流程,而不是预测买卖信号。
细节
- 入场条件:
- 做多:每当已完成的K线数量达到
Check Interval(默认每 980 根K线)时,按市价买入。止损计算以触发K线的收盘价为基准。
- 做多:每当已完成的K线数量达到
- 多空方向:仅做多。
- 离场条件:
- 通过
StartProtection自动附加止损,距离来源于Stop Loss (pips)参数。 - 不设置止盈;仓位仅由止损或人工干预退出。
- 通过
- 止损类型:只有止损。
- 默认参数:
Stop Loss (pips)= 25Risk Percent= 10Check Interval= 980Candle Type= 1 分钟周期
- 筛选标签:
- 类别:风险管理
- 方向:多头
- 指标:无
- 止损:是(止损单)
- 复杂度:基础
- 周期:日内(可通过
Candle Type配置) - 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等(取决于
Risk Percent)
仓位计算逻辑
- 读取
Security.PriceStep与Security.Decimals来判断点值。对于 3 或 5 位小数的品种,乘以 10 以匹配 MetaTrader 对点的定义。 - 将
Stop Loss (pips)与点值相乘得到绝对止损距离 (ExtStopLoss),与 MQL5 代码保持一致。 - 使用
Portfolio.CurrentValue(若不可用则使用Portfolio.BeginValue)乘以Risk Percent / 100计算每笔交易的风险金额。 - 通过止损距离、对应的价格步数以及
Security.StepPrice(若可用)求出 1 手的风险金额;若缺少StepPrice则退化为直接使用价格距离。 - 用风险金额除以单手风险得到目标手数,再根据
VolumeStep进行归一化,并限制在最小/最大手数之间,同时写入日志。另会记录在无止损情况下的计算结果,以说明没有保护性止损时资金管理会拒绝交易。
工作流程
- 启动时订阅设定的K线序列,计算点值,并使用绝对止损距离调用
StartProtection。 - 每根收盘的K线都会累加内部计数器;当计数达到
Check Interval时,策略计算仓位规模、输出诊断信息并重置计数器。 - 若得到的手数大于零,则按市价买入。保护机制会把止损放在
Close - ExtStopLoss。若出现价格为零或缺少元数据等问题,则不会下单。 - 随后策略等待下一轮计数周期,强调资金管理而非信号频率。
使用提示
- 连接真实账户时请将
Risk Percent调整到保守水平;默认的 10% 仅用于复现原始示例,实际交易中风险较高。 - 请确认标的提供有效的
PriceStep和StepPrice。若缺失这些信息,策略会以价格单位估算风险,精度会降低。 - 策略故意不做空,以保持与原示例一致;如需双向交易,可自行扩展
BuyMarket/SellMarket调用。 - 资金管理逻辑可以复用:将策略代码中的仓位计算方法集成到其他信号模块即可。
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Recreates the MetaTrader Money Fixed Margin sample using StockSharp.
/// It demonstrates fixed percentage risk sizing for long trades.
/// </summary>
public class MoneyFixedMarginStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _checkInterval;
private readonly StrategyParam<DataType> _candleType;
private int _barCount;
private decimal _pipSize;
/// <summary>
/// Stop-loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Portfolio percentage risked on each trade.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Number of finished candles between trade attempts.
/// </summary>
public int CheckInterval
{
get => _checkInterval.Value;
set => _checkInterval.Value = value;
}
/// <summary>
/// Candle series used to time entries.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MoneyFixedMarginStrategy"/>.
/// </summary>
public MoneyFixedMarginStrategy()
{
_stopLossPips = Param(nameof(StopLossPips), 25m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Stop loss distance in pips", "Risk");
_riskPercent = Param(nameof(RiskPercent), 10m)
.SetGreaterThanZero()
.SetDisplay("Risk Percent", "Percent of equity risked per trade", "Risk");
_checkInterval = Param(nameof(CheckInterval), 150)
.SetGreaterThanZero()
.SetDisplay("Check Interval", "Completed candles between trades", "Execution");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_barCount = 0;
_pipSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var priceStep = Security?.PriceStep ?? 1m;
var decimals = Security?.Decimals ?? 0;
var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;
_pipSize = priceStep * adjust;
if (_pipSize <= 0m)
_pipSize = priceStep > 0m ? priceStep : 1m;
// Attach a protective stop using the pip-based distance converted to price units.
StartProtection(
new Unit(StopLossPips * _pipSize, UnitTypes.Absolute),
new Unit(StopLossPips * _pipSize * 2, UnitTypes.Absolute));
// Subscribe to the candle stream that emulates the tick counter from the MQL example.
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Count finished candles to mirror the tick counter from the original script.
_barCount++;
if (_barCount < CheckInterval)
return;
var entryPrice = candle.ClosePrice;
if (entryPrice <= 0m)
{
LogWarning("Skip trade because entry price is not positive. Close={0}", entryPrice);
return;
}
var riskAmount = CalculateRiskAmount();
if (riskAmount <= 0m)
{
LogWarning("Skip trade because risk amount is not positive. Portfolio value={0}", riskAmount);
return;
}
var stopDistance = StopLossPips * _pipSize;
var stopPrice = entryPrice - stopDistance;
var volumeWithoutStop = CalculateFixedMarginVolume(entryPrice, 0m, riskAmount);
var volumeWithStop = CalculateFixedMarginVolume(entryPrice, stopPrice, riskAmount);
this.LogInfo(
"StopLoss=0 -> volume {0:0.####}; StopLoss={1:0.#####} -> volume {2:0.####}; Portfolio={3:0.##}",
volumeWithoutStop,
stopPrice,
volumeWithStop,
GetPortfolioValue());
BuyMarket();
// Reset the counter only after successfully sending an order.
_barCount = 0;
}
private decimal CalculateRiskAmount()
{
var portfolioValue = GetPortfolioValue();
return portfolioValue > 0m ? portfolioValue * RiskPercent / 100m : 0m;
}
private decimal GetPortfolioValue()
{
var current = Portfolio?.CurrentValue ?? 0m;
if (current > 0m)
return current;
var begin = Portfolio?.BeginValue ?? 0m;
return begin > 0m ? begin : current;
}
private decimal CalculateFixedMarginVolume(decimal entryPrice, decimal stopPrice, decimal riskAmount)
{
if (riskAmount <= 0m || entryPrice <= 0m || stopPrice <= 0m)
return 0m;
var stopDistance = entryPrice - stopPrice;
if (stopDistance <= 0m)
return 0m;
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
priceStep = 1m;
var stepPrice = 0m;
if (stepPrice <= 0m)
stepPrice = priceStep;
var stepsCount = stopDistance / priceStep;
if (stepsCount <= 0m)
return 0m;
var riskPerVolume = stepsCount * stepPrice;
if (riskPerVolume <= 0m)
return 0m;
var rawVolume = riskAmount / riskPerVolume;
return NormalizeVolume(rawVolume);
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
if (Security?.VolumeStep is decimal step && step > 0m)
{
volume = Math.Ceiling(volume / step) * step;
}
return volume;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class money_fixed_margin_strategy(Strategy):
def __init__(self):
super(money_fixed_margin_strategy, self).__init__()
self._stop_loss_pips = self.Param("StopLossPips", 25.0)
self._risk_percent = self.Param("RiskPercent", 10.0)
self._check_interval = self.Param("CheckInterval", 150)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._bar_count = 0
self._pip_size = 1.0
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def RiskPercent(self):
return self._risk_percent.Value
@RiskPercent.setter
def RiskPercent(self, value):
self._risk_percent.Value = value
@property
def CheckInterval(self):
return self._check_interval.Value
@CheckInterval.setter
def CheckInterval(self, value):
self._check_interval.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(money_fixed_margin_strategy, self).OnStarted2(time)
self._bar_count = 0
sec = self.Security
price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if price_step <= 0.0:
price_step = 1.0
decimals = int(sec.Decimals) if sec is not None and sec.Decimals is not None else 0
adjust = 10.0 if decimals == 3 or decimals == 5 else 1.0
self._pip_size = price_step * adjust
if self._pip_size <= 0.0:
self._pip_size = price_step if price_step > 0.0 else 1.0
sl_distance = float(self.StopLossPips) * self._pip_size
self.StartProtection(
Unit(sl_distance, UnitTypes.Absolute),
Unit(sl_distance * 2.0, UnitTypes.Absolute))
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self._bar_count += 1
if self._bar_count < int(self.CheckInterval):
return
entry_price = float(candle.ClosePrice)
if entry_price <= 0.0:
return
self.BuyMarket()
self._bar_count = 0
def OnReseted(self):
super(money_fixed_margin_strategy, self).OnReseted()
self._bar_count = 0
self._pip_size = 1.0
def CreateClone(self):
return money_fixed_margin_strategy()