GitHub で見る

スワパー戦略 (API 3751)

概要

Swaper 戦略 は、StockSharp の高レベル戦略 API を使用して、MetaTrader エキスパート アドバイザー「Swaper 1.1」を複製します。の オリジナルのシステムは、ロングエクスポージャーとショートエクスポージャーの間で合成ポートフォリオのバランスを常に再調整することでスワップゲインを蓄積します。これ 変換は、専門家の仮想残高を再構築し、公正価値を計算することにより、マネーフローのロジックを維持します。 基礎となる金融商品を調整し、オープンポジションをそのターゲット値に合わせます。

中核ロジック

  1. 合成資本の再構築。 この戦略は、初期資本を組み合わせて、MetaTrader money アキュムレータを再作成します。 残高(BaseUnits * BeginPrice)、約定した注文からの実現利益、および現在のポジションの未実現部分 ContractMultiplier でスケールされます。
  2. 公正価値の分母。 MQL の専門家は、アクティブな取引量に応じて増加または縮小する com 変数を管理します。 StockSharp ポートは、BaseUnits + ContractMultiplier * Position を通じてこの動作をミラーリングします。
  3. ターゲットボリュームの計算。 アルゴリズムは、過去 2 つのローソク足の高値の最大値を評価します (市場スプレッドによって調整されます)。 MetaTrader のガードレールを再現するための最後の 2 つの最低値。 Experts / (Experts + 1) 要素は、どのように制御するかを制御します 戦略は公正価値に向けて積極的に動きます。
  4. 位置調整。 計算された dt 値に応じて、戦略は次のいずれかになります。
    • 計算された調整がロットの 10 分の 1 を下回った場合にポジションをクローズする、または
    • dt < 0 のときに追加の数量を販売する、または
    • dt >= 0 のときに追加のボリュームを購入します。
  5. マージンを考慮したロットサイジング。 ヘルパー メソッド GetTradableVolume は、AccountFreeMargin() チェックを比較することで近似します。 利用可能なポートフォリオ資本で MarginPerLot を構成しました。リクエストされたサイズが利用可能なマージンを超える場合、ロットは 金額は 10 分の 1 に切り捨てられます。

ループ全体は完成したローソク足で実行され、経済ロジックを維持しながら元のティックベースの関数を置き換えます。 無傷。

パラメーター

パラメータ デフォルト 説明
Experts 1 合成公正価値調整に適用される加重。
BeginPrice 1.8014 仮想バランスを再構築するために使用される開始価格。
MagicNumber 777 MetaTrader バージョンとの互換性のために保存された識別子 (必要に応じて注文にログイン)。
BaseUnits 1000 公正価値方程式の分母として使用される初期資本単位。
ContractMultiplier 10 価格差をアカウント通貨に変換する乗数。
MarginPerLot 1000 1 つのロットをサポートするために必要なおおよその資本。ロット削減ロジックを制御します。
FallbackSpreadSteps 1 レベル 1 の相場が欠落している場合は、価格ステップでスプレッドします。
CandleType 1 Hour リバランス ループに供給されるプライマリ タイムフレーム。

取引ワークフロー

  1. 構成されたローソク足シリーズとレベル 1 データをサブスクライブします。
  2. 正確なスプレッドを取得するために、最良の買値/売値を追跡します。フィードが沈黙している場合は、フォールバックします。 FallbackSpreadSteps * PriceStep
  3. 完成したすべてのキャンドルの合成資本と分母を再計算します。
  4. 高価格パスを使用して dt を計算します。 dt < 0 の場合、低価格ブランチに切り替えて、元の保護機能をエミュレートします ロジック。
  5. 位置を縮小または拡大するには、AdjustShort または AdjustLong を使用します。対象サイズがロットの10分の1より小さい場合、 ポジションを完全にクローズして、MetaTrader から closeby の動作をコピーします。
  6. 後続の反復で最新の残高が使用されるように、OnOwnTradeReceived 内の実現損益を更新します。

