在 GitHub 上查看
Cryptos 策略
概述
Cryptos Strategy 是 MetaTrader4 专家顾问 cryptos.mq4 的 StockSharp 版本。策略主要面向 ETH/USD,通过布林带与线性加权移动平均线(LWMA)的组合,在波动收缩后捕捉突破行情。算法会在可配置的蜡烛数量内追踪摆动高点与低点,并将得到的范围乘以系数来生成收益目标。
交易逻辑
- 趋势识别:当收盘价触及上轨时,策略进入做空偏好;当收盘价触及下轨时,策略切换为做多偏好,同时冻结当前的摆动高低点,停止自动更新。
- 入场条件:
- 当收盘价跌破 LWMA、偏好为空且当前没有空头仓位时开空。
- 当收盘价升破 LWMA、偏好为多且当前没有多头仓位时开多。
- 区间投射:使用自动或手动锁定的摆动高/低点与 LWMA 之间的距离(以跳动点计算),并乘以 take-profit 系数来确定利润目标与风险基础上的仓位规模。
- 风险控制:每笔交易都会设置止损与止盈。多头止损放在摆动低点下方,空头止损放在摆动高点上方。参数在每次入场时重新计算,并在主循环中强制执行。
- 跟踪退出:如果多头收盘价跌破下轨(或空头收盘价升破上轨),仓位立即平仓,以复制原始 EA 的保护逻辑。
参数
| 参数 |
说明 |
CandleType |
所有指标使用的蜡烛数据类型。 |
BollingerPeriod, BollingerWidth |
布林带的周期与标准差倍数。 |
MaPeriod |
基于中位价的线性加权均线周期。 |
LookbackCandles |
自动搜寻摆动高/低点的蜡烛数量。 |
TakeProfitRatio |
交易 ETH/USD 时用于目标价的区间倍数。 |
AlternativeTakeProfitRatio |
其他品种使用的区间倍数。 |
RiskPerTrade |
每笔交易计划承担的风险金额(报价货币)。 |
ValueIndex, CryptoValueIndex |
将风险金额转换为仓位体量的乘数,分别用于普通品种和加密货币。 |
MinVolume, MaxVolume |
在对齐交易量步长后允许的最小/最大仓位。 |
MinRangeTicks |
允许的最小区间(以跳动点计),避免得到零距离的保护位。 |
SpreadPoints |
以跳动点计的手动价差;若有最优买卖价则自动推算。 |
GlobalTrend |
手动方向:1 强制空头,2 强制多头,0 则自动判断。 |
AutoHighLow |
启用时每根蜡烛都会更新摆动点;禁用后维持当前值直到新的布林带触发。 |
ManualBuyTrigger, ManualSellTrigger |
设为 true 可立即触发多头或空头入场(执行后自动复位)。 |
SkipBuys, SkipSells |
分别禁止新建多单或空单。 |
仓位计算
仓位遵循 MT4 公式:volume = RiskPerTrade / rangeTicks * valueIndex。结果会按照 VolumeStep 对齐,然后限制在 MinVolume/MaxVolume 以及交易所要求的范围内。
使用提示
- 启动时策略会检查组合资金。如果余额低于
RiskPerTrade * 3,交易将被禁用,并记录一条警告,与原始 EA 的安全检查保持一致。
- 手动触发与偏好控制可让交易者在实盘中与主观决策保持同步。
- 对 ETH/USD 自动使用
CryptoValueIndex 与 TakeProfitRatio;其他品种会切换到备用参数。
- 止损与止盈在策略内部监控,无需额外的保护模块。
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 "Cryptos" MetaTrader expert.
/// Uses Bollinger Bands with a WMA trend filter. Buys when price is below WMA
/// and touches lower band; sells when price is above WMA and touches upper band.
/// </summary>
public class CryptosStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _wmaPeriod;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private ExponentialMovingAverage _bandEma;
private ExponentialMovingAverage _trendEma;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int WmaPeriod
{
get => _wmaPeriod.Value;
set => _wmaPeriod.Value = value;
}
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
public CryptosStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signals", "General");
_wmaPeriod = Param(nameof(WmaPeriod), 55)
.SetGreaterThanZero()
.SetDisplay("WMA Period", "Weighted moving average period", "Indicators");
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 2m)
.SetGreaterThanZero()
.SetDisplay("BB Width", "Bollinger Bands deviation", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bandEma = new ExponentialMovingAverage { Length = BollingerPeriod };
_trendEma = new ExponentialMovingAverage { Length = WmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_bandEma, _trendEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bandEma);
DrawIndicator(area, _trendEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal bandValue, decimal trendValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_bandEma.IsFormed || !_trendEma.IsFormed)
return;
var close = candle.ClosePrice;
var bandOffset = bandValue * (BollingerWidth / 100m);
var upperBand = bandValue + bandOffset;
var lowerBand = bandValue - bandOffset;
var volume = Volume;
if (volume <= 0)
volume = 1;
// Buy: price below WMA, touches lower band
if (close < trendValue && close <= lowerBand)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Sell: price above WMA, touches upper band
else if (close > trendValue && close >= upperBand)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
// Exit at WMA cross
if (Position > 0 && close >= upperBand)
SellMarket(Position);
else if (Position < 0 && close <= lowerBand)
BuyMarket(Math.Abs(Position));
}
/// <inheritdoc />
protected override void OnReseted()
{
_bandEma = null;
_trendEma = 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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class cryptos_strategy(Strategy):
def __init__(self):
super(cryptos_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._wma_period = self.Param("WmaPeriod", 55)
self._bollinger_period = self.Param("BollingerPeriod", 20)
self._bollinger_width = self.Param("BollingerWidth", 2.0)
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def WmaPeriod(self):
return self._wma_period.Value
@WmaPeriod.setter
def WmaPeriod(self, value):
self._wma_period.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@BollingerPeriod.setter
def BollingerPeriod(self, value):
self._bollinger_period.Value = value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
@BollingerWidth.setter
def BollingerWidth(self, value):
self._bollinger_width.Value = value
def OnReseted(self):
super(cryptos_strategy, self).OnReseted()
def OnStarted2(self, time):
super(cryptos_strategy, self).OnStarted2(time)
band_ema = ExponentialMovingAverage()
band_ema.Length = self.BollingerPeriod
trend_ema = ExponentialMovingAverage()
trend_ema.Length = self.WmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(band_ema, trend_ema, self._process_candle).Start()
def _process_candle(self, candle, band_value, trend_value):
if candle.State != CandleStates.Finished:
return
band_val = float(band_value)
trend_val = float(trend_value)
close = float(candle.ClosePrice)
band_offset = band_val * (float(self.BollingerWidth) / 100.0)
upper_band = band_val + band_offset
lower_band = band_val - band_offset
# Buy: price below WMA, touches lower band
if close < trend_val and close <= lower_band:
if self.Position <= 0:
self.BuyMarket()
# Sell: price above WMA, touches upper band
elif close > trend_val and close >= upper_band:
if self.Position >= 0:
self.SellMarket()
# Exit at opposite band
if self.Position > 0 and close >= upper_band:
self.SellMarket()
elif self.Position < 0 and close <= lower_band:
self.BuyMarket()
def CreateClone(self):
return cryptos_strategy()