GitHub で見る
マニュアル取引の軽量ユーティリティ戦略
概要
オリジナルの「Manual Trading Lightweight Utility」エキスパート アドバイザはコンパクトな MetaTrader パネルで、成行注文、指値注文、逆指値注文を切り替えるためのボタンが表示され、売買アクションの数量を個別に調整し、ストップロスとテイクプロフィットのオフセットを自動的に付加します。この C# ポートは、すべてのパネル ボタンを戦略パラメータとして表すことにより、StockSharp 内で同じワークフローを再作成します。この戦略は自律的な信号を生成しません。手動の指示を待ってから、保護出口を監視しながら、高レベルの API を使用して要求されたアクションを実行します。
再現された機能
- ワンショットの売買リクエスト。 2 つのブール トグルがパネル ボタンをエミュレートします。
BuyRequest または SellRequest を true に設定すると、選択したモードに基づいて 1 つの市場注文、指値注文、または逆指値注文がトリガーされ、すぐにトグルが false にリセットされます。
- 自動または手動の保留価格。 各側は、MetaTrader オフセット (
LimitOrderPoints および StopOrderPoints) を再利用するか、手動の絶対価格を受け入れることができます。自動価格設定では、相場が利用できない場合は、現在の最良買値/売値、または最新のローソク足の終値が使用されます。
- 独立したボリューム。 1 つのデフォルト ボリュームを両側で共有したり、サイドごとのボリュームをアクティブにして、MQL バージョンのロット コントロール スイッチをミラーリングしたりできます。
- ポイントベースの保護。
TakeProfitPoints と StopLossPoints は、商品 PriceStep を使用して、MetaTrader ポイントの距離を価格オフセットに変換します。この戦略は完了したローソク足を監視し、保護レベルを突破したときに成行注文でポジションを閉じます。
- コメントのフィードバック。 手動アクションごとに、構成された
OrderComment を含むログ エントリが書き込まれるため、視覚的なパネルを使用しなくても、実行されたコマンドを簡単に追跡できます。
戦略の流れ
- この戦略は、
CandleType によって選択されたローソク足タイプをサブスクライブします。完成したローソク足は、オフセットとリスク監視に使用される参照価格を提供します。
- 完成したキャンドルごとに、次のような戦略を立てます。
- 基本クラス
Volume を DefaultVolume で更新します (StockSharp での視覚的検査に役立ちます)。
BuyRequest と SellRequest の変更を検出し、保留中のアクションとしてマークします。
- 市場データの準備ができたら (
IsFormedAndOnlineAndAllowTrading())、要求されたアクションを実行し、未決注文の価格を解決し、結果を記録します。
- ネットポジションが変化するたびにエントリー価格を記録し、ストップロスまたはテイクプロフィットのしきい値を超えた場合に市場からの退場を発行するリスクマネージャーを呼び出します。
- 位置がフラットに戻ると、すべての内部状態がリセットされるため、次の手動リクエストは白紙の状態から始まります。
パラメーター
CandleType – 価格参照とリスク管理に使用される市場データ シリーズ。
BuyOrderMode / SellOrderMode – 両側に MarketExecution、PendingLimit、または PendingStop のいずれかを選択します。
UseAutomaticBuyPrice / UseAutomaticSellPrice – 自動オフセット価格設定を有効にします。固定の絶対価格の提供を無効にします。
BuyManualPrice / SellManualPrice – 自動価格設定がオフの場合に適用される手動保留注文価格(無視するには 0 に設定します)。
DefaultVolume – 個別の数量が無効になっている場合の共有注文数量。
UseIndividualVolumes – Lot Control アナログを切り替えます。有効にすると、次の 2 つのパラメータが共有ボリュームをオーバーライドします。
BuyVolume / SellVolume – サイドごとのボリューム。
TakeProfitPoints / StopLossPoints – MetaTrader ポイントで表される保護距離。ゼロを指定すると、それぞれの機能が無効になります。
LimitOrderPoints / StopOrderPoints – 自動指値とストップ価格に適用されるオフセット。これもポイント単位で測定されます。
BuyRequest / SellRequest – パネル ボタンをエミュレートする一時的な切り替え。これらはリクエストの処理後に自動的にリセットされます。
OrderComment – アクションの実行時にログに追加される自由形式のテキスト。
使用ガイドライン
- オフセットとリスク チェックに使用する粒度に一致するように
CandleType を構成します。デフォルトの 1 分間の時間枠は、過去のバックテストとの互換性を保ちながら、MetaTrader スクリプトのティック主導の動作に似ています。
- 単一の
DefaultVolume を使用するか、UseIndividualVolumes を有効にして売買数量を個別に制御するかを選択します。ボリュームはプラスのままでなければなりません。
- 未決価格をどのように計算するかを決定します。
UseAutomatic*Price を有効のままにして MetaTrader ポイント オフセットを複製するか、無効にして BuyManualPrice / SellManualPrice の値を明示的に指定します。
- 必要に応じて、
TakeProfitPoints と StopLossPoints を設定します。それらがゼロより大きい場合、戦略は商品 PriceStep を使用してそれらを価格距離に変換し、ローソク足が関連するしきい値を超えるとすぐに成行注文でポジションを閉じます。シンボルに設定された PriceStep がない場合、警告が記録され、保護距離はスキップされます。
- 注文を送信するには、
BuyRequest または SellRequest を false から true に変更します。このストラテジは、次に終了したローソク足でリクエストを解決し、選択した注文タイプを送信し、ログ エントリを書き込み、アクションが自動的に繰り返されないようにフラグをリセットします。
- 対応するパラメータを再度切り替えて、アクションを再実行します。必要な価格を解決できない場合 (たとえば、手動価格がゼロであるため)、リクエストはアイドル状態のままになります。構成を修正し、再度切り替えて再試行してください。
元の MQL ユーティリティとの違い
- MetaTrader チャート オブジェクトは StockSharp パラメータに置き換えられます。元のパネルのすべてのボタンとトグルは、UI または自動化スクリプト経由で制御できる編集可能なプロパティになりました。
- プロテクションレベルは、違反した場合に個別のストップ/リミットプロテクト注文を登録するのではなく、成行注文とともに実行されます。これにより、実装が高レベルの API 内に維持され、注文のライフサイクルを手動で管理する必要がなくなります。
- 最良の買値/売値が利用できない場合、自動価格は最新のローソク足の終値に戻り、注文帳データが存在しない可能性があるバックテスト中の確定的な動作が保証されます。
注意事項
- この戦略は、ネットポジションが変化するたびにエントリー価格を保存します。取引にスケールインすると、新しいサイズを反映する保護オフセットがローソク足の終値に再度固定されます。
- スプレッド補償は、設定されたポイント距離に最もよく知られているスプレッド (または相場が欠落している場合は 1 つの価格ステップ) を追加することでストップロスの計算に含まれ、現在のスプレッドによって売りストップを拡大する MQL ロジックを反映しています。
- ログエントリには、設定されたコメント、注文タイプ、価格 (未決注文の場合)、および数量が含まれており、各手動アクションの簡潔な監査証跡を提供します。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Manual Trading Lightweight Utility strategy: WMA trend following.
/// Buys when price is above WMA and rising, sells when below and falling.
/// </summary>
public class ManualTradingLightweightUtilityStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _wmaPeriod;
private decimal _prevWma;
private bool _hasPrev;
private bool _wasBullish;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int WmaPeriod { get => _wmaPeriod.Value; set => _wmaPeriod.Value = value; }
public ManualTradingLightweightUtilityStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_wmaPeriod = Param(nameof(WmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("WMA Period", "Weighted MA period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevWma = 0m;
_hasPrev = false;
_wasBullish = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var wma = new WeightedMovingAverage { Length = WmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(wma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal wma)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var isBullish = close > wma && wma > _prevWma;
if (_hasPrev)
{
if (isBullish && !_wasBullish && Position <= 0)
BuyMarket();
else if (!isBullish && close < wma && wma < _prevWma && _wasBullish && Position >= 0)
SellMarket();
}
_prevWma = wma;
_hasPrev = true;
_wasBullish = isBullish;
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class manual_trading_lightweight_utility_strategy(Strategy):
def __init__(self):
super(manual_trading_lightweight_utility_strategy, self).__init__()
self._wma_period = self.Param("WmaPeriod", 50) \
.SetDisplay("WMA Period", "Weighted MA period", "Indicators")
self._wma = None
self._prev_wma = 0.0
self._has_prev = False
self._was_bullish = False
@property
def wma_period(self):
return self._wma_period.Value
def OnReseted(self):
super(manual_trading_lightweight_utility_strategy, self).OnReseted()
self._wma = None
self._prev_wma = 0.0
self._has_prev = False
self._was_bullish = False
def OnStarted2(self, time):
super(manual_trading_lightweight_utility_strategy, self).OnStarted2(time)
self._wma = WeightedMovingAverage()
self._wma.Length = self.wma_period
self._has_prev = False
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(30)))
subscription.Bind(self._wma, self._process_candle)
subscription.Start()
def _process_candle(self, candle, wma_value):
if candle.State != CandleStates.Finished:
return
if not self._wma.IsFormed:
return
close = float(candle.ClosePrice)
wma_val = float(wma_value)
is_bullish = close > wma_val and wma_val > self._prev_wma
if self._has_prev:
if is_bullish and not self._was_bullish and self.Position <= 0:
self.BuyMarket()
elif not is_bullish and close < wma_val and wma_val < self._prev_wma and self._was_bullish and self.Position >= 0:
self.SellMarket()
self._prev_wma = wma_val
self._has_prev = True
self._was_bullish = is_bullish
def CreateClone(self):
return manual_trading_lightweight_utility_strategy()