MQL4 バージョンとの違い

  • ティック駆動の start() ループはローソク足処理に置き換えられ、戦略を維持しながら忙しい待機を回避します。 意図。
  • 注文履歴とオープントレードのスキャンは、OrdersHistoryTotal() ではなく戦略独自のトレード ストリームを通じて概算されます。 そしてOrdersTotal()
  • マージンチェックでは、ブローカー固有のマージンがあるため、構成可能な MarginPerLot 定数とともに Portfolio.CurrentValue を使用します。 関数は StockSharp では使用できません。
  • OrderCloseBy を介したペアクローズは、ほとんどのネッティング モデルと一致して、ネット ポジションを単純にフラット化することでエミュレートされます。 StockSharp コネクタ。

使用上の注意

  • コネクタのコントラクト仕様に従って MarginPerLot を構成し、戦略が ありえないボリューム。
  • この戦略では、ローソク足データが信頼できる高値と安値を提供することを期待しています。が使用するブローカーフィードと一致する時間枠を使用します。 同一の動作が必要な場合は、MetaTrader バージョンを使用してください。
  • レベル 1 相場は非同期で到着する可能性があるため、この戦略では最新のスプレッドが保存されます。キャンドルとレベルの両方が揃っていることを確認してください 正確なレプリケーションのために 1 つのサブスクリプションが有効になっています。
namespace StockSharp.Samples.Strategies;

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;

/// <summary>
/// Swap-based mean reversion strategy converted from the MetaTrader expert "Swaper 1.1".
/// Calculates a synthetic fair value using closed trades, adjusts the open position, and keeps the volume within the available margin.
/// </summary>
public class SwaperStrategy : Strategy
{
	private readonly StrategyParam<decimal> _experts;
	private readonly StrategyParam<decimal> _beginPrice;
	private readonly StrategyParam<int> _magicNumber;
	private readonly StrategyParam<decimal> _baseUnits;
	private readonly StrategyParam<decimal> _contractMultiplier;
	private readonly StrategyParam<decimal> _marginPerLot;
	private readonly StrategyParam<decimal> _fallbackSpreadSteps;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _initialCapital;
	private decimal _realizedPnL;
	private decimal _positionVolume;
	private decimal _averagePrice;
	private decimal? _bestBid;
	private decimal? _bestAsk;
	private ICandleMessage _previousCandle;

