Crypto Analysis戦略
概要
この戦略は、MetaTrader 4のエキスパートアドバイザー「Crypto Analysis」をStockSharpへ移植したものです。主取引時間軸で価格が外側のボリンジャーバンドに触れた後、マーケット構造が弱気(高速LWMAが低速LWMAを下回る)である状況のブレイクアウトを探します。上位時間軸のモメンタム急増と、さらに上位の時間軸のMACDフィルターが望む方向で一致した場合にのみ取引を許可します。市場に入った後は、元のEAを反映する階層的な保護ブロックでポジションを管理します。pipベースのストップ、金額ベースのトレーリング、ブレイクイーブン移動、ポートフォリオのドローダウン管理を含みます。
取引ロジック
- シグナル時間軸: 設定可能(既定はM15)。すべてのエントリー/決済ルールはこのローソク足で評価されます。
- ボラティリティトリガー: ロング準備では直前ローソク足の安値が下側ボリンジャーバンド(20, 2)に触れるか突き抜ける必要があります。上側バンドへの接触はショート準備になります。
- トレンドフィルター: どちらのシナリオでも、高速線形加重移動平均(LWMA、既定6)が低速LWMA(既定85)より下にある必要があり、EAの弱気バイアス確認を再現します。
- RSI確認: RSI(14)はロングで50より上、ショートで50より下である必要があります。
- モメンタム急増: 上位時間軸の直近3つのMomentum(14)値について、100の基準線からの最大絶対偏差が買い/売りしきい値を超える必要があります。これはMQLコードで使われるモメンタムスパイクを捉えます。
- 上位時間軸MACDフィルター: 上位時間軸のローソク足(既定はH4)で計算する別のMACD(12, 26, 9)が方向を確認します。ロングではMACDメインがシグナルを上回り、ショートではその逆を要求します。
- エントリー実行: すべてのフィルターが一致すると、戦略は成行注文を開きます。反転前に反対ポジションを解消し、単一のネットポジションを保ちます。これはEAが反対取引を閉じる動作を反映します。
ポジション管理
- 初期ストップと目標: 設定可能なpip距離は、EAと同じ5桁/3桁の扱いで銘柄のティックサイズから変換されます(
0.00001と0.001のステップは10倍されます)。 - トレーリングストップ: 新しい高値(ロング)または安値(ショート)が形成された後、ストップは常に到達済みの最良水準を尊重しながら、価格から
TrailingStopPipsだけ後ろへ引き上げられます。 - ブレイクイーブン: 利益が
BreakEvenTriggerPipsに達すると、ストップはエントリー価格にBreakEvenOffsetPipsを加えた位置(ロング)または差し引いた位置(ショート)へ移動します。 - 金額目標: 任意の絶対額またはパーセントベースの利益上限により、浮動PnLが指定水準に達した時点でポジションを閉じます。
- 金額トレーリング: 含み益が
MoneyTrailTargetを超えると、戦略はピークを追跡し、戻り幅がMoneyTrailStop以上になった場合にポジションを閉じます。 - エクイティストップ: 浮動エクイティ(現在のポートフォリオ価値と未実現PnLの合計)を監視します。ピークからのドローダウンが
EquityRiskPercentを超えると、ポジションを解消します。
複数時間軸データ
3つの購読が自動登録されます。
- ボリンジャー/LWMA/RSIルール用の主ローソク足系列。
- モメンタムフィルター用の上位時間軸ローソク足(既定はH1)。
- MACD確認用の上位時間軸ローソク足(既定はH4)。
パラメーター
| パラメーター | 説明 |
|---|---|
OrderVolume |
基本注文サイズ。新しいポジションを開く前に反対ポジションを閉じます。 |
UseMoneyTakeProfit |
絶対金額のテイクプロフィット目標を有効にします。 |
MoneyTakeProfit |
UseMoneyTakeProfitがtrueの場合に決済を起動するポートフォリオ通貨での利益。 |
UsePercentTakeProfit |
初期エクイティから計算するパーセントベースのテイクプロフィット目標を有効にします。 |
PercentTakeProfit |
パーセント目標が有効なとき、ポジションを閉じるために必要な利益率。 |
EnableMoneyTrailing |
金額ベースのトレーリングブロックを有効にします。 |
MoneyTrailTarget |
金額トレーリングを開始する利益水準。 |
MoneyTrailStop |
MoneyTrailTarget到達後に許容する最大利益戻り幅。 |
StopLossPips |
初期ストップロス距離(pips)。 |
TakeProfitPips |
初期テイクプロフィット距離(pips)。 |
TrailingStopPips |
トレーリングストップ距離(pips)。 |
UseBreakEven |
ブレイクイーブンへのストップ移動を有効にします。 |
BreakEvenTriggerPips |
ブレイクイーブン保護を起動する前に必要なpip利益。 |
BreakEvenOffsetPips |
ブレイクイーブンストップ配置時にエントリー価格へ追加するpips。 |
FastMaPeriod |
典型価格で計算する高速LWMAの長さ。 |
SlowMaPeriod |
典型価格で計算する低速LWMAの長さ。 |
MomentumPeriod |
上位時間軸のMomentum指標の期間。 |
MomentumBuyThreshold |
ロングエントリーに必要な最小モメンタム偏差。 |
MomentumSellThreshold |
ショートエントリーに必要な最小モメンタム偏差。 |
MacdFastLength |
上位時間軸MACDフィルターの高速EMA長。 |
MacdSlowLength |
上位時間軸MACDフィルターの低速EMA長。 |
MacdSignalLength |
上位時間軸MACDフィルターのシグナル長。 |
UseEquityStop |
ポートフォリオのドローダウン保護を有効にします。 |
EquityRiskPercent |
ポジションを強制決済する前に許容するエクイティドローダウン率。 |
CandleType |
エントリーに使う主時間軸。 |
MomentumCandleType |
モメンタム確認に使う上位時間軸。 |
MacdCandleType |
MACD確認に使う上位時間軸。 |
注記
- StockSharp版は単一のネットポジションを維持し、新しい取引の前に反対注文を閉じるEAと一致します。
- すべての保護ルールは、元のスクリプトの「新しいバー」処理を再現するため、確定済みローソク足で動作します。
- 標準的なpipサイズを持たない合成シンボルや銘柄を使う場合は、
StopLossPipsおよび関連パラメーターを取引所のティック値に合わせて調整してください。
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Crypto Analysis: Bollinger/LWMA/RSI entry with higher-timeframe Momentum and MACD filters.
/// </summary>
public class CryptoAnalysisStrategy : Strategy
{
private const int _rsiLength = 14;
private const int _bandLength = 20;
private const decimal _bandWidth = 2m;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<bool> _useMoneyTakeProfit;
private readonly StrategyParam<decimal> _moneyTakeProfit;
private readonly StrategyParam<bool> _usePercentTakeProfit;
private readonly StrategyParam<decimal> _percentTakeProfit;
private readonly StrategyParam<bool> _enableMoneyTrailing;
private readonly StrategyParam<decimal> _moneyTrailTarget;
private readonly StrategyParam<decimal> _moneyTrailStop;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<bool> _useBreakEven;
private readonly StrategyParam<int> _breakEvenTriggerPips;
private readonly StrategyParam<int> _breakEvenOffsetPips;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<decimal> _momentumBuyThreshold;
private readonly StrategyParam<decimal> _momentumSellThreshold;
private readonly StrategyParam<int> _macdFastLength;
private readonly StrategyParam<int> _macdSlowLength;
private readonly StrategyParam<int> _macdSignalLength;
private readonly StrategyParam<bool> _useEquityStop;
private readonly StrategyParam<decimal> _equityRiskPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _momentumCandleType;
private readonly StrategyParam<DataType> _macdCandleType;
private readonly RelativeStrengthIndex _rsi = new() { Length = _rsiLength };
private readonly List<Bar> _primary = [];
private readonly List<decimal> _momentumCloses = [];
private readonly Queue<decimal> _momentumDeviations = new();
private decimal? _macdFast;
private decimal? _macdSlow;
private decimal? _macdSignal;
private bool _macdReady;
private int _macdCount;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _bestPrice;
private decimal _initialEquity;
private decimal _peakEquity;
private decimal? _moneyTrailPeak;
public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
public bool UseMoneyTakeProfit { get => _useMoneyTakeProfit.Value; set => _useMoneyTakeProfit.Value = value; }
public decimal MoneyTakeProfit { get => _moneyTakeProfit.Value; set => _moneyTakeProfit.Value = value; }
public bool UsePercentTakeProfit { get => _usePercentTakeProfit.Value; set => _usePercentTakeProfit.Value = value; }
public decimal PercentTakeProfit { get => _percentTakeProfit.Value; set => _percentTakeProfit.Value = value; }
public bool EnableMoneyTrailing { get => _enableMoneyTrailing.Value; set => _enableMoneyTrailing.Value = value; }
public decimal MoneyTrailTarget { get => _moneyTrailTarget.Value; set => _moneyTrailTarget.Value = value; }
public decimal MoneyTrailStop { get => _moneyTrailStop.Value; set => _moneyTrailStop.Value = value; }
public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
public int TrailingStopPips { get => _trailingStopPips.Value; set => _trailingStopPips.Value = value; }
public bool UseBreakEven { get => _useBreakEven.Value; set => _useBreakEven.Value = value; }
public int BreakEvenTriggerPips { get => _breakEvenTriggerPips.Value; set => _breakEvenTriggerPips.Value = value; }
public int BreakEvenOffsetPips { get => _breakEvenOffsetPips.Value; set => _breakEvenOffsetPips.Value = value; }
public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
public decimal MomentumBuyThreshold { get => _momentumBuyThreshold.Value; set => _momentumBuyThreshold.Value = value; }
public decimal MomentumSellThreshold { get => _momentumSellThreshold.Value; set => _momentumSellThreshold.Value = value; }
public int MacdFastLength { get => _macdFastLength.Value; set => _macdFastLength.Value = value; }
public int MacdSlowLength { get => _macdSlowLength.Value; set => _macdSlowLength.Value = value; }
public int MacdSignalLength { get => _macdSignalLength.Value; set => _macdSignalLength.Value = value; }
public bool UseEquityStop { get => _useEquityStop.Value; set => _useEquityStop.Value = value; }
public decimal EquityRiskPercent { get => _equityRiskPercent.Value; set => _equityRiskPercent.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DataType MomentumCandleType { get => _momentumCandleType.Value; set => _momentumCandleType.Value = value; }
public DataType MacdCandleType { get => _macdCandleType.Value; set => _macdCandleType.Value = value; }
public CryptoAnalysisStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m).SetGreaterThanZero();
_useMoneyTakeProfit = Param(nameof(UseMoneyTakeProfit), false);
_moneyTakeProfit = Param(nameof(MoneyTakeProfit), 100m).SetNotNegative();
_usePercentTakeProfit = Param(nameof(UsePercentTakeProfit), false);
_percentTakeProfit = Param(nameof(PercentTakeProfit), 1m).SetNotNegative();
_enableMoneyTrailing = Param(nameof(EnableMoneyTrailing), false);
_moneyTrailTarget = Param(nameof(MoneyTrailTarget), 50m).SetNotNegative();
_moneyTrailStop = Param(nameof(MoneyTrailStop), 20m).SetNotNegative();
_stopLossPips = Param(nameof(StopLossPips), 50).SetNotNegative();
_takeProfitPips = Param(nameof(TakeProfitPips), 100).SetNotNegative();
_trailingStopPips = Param(nameof(TrailingStopPips), 30).SetNotNegative();
_useBreakEven = Param(nameof(UseBreakEven), true);
_breakEvenTriggerPips = Param(nameof(BreakEvenTriggerPips), 30).SetNotNegative();
_breakEvenOffsetPips = Param(nameof(BreakEvenOffsetPips), 2).SetNotNegative();
_fastMaPeriod = Param(nameof(FastMaPeriod), 6).SetGreaterThanZero();
_slowMaPeriod = Param(nameof(SlowMaPeriod), 85).SetGreaterThanZero();
_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetGreaterThanZero();
_momentumBuyThreshold = Param(nameof(MomentumBuyThreshold), 0.3m).SetNotNegative();
_momentumSellThreshold = Param(nameof(MomentumSellThreshold), 0.3m).SetNotNegative();
_macdFastLength = Param(nameof(MacdFastLength), 12).SetGreaterThanZero();
_macdSlowLength = Param(nameof(MacdSlowLength), 26).SetGreaterThanZero();
_macdSignalLength = Param(nameof(MacdSignalLength), 9).SetGreaterThanZero();
_useEquityStop = Param(nameof(UseEquityStop), false);
_equityRiskPercent = Param(nameof(EquityRiskPercent), 10m).SetNotNegative();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame());
_momentumCandleType = Param(nameof(MomentumCandleType), TimeSpan.FromHours(1).TimeFrame());
_macdCandleType = Param(nameof(MacdCandleType), TimeSpan.FromHours(4).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, MomentumCandleType), (Security, MacdCandleType)];
protected override void OnReseted()
{
base.OnReseted();
_rsi.Reset();
_primary.Clear();
_momentumCloses.Clear();
_momentumDeviations.Clear();
_macdFast = null;
_macdSlow = null;
_macdSignal = null;
_macdReady = false;
_macdCount = 0;
ResetTradeState();
_initialEquity = 0m;
_peakEquity = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_initialEquity = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
_peakEquity = _initialEquity;
SubscribeCandles(MomentumCandleType).Bind(ProcessMomentum).Start();
SubscribeCandles(MacdCandleType).Bind(ProcessMacd).Start();
SubscribeCandles(CandleType).Bind(ProcessPrimary).Start();
}
private void ProcessMomentum(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_momentumCloses.Add(candle.ClosePrice);
var keep = MomentumPeriod + 5;
if (_momentumCloses.Count > keep)
_momentumCloses.RemoveRange(0, _momentumCloses.Count - keep);
if (_momentumCloses.Count <= MomentumPeriod)
return;
var previous = _momentumCloses[^(MomentumPeriod + 1)];
if (previous == 0m)
return;
var momentum = candle.ClosePrice / previous * 100m;
_momentumDeviations.Enqueue(Math.Abs(momentum - 100m));
while (_momentumDeviations.Count > 3)
_momentumDeviations.Dequeue();
}
private void ProcessMacd(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_macdCount++;
_macdFast = Ema(_macdFast, candle.ClosePrice, MacdFastLength);
_macdSlow = Ema(_macdSlow, candle.ClosePrice, MacdSlowLength);
var main = _macdFast.Value - _macdSlow.Value;
_macdSignal = Ema(_macdSignal, main, MacdSignalLength);
_macdReady = _macdCount >= MacdSlowLength + MacdSignalLength;
}
private void ProcessPrimary(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var required = Math.Max(Math.Max(FastMaPeriod, SlowMaPeriod), _bandLength + 1);
_primary.Add(new(candle.OpenPrice, candle.HighPrice, candle.LowPrice, candle.ClosePrice));
if (_primary.Count > required + 5)
_primary.RemoveRange(0, _primary.Count - required - 5);
var rsiValue = _rsi.Process(candle.ClosePrice, candle.ServerTime, true);
if (Position != 0 && ApplyRisk(candle))
return;
if (_primary.Count < required ||
!_rsi.IsFormed ||
_momentumDeviations.Count < 3 ||
!_macdReady ||
_macdSignal is null ||
_macdFast is null ||
_macdSlow is null)
return;
var previous = _primary[^2];
var (upper, lower) = BollingerAtPrevious();
var fast = Lwma(FastMaPeriod);
var slow = Lwma(SlowMaPeriod);
var rsi = rsiValue.ToDecimal();
var momentum = _momentumDeviations.Max();
var macdMain = _macdFast.Value - _macdSlow.Value;
var macdSignal = _macdSignal.Value;
var longSignal =
previous.Low <= lower &&
fast < slow &&
rsi > 50m &&
momentum > MomentumBuyThreshold &&
macdMain > macdSignal;
var shortSignal =
previous.High >= upper &&
fast < slow &&
rsi < 50m &&
momentum > MomentumSellThreshold &&
macdMain < macdSignal;
if (longSignal && Position <= 0m)
Enter(Sides.Buy, candle.ClosePrice);
else if (shortSignal && Position >= 0m)
Enter(Sides.Sell, candle.ClosePrice);
}
private void Enter(Sides side, decimal price)
{
var volume = OrderVolume + Math.Abs(Position);
if (side == Sides.Buy)
BuyMarket(volume);
else
SellMarket(volume);
_entryPrice = price;
var pip = GetPipSize();
_stopPrice = StopLossPips > 0
? side == Sides.Buy ? price - StopLossPips * pip : price + StopLossPips * pip
: null;
_takePrice = TakeProfitPips > 0
? side == Sides.Buy ? price + TakeProfitPips * pip : price - TakeProfitPips * pip
: null;
_bestPrice = price;
_moneyTrailPeak = null;
}
private bool ApplyRisk(ICandleMessage candle)
{
var floating = FloatingPnL(candle.ClosePrice);
var equity = (Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? _initialEquity) + floating;
_peakEquity = Math.Max(_peakEquity, equity);
// The levels are checked as they stood before this candle; moving them afterwards keeps
// a move made from this candle's high or low from being hit by the same candle.
if (IsLevelHit(candle))
return Flatten();
if (UseMoneyTakeProfit && floating >= MoneyTakeProfit)
return Flatten();
if (UsePercentTakeProfit && _initialEquity > 0m && floating >= _initialEquity * PercentTakeProfit / 100m)
return Flatten();
if (EnableMoneyTrailing)
{
// Once armed, the peak is tracked and the giveback checked even below the target.
if (_moneyTrailPeak is decimal peak)
_moneyTrailPeak = Math.Max(peak, floating);
else if (floating > MoneyTrailTarget)
_moneyTrailPeak = floating;
if (_moneyTrailPeak is decimal armedPeak && armedPeak - floating >= MoneyTrailStop)
return Flatten();
}
if (UseEquityStop && _peakEquity > 0m && (_peakEquity - equity) / _peakEquity * 100m > EquityRiskPercent)
return Flatten();
MoveLevels(candle);
return false;
}
private bool IsLevelHit(ICandleMessage candle)
{
if (Position > 0m)
{
return (_stopPrice is decimal longStop && candle.LowPrice <= longStop) ||
(_takePrice is decimal longTake && candle.HighPrice >= longTake);
}
return (_stopPrice is decimal shortStop && candle.HighPrice >= shortStop) ||
(_takePrice is decimal shortTake && candle.LowPrice <= shortTake);
}
private void MoveLevels(ICandleMessage candle)
{
var pip = GetPipSize();
if (Position > 0m)
{
if (UseBreakEven && candle.HighPrice - _entryPrice >= BreakEvenTriggerPips * pip)
RaiseStop(_entryPrice + BreakEvenOffsetPips * pip);
// The stop trails only a new high beyond the best price since the entry.
if (candle.HighPrice > _bestPrice)
{
_bestPrice = candle.HighPrice;
if (TrailingStopPips > 0)
RaiseStop(_bestPrice - TrailingStopPips * pip);
}
}
else
{
if (UseBreakEven && _entryPrice - candle.LowPrice >= BreakEvenTriggerPips * pip)
LowerStop(_entryPrice - BreakEvenOffsetPips * pip);
if (candle.LowPrice < _bestPrice)
{
_bestPrice = candle.LowPrice;
if (TrailingStopPips > 0)
LowerStop(_bestPrice + TrailingStopPips * pip);
}
}
}
private void RaiseStop(decimal level)
{
if (_stopPrice is not decimal stop || level > stop)
_stopPrice = level;
}
private void LowerStop(decimal level)
{
if (_stopPrice is not decimal stop || level < stop)
_stopPrice = level;
}
private bool Flatten()
{
if (Position > 0m)
SellMarket(Math.Abs(Position));
else if (Position < 0m)
BuyMarket(Math.Abs(Position));
else
return false;
ResetTradeState();
return true;
}
private decimal FloatingPnL(decimal price)
{
if (Position == 0m || _entryPrice == 0m)
return 0m;
var direction = Position > 0m ? 1m : -1m;
return (price - _entryPrice) * direction * Math.Abs(Position) * (Security?.Multiplier ?? 1m);
}
private (decimal Upper, decimal Lower) BollingerAtPrevious()
{
var end = _primary.Count - 1;
var total = 0m;
for (var i = end - _bandLength; i < end; i++)
total += _primary[i].Close;
var mean = total / _bandLength;
var squares = 0m;
for (var i = end - _bandLength; i < end; i++)
{
var deviation = _primary[i].Close - mean;
squares += deviation * deviation;
}
var std = (decimal)Math.Sqrt((double)(squares / _bandLength));
return (mean + _bandWidth * std, mean - _bandWidth * std);
}
private decimal Lwma(int period)
{
var start = _primary.Count - period;
var total = 0m;
var weights = 0m;
for (var i = 0; i < period; i++)
{
var weight = i + 1m;
total += _primary[start + i].Typical * weight;
weights += weight;
}
return total / weights;
}
private decimal GetPipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m) return 0.0001m;
return step is 0.00001m or 0.001m ? step * 10m : step;
}
private static decimal Ema(decimal? previous, decimal value, int period)
{
if (previous is null) return value;
var alpha = 2m / (period + 1m);
return previous.Value + alpha * (value - previous.Value);
}
private void ResetTradeState()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_bestPrice = 0m;
_moneyTrailPeak = null;
}
private readonly record struct Bar(decimal Open, decimal High, decimal Low, decimal Close)
{
public decimal Typical => (High + Low + Close) / 3m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class crypto_analysis_strategy(Strategy):
RSI_LENGTH = 14
BAND_LENGTH = 20
BAND_WIDTH = 2
def __init__(self):
super(crypto_analysis_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", Decimal(0.1)).SetGreaterThanZero()
self._use_money_tp = self.Param("UseMoneyTakeProfit", False)
self._money_tp = self.Param("MoneyTakeProfit", Decimal(100)).SetNotNegative()
self._use_percent_tp = self.Param("UsePercentTakeProfit", False)
self._percent_tp = self.Param("PercentTakeProfit", Decimal(1)).SetNotNegative()
self._enable_money_trailing = self.Param("EnableMoneyTrailing", False)
self._money_trail_target = self.Param("MoneyTrailTarget", Decimal(50)).SetNotNegative()
self._money_trail_stop = self.Param("MoneyTrailStop", Decimal(20)).SetNotNegative()
self._stop_loss = self.Param("StopLossPips", 50).SetNotNegative()
self._take_profit = self.Param("TakeProfitPips", 100).SetNotNegative()
self._trailing_stop = self.Param("TrailingStopPips", 30).SetNotNegative()
self._use_break_even = self.Param("UseBreakEven", True)
self._break_even_trigger = self.Param("BreakEvenTriggerPips", 30).SetNotNegative()
self._break_even_offset = self.Param("BreakEvenOffsetPips", 2).SetNotNegative()
self._fast_ma = self.Param("FastMaPeriod", 6).SetGreaterThanZero()
self._slow_ma = self.Param("SlowMaPeriod", 85).SetGreaterThanZero()
self._momentum_period = self.Param("MomentumPeriod", 14).SetGreaterThanZero()
self._momentum_buy = self.Param("MomentumBuyThreshold", Decimal(0.3)).SetNotNegative()
self._momentum_sell = self.Param("MomentumSellThreshold", Decimal(0.3)).SetNotNegative()
self._macd_fast_len = self.Param("MacdFastLength", 12).SetGreaterThanZero()
self._macd_slow_len = self.Param("MacdSlowLength", 26).SetGreaterThanZero()
self._macd_signal_len = self.Param("MacdSignalLength", 9).SetGreaterThanZero()
self._use_equity_stop = self.Param("UseEquityStop", False)
self._equity_risk = self.Param("EquityRiskPercent", Decimal(10)).SetNotNegative()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._momentum_type = self.Param("MomentumCandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._macd_type = self.Param("MacdCandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RSI_LENGTH
self._primary = []
self._momentum_closes = []
self._momentum_deviations = []
self._macd_fast = None
self._macd_slow = None
self._macd_signal = None
self._macd_ready = False
self._macd_count = 0
self._entry_price = Decimal.Zero
self._stop_price = None
self._take_price = None
self._best_price = Decimal.Zero
self._initial_equity = Decimal.Zero
self._peak_equity = Decimal.Zero
self._money_trail_peak = None
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value), (self.Security, self._momentum_type.Value), (self.Security, self._macd_type.Value)]
def OnReseted(self):
super(crypto_analysis_strategy, self).OnReseted()
self._rsi.Reset()
self._primary = []
self._momentum_closes = []
self._momentum_deviations = []
self._macd_fast = self._macd_slow = self._macd_signal = None
self._macd_ready = False
self._macd_count = 0
self._reset_trade()
self._initial_equity = Decimal.Zero
self._peak_equity = Decimal.Zero
def OnStarted2(self, time):
super(crypto_analysis_strategy, self).OnStarted2(time)
self._initial_equity = self._portfolio_value(Decimal.Zero)
self._peak_equity = self._initial_equity
self.SubscribeCandles(self._momentum_type.Value).Bind(self._process_momentum).Start()
self.SubscribeCandles(self._macd_type.Value).Bind(self._process_macd).Start()
self.SubscribeCandles(self._candle_type.Value).Bind(self._process_primary).Start()
def _process_momentum(self, candle):
if candle.State != CandleStates.Finished:
return
period = self._momentum_period.Value
self._momentum_closes.append(candle.ClosePrice)
keep = period + 5
if len(self._momentum_closes) > keep:
del self._momentum_closes[:len(self._momentum_closes) - keep]
if len(self._momentum_closes) <= period:
return
previous = self._momentum_closes[-1 - period]
if previous == Decimal.Zero:
return
momentum = candle.ClosePrice / previous * Decimal(100)
self._momentum_deviations.append(Math.Abs(momentum - Decimal(100)))
if len(self._momentum_deviations) > 3:
del self._momentum_deviations[:-3]
def _process_macd(self, candle):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
self._macd_count += 1
self._macd_fast = self._ema(self._macd_fast, close, self._macd_fast_len.Value)
self._macd_slow = self._ema(self._macd_slow, close, self._macd_slow_len.Value)
main = self._macd_fast - self._macd_slow
self._macd_signal = self._ema(self._macd_signal, main, self._macd_signal_len.Value)
self._macd_ready = self._macd_count >= self._macd_slow_len.Value + self._macd_signal_len.Value
def _process_primary(self, candle):
if candle.State != CandleStates.Finished:
return
required = max(max(self._fast_ma.Value, self._slow_ma.Value), self.BAND_LENGTH + 1)
self._primary.append((candle.OpenPrice, candle.HighPrice, candle.LowPrice, candle.ClosePrice))
if len(self._primary) > required + 5:
del self._primary[:len(self._primary) - required - 5]
rsi_value = process_value(self._rsi, candle.ClosePrice, candle.ServerTime, True)
if self.Position != 0 and self._apply_risk(candle):
return
if (len(self._primary) < required or not self._rsi.IsFormed or len(self._momentum_deviations) < 3 or
not self._macd_ready or self._macd_signal is None or self._macd_fast is None or self._macd_slow is None):
return
previous = self._primary[-2]
upper, lower = self._bollinger_previous()
fast = self._lwma(self._fast_ma.Value)
slow = self._lwma(self._slow_ma.Value)
rsi = to_decimal(rsi_value)
momentum = max(self._momentum_deviations)
macd_main = self._macd_fast - self._macd_slow
macd_signal = self._macd_signal
fifty = Decimal(50)
long_signal = (previous[2] <= lower and fast < slow and rsi > fifty and
momentum > self._momentum_buy.Value and macd_main > macd_signal)
short_signal = (previous[1] >= upper and fast < slow and rsi < fifty and
momentum > self._momentum_sell.Value and macd_main < macd_signal)
if long_signal and self.Position <= 0:
self._enter(Sides.Buy, candle.ClosePrice)
elif short_signal and self.Position >= 0:
self._enter(Sides.Sell, candle.ClosePrice)
def _enter(self, side, price):
volume = self._order_volume.Value + Math.Abs(self.Position)
if side == Sides.Buy:
self.BuyMarket(volume)
else:
self.SellMarket(volume)
self._entry_price = price
pip = self._pip_size()
if self._stop_loss.Value > 0:
distance = Decimal(self._stop_loss.Value) * pip
self._stop_price = price - distance if side == Sides.Buy else price + distance
else:
self._stop_price = None
if self._take_profit.Value > 0:
distance = Decimal(self._take_profit.Value) * pip
self._take_price = price + distance if side == Sides.Buy else price - distance
else:
self._take_price = None
self._best_price = price
self._money_trail_peak = None
def _apply_risk(self, candle):
floating = self._floating_pnl(candle.ClosePrice)
equity = self._portfolio_value(self._initial_equity) + floating
if equity > self._peak_equity:
self._peak_equity = equity
# The levels are checked as they stood before this candle; moving them afterwards keeps
# a move made from this candle's high or low from being hit by the same candle.
if self._is_level_hit(candle):
return self._flatten()
if self._use_money_tp.Value and floating >= self._money_tp.Value:
return self._flatten()
if (self._use_percent_tp.Value and self._initial_equity > 0 and
floating >= self._initial_equity * self._percent_tp.Value / Decimal(100)):
return self._flatten()
if self._enable_money_trailing.Value:
# Once armed, the peak is tracked and the giveback checked even below the target.
if self._money_trail_peak is not None:
if floating > self._money_trail_peak:
self._money_trail_peak = floating
elif floating > self._money_trail_target.Value:
self._money_trail_peak = floating
if self._money_trail_peak is not None and self._money_trail_peak - floating >= self._money_trail_stop.Value:
return self._flatten()
if (self._use_equity_stop.Value and self._peak_equity > 0 and
(self._peak_equity - equity) / self._peak_equity * Decimal(100) > self._equity_risk.Value):
return self._flatten()
self._move_levels(candle)
return False
def _is_level_hit(self, candle):
if self.Position > 0:
return ((self._stop_price is not None and candle.LowPrice <= self._stop_price) or
(self._take_price is not None and candle.HighPrice >= self._take_price))
return ((self._stop_price is not None and candle.HighPrice >= self._stop_price) or
(self._take_price is not None and candle.LowPrice <= self._take_price))
def _move_levels(self, candle):
pip = self._pip_size()
if self.Position > 0:
if (self._use_break_even.Value and
candle.HighPrice - self._entry_price >= Decimal(self._break_even_trigger.Value) * pip):
self._raise_stop(self._entry_price + Decimal(self._break_even_offset.Value) * pip)
# The stop trails only a new high beyond the best price since the entry.
if candle.HighPrice > self._best_price:
self._best_price = candle.HighPrice
if self._trailing_stop.Value > 0:
self._raise_stop(self._best_price - Decimal(self._trailing_stop.Value) * pip)
else:
if (self._use_break_even.Value and
self._entry_price - candle.LowPrice >= Decimal(self._break_even_trigger.Value) * pip):
self._lower_stop(self._entry_price - Decimal(self._break_even_offset.Value) * pip)
if candle.LowPrice < self._best_price:
self._best_price = candle.LowPrice
if self._trailing_stop.Value > 0:
self._lower_stop(self._best_price + Decimal(self._trailing_stop.Value) * pip)
def _raise_stop(self, level):
if self._stop_price is None or level > self._stop_price:
self._stop_price = level
def _lower_stop(self, level):
if self._stop_price is None or level < self._stop_price:
self._stop_price = level
def _flatten(self):
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
elif self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
else:
return False
self._reset_trade()
return True
def _floating_pnl(self, price):
if self.Position == 0 or self._entry_price == Decimal.Zero:
return Decimal.Zero
direction = Decimal(1) if self.Position > 0 else Decimal(-1)
multiplier = Decimal(1)
if self.Security is not None and self.Security.Multiplier is not None:
multiplier = self.Security.Multiplier
return (price - self._entry_price) * direction * Math.Abs(self.Position) * multiplier
def _portfolio_value(self, fallback):
portfolio = self.Portfolio
if portfolio is not None:
if portfolio.CurrentValue is not None:
return portfolio.CurrentValue
if portfolio.BeginValue is not None:
return portfolio.BeginValue
return fallback
def _bollinger_previous(self):
end = len(self._primary) - 1
length = Decimal(self.BAND_LENGTH)
total = Decimal.Zero
for i in range(end - self.BAND_LENGTH, end):
total += self._primary[i][3]
mean = total / length
squares = Decimal.Zero
for i in range(end - self.BAND_LENGTH, end):
deviation = self._primary[i][3] - mean
squares += deviation * deviation
std = Decimal(Math.Sqrt(Decimal.ToDouble(squares / length)))
width = Decimal(self.BAND_WIDTH)
return mean + width * std, mean - width * std
def _lwma(self, period):
start = len(self._primary) - period
total = Decimal.Zero
weights = Decimal.Zero
for i in range(period):
weight = Decimal(i + 1)
bar = self._primary[start + i]
typical = (bar[1] + bar[2] + bar[3]) / Decimal(3)
total += typical * weight
weights += weight
return total / weights
def _pip_size(self):
step = Decimal.Zero
if self.Security is not None and self.Security.PriceStep is not None:
step = self.Security.PriceStep
if step <= 0:
return Decimal(0.0001)
if step == Decimal(0.00001) or step == Decimal(0.001):
return step * Decimal(10)
return step
@staticmethod
def _ema(previous, value, period):
if previous is None:
return value
alpha = Decimal(2) / (Decimal(period) + Decimal(1))
return previous + alpha * (value - previous)
def _reset_trade(self):
self._entry_price = Decimal.Zero
self._stop_price = None
self._take_price = None
self._best_price = Decimal.Zero
self._money_trail_peak = None
def CreateClone(self):
return crypto_analysis_strategy()