	/// <summary>
	/// Initializes a new instance of the <see cref="SwaperStrategy"/> class.
	/// </summary>
	public SwaperStrategy()
	{
		_experts = Param(nameof(Experts), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Experts", "Weighting coefficient applied to the synthetic fair value.", "General");

		_beginPrice = Param(nameof(BeginPrice), 1.8014m)
		.SetGreaterThanZero()
		.SetDisplay("Begin Price", "Initial price used to recreate the historical balance.", "General");

		_magicNumber = Param(nameof(MagicNumber), 777)
		.SetDisplay("Magic Number", "Identifier kept for compatibility with the MetaTrader expert.", "General");

		_baseUnits = Param(nameof(BaseUnits), 1000m)
		.SetGreaterThanZero()
		.SetDisplay("Base Units", "Synthetic account units used when calculating the fair value denominator.", "Money Management");

		_contractMultiplier = Param(nameof(ContractMultiplier), 10m)
		.SetGreaterThanZero()
		.SetDisplay("Contract Multiplier", "Value multiplier applied to realized and unrealized profit.", "Money Management");

		_marginPerLot = Param(nameof(MarginPerLot), 1000m)
		.SetGreaterThanZero()
		.SetDisplay("Margin Per Lot", "Approximate capital required to keep one lot open.", "Money Management");

		_fallbackSpreadSteps = Param(nameof(FallbackSpreadSteps), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Fallback Spread (steps)", "Spread expressed in price steps when level-one data is unavailable.", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Primary timeframe that replaces the tick-based loop of the original expert.", "Data");
	}

	/// <summary>
	/// Weighting coefficient applied to the synthetic fair value.
	/// </summary>
	public decimal Experts
	{
		get => _experts.Value;
		set => _experts.Value = value;
	}

	/// <summary>
	/// Initial price used to recreate the historical balance.
	/// </summary>
	public decimal BeginPrice
	{
		get => _beginPrice.Value;
		set => _beginPrice.Value = value;
	}

	/// <summary>
	/// Identifier kept for compatibility with the MetaTrader expert.
	/// </summary>
	public int MagicNumber
	{
		get => _magicNumber.Value;
		set => _magicNumber.Value = value;
	}

	/// <summary>
	/// Synthetic account units used when calculating the fair value denominator.
	/// </summary>
	public decimal BaseUnits
	{
		get => _baseUnits.Value;
		set => _baseUnits.Value = value;
	}

	/// <summary>
	/// Value multiplier applied to realized and unrealized profit.
	/// </summary>
	public decimal ContractMultiplier
	{
		get => _contractMultiplier.Value;
		set => _contractMultiplier.Value = value;
	}

	/// <summary>
	/// Approximate capital required to keep one lot open.
	/// </summary>
	public decimal MarginPerLot
	{
		get => _marginPerLot.Value;
		set => _marginPerLot.Value = value;
	}

	/// <summary>
	/// Spread expressed in price steps when level-one data is unavailable.
	/// </summary>
	public decimal FallbackSpreadSteps
	{
		get => _fallbackSpreadSteps.Value;
		set => _fallbackSpreadSteps.Value = value;
	}

	/// <summary>
	/// Primary timeframe that replaces the tick-based loop of the original expert.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_initialCapital = 0m;
		_realizedPnL = 0m;
		_positionVolume = 0m;
		_averagePrice = 0m;
		_bestBid = null;
		_bestAsk = null;
		_previousCandle = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_initialCapital = BaseUnits * BeginPrice;
		_realizedPnL = 0m;
		_positionVolume = 0m;
		_averagePrice = 0m;
		_bestBid = null;
		_bestAsk = null;
		_previousCandle = null;

		var candleSubscription = SubscribeCandles(CandleType);
		candleSubscription.Bind(ProcessCandle).Start();
	}

	private void ProcessLevel1(Level1ChangeMessage message)
	{
		if (message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid)
		_bestBid = bid;

		if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask)
		_bestAsk = ask;
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (_previousCandle == null)
		{
		_previousCandle = candle;
		return;
		}

		var security = Security;
		var priceStep = security?.PriceStep ?? 0.0001m;
		var spread = GetSpread(priceStep);
		var high = Math.Max(candle.HighPrice, _previousCandle.HighPrice);
		var low = Math.Min(candle.LowPrice, _previousCandle.LowPrice);

		if (high <= 0m || low <= 0m)
		{
		_previousCandle = candle;
		return;
		}

		var denominator = high + spread;
		if (denominator <= 0m)
		{
		_previousCandle = candle;
		return;
		}

		var com = CalculateDenominator();
		if (com == 0m)
		{
		_previousCandle = candle;
		return;
		}

		var money = CalculateSyntheticCapital(candle.ClosePrice);
		var expertsWeight = Experts;
		var dt = (money / denominator - com) * expertsWeight / (expertsWeight + 1m);

		if (dt < 0m)
		{
		var altDenominator = money / low;
		var dtAlt = (com - altDenominator) * expertsWeight / (expertsWeight + 1m);

		if (dtAlt < 1m)
		{
		ClosePositionIfExists();
		_previousCandle = candle;
		return;
		}

		var lots = (decimal)Math.Floor((double)dtAlt) / 10m;
		AdjustShort(lots);
		}
		else
		{
		if (dt < 1m)
		{
		ClosePositionIfExists();
		_previousCandle = candle;
		return;
		}

		var lots = (decimal)Math.Floor((double)dt) / 10m;
		AdjustLong(lots);
		}

		_previousCandle = candle;
	}

	private decimal CalculateSyntheticCapital(decimal currentPrice)
	{
		var multiplier = ContractMultiplier;
		var unrealized = _positionVolume * currentPrice * multiplier;
		return _initialCapital + _realizedPnL + unrealized;
	}

	private decimal CalculateDenominator()
	{
		return BaseUnits + ContractMultiplier * _positionVolume;
	}

	private decimal GetSpread(decimal priceStep)
	{
		if (_bestBid is decimal bid && _bestAsk is decimal ask && ask > bid)
		return ask - bid;

		var steps = FallbackSpreadSteps;
		return (steps <= 0m ? 1m : steps) * priceStep;
	}

	private void AdjustShort(decimal targetLots)
	{
		if (targetLots <= 0m)
		return;

		if (Position > 0m)
		{
		var reduce = Math.Min(Position, targetLots);
		if (reduce > 0m)
		SellMarket(reduce);
		return;
		}

		var currentShort = Position < 0m ? Math.Abs(Position) : 0m;
		if (currentShort >= targetLots)
		return;

		var additional = targetLots - currentShort;
		var tradable = GetTradableVolume(additional);
		if (tradable > 0m)
		SellMarket(tradable);
	}

	private void AdjustLong(decimal targetLots)
	{
		if (targetLots <= 0m)
		return;

		if (Position < 0m)
		{
		var reduce = Math.Min(Math.Abs(Position), targetLots);
		if (reduce > 0m)
		BuyMarket(reduce);
		return;
		}

		var currentLong = Position > 0m ? Position : 0m;
		if (currentLong >= targetLots)
		return;

		var additional = targetLots - currentLong;
		var tradable = GetTradableVolume(additional);
		if (tradable > 0m)
		BuyMarket(tradable);
	}

	private void ClosePositionIfExists()
	{
		var volume = Math.Abs(Position);
		if (volume <= 0m)
		return;

		if (Position > 0m)
		SellMarket(volume);
		else
		BuyMarket(volume);
	}

	private decimal GetTradableVolume(decimal desiredLots)
	{
		if (desiredLots <= 0m)
		return 0m;

		var marginPerLot = MarginPerLot;
		var availableCapital = Portfolio?.CurrentValue ?? (_initialCapital + _realizedPnL);

		if (marginPerLot <= 0m || availableCapital <= 0m)
		return (decimal)Math.Floor((double)(desiredLots * 10m)) / 10m;

		var maxLots = (decimal)Math.Floor((double)((availableCapital / marginPerLot) * 10m)) / 10m;
		if (maxLots <= 0m)
		return 0m;

		return Math.Min(desiredLots, (decimal)maxLots);
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		var order = trade.Order;
		if (order == null || order.Security != Security)
		return;

		var tradeInfo = trade.Trade;
		var volume = tradeInfo.Volume;
		if (volume <= 0m)
		return;

		var signedVolume = order.Side == Sides.Buy ? volume : -volume;
		var price = tradeInfo.Price;

		if (_positionVolume == 0m || Math.Sign(_positionVolume) == Math.Sign(signedVolume))
		{
		var totalVolume = _positionVolume + signedVolume;
		if (totalVolume == 0m)
		{
		_positionVolume = 0m;
		_averagePrice = 0m;
		}
		else
		{
		var weightedPrice = _averagePrice * _positionVolume + price * signedVolume;
		_positionVolume = totalVolume;
		_averagePrice = weightedPrice / totalVolume;
		}
		return;
		}

		var closingVolume = Math.Min(Math.Abs(signedVolume), Math.Abs(_positionVolume));
		var realized = (price - _averagePrice) * closingVolume * Math.Sign(_positionVolume) * ContractMultiplier;
		_realizedPnL += realized;

		var remainingVolume = _positionVolume + signedVolume;

		if (remainingVolume == 0m)
		{
		_positionVolume = 0m;
		_averagePrice = 0m;
		return;
		}

		if (Math.Sign(_positionVolume) == Math.Sign(remainingVolume))
		{
		_positionVolume = remainingVolume;
		return;
		}

		_positionVolume = remainingVolume;
		_averagePrice = price;
	}
}