244 lines
8.8 KiB
Plaintext
244 lines
8.8 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| HITPositionManagerTestHarness.mq5 |
|
|
//| Copyright 2026, nanpin-martin.com |
|
|
//| https://www.nanpin-martin.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2026, nanpin-martin.com"
|
|
#property link "https://nanpin-martin.com/"
|
|
#property version "1.00"
|
|
|
|
#include <Trade/Trade.mqh>
|
|
#include <MyLib/Trading/SLTPManager.mqh>
|
|
#include <MyLib/Trading/HITPositionLifecycleManager.mqh>
|
|
|
|
// Strategy Tester harness only. It seeds positions, then calls the production managers.
|
|
input int input_managed_magic_number = 10001; // 管理対象magic
|
|
input int input_control_magic_number = 20002; // 非対象確認用magic
|
|
input ulong input_slippage_points = 10; // 許容偏差(point)
|
|
input double input_test_lot_size = 0.01; // 検証用ロット
|
|
input bool input_seed_managed_buy = true; // 管理対象BUYを作成
|
|
input bool input_seed_managed_sell = true; // 管理対象SELLを作成
|
|
input bool input_seed_control_position = true; // magic違いポジションを作成
|
|
input int input_position_close_after_h1_bars = 12; // 保有時間決済(H1本数、0で無効)
|
|
input bool input_sltp_manager_enabled = true; // SLTP管理を有効化
|
|
input bool input_sltp_use_breakeven = true; // 通常BEを有効化
|
|
input double input_sltp_breakeven_trigger_pips = 30.0;
|
|
input double input_sltp_breakeven_buffer_pips = 3.0;
|
|
input bool input_sltp_use_elapsed_breakeven = false;
|
|
input double input_sltp_elapsed_breakeven_hours = 4.0;
|
|
input double input_sltp_elapsed_breakeven_buffer_pips = 3.0;
|
|
input bool input_sltp_use_active_trailing = false;
|
|
input double input_sltp_active_breakeven_pips = 30.0;
|
|
input double input_sltp_active_stop_loss_offset_pips = 5.0;
|
|
input double input_sltp_active_step_trigger_pips = 10.0;
|
|
input double input_sltp_active_step_move_pips = 5.0;
|
|
input bool input_sltp_use_tp_progress_stop = false;
|
|
input double input_sltp_tp_progress_trigger_percent = 70.0;
|
|
input double input_sltp_tp_progress_sl_lock_percent = 30.0;
|
|
input bool input_sltp_use_high_volatility_limit = false;
|
|
|
|
CTrade g_test_trade;
|
|
CSLTPManager g_sltp_manager;
|
|
CHITPositionLifecycleManager g_position_lifecycle;
|
|
|
|
bool g_seed_done = false;
|
|
bool g_sltp_settings_valid = false;
|
|
|
|
int EffectiveCloseAfterSeconds()
|
|
{
|
|
int close_bars = input_position_close_after_h1_bars;
|
|
if(close_bars < 0)
|
|
close_bars = 0;
|
|
if(close_bars > 240)
|
|
close_bars = 240;
|
|
|
|
const int h1_seconds = PeriodSeconds(PERIOD_H1);
|
|
if(close_bars <= 0 || h1_seconds <= 0)
|
|
return 0;
|
|
|
|
return close_bars * h1_seconds;
|
|
}
|
|
|
|
bool IsHedgingAccount()
|
|
{
|
|
return (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
|
|
}
|
|
|
|
double NormalizedVolume(const double volume)
|
|
{
|
|
const double min_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
const double max_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
const double step_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
if(min_volume <= 0.0 || max_volume <= 0.0 || step_volume <= 0.0)
|
|
return volume;
|
|
|
|
double normalized = volume;
|
|
if(normalized < min_volume)
|
|
normalized = min_volume;
|
|
if(normalized > max_volume)
|
|
normalized = max_volume;
|
|
|
|
const double steps = MathFloor((normalized - min_volume) / step_volume + 0.5);
|
|
normalized = min_volume + steps * step_volume;
|
|
|
|
int volume_digits = 0;
|
|
double step = step_volume;
|
|
while(step < 1.0 && volume_digits < 8)
|
|
{
|
|
step *= 10.0;
|
|
volume_digits++;
|
|
}
|
|
|
|
return NormalizeDouble(normalized, volume_digits);
|
|
}
|
|
|
|
bool ConfigureManagers()
|
|
{
|
|
if(!g_position_lifecycle.Init(_Symbol,
|
|
(ulong)input_managed_magic_number,
|
|
input_slippage_points,
|
|
EffectiveCloseAfterSeconds()))
|
|
return false;
|
|
|
|
g_sltp_manager.SetMagicNumber((ulong)input_managed_magic_number);
|
|
g_sltp_manager.SetSymbol(_Symbol);
|
|
g_sltp_manager.SetDeviationInPoints(input_slippage_points);
|
|
g_sltp_manager.SetBreakevenSettings(input_sltp_use_breakeven,
|
|
input_sltp_breakeven_trigger_pips,
|
|
input_sltp_breakeven_buffer_pips);
|
|
g_sltp_manager.SetElapsedBreakevenSettings(input_sltp_use_elapsed_breakeven,
|
|
input_sltp_elapsed_breakeven_hours,
|
|
input_sltp_elapsed_breakeven_buffer_pips);
|
|
g_sltp_manager.SetActiveTrailingSettings(input_sltp_use_active_trailing,
|
|
input_sltp_active_breakeven_pips,
|
|
input_sltp_active_stop_loss_offset_pips,
|
|
input_sltp_active_step_trigger_pips,
|
|
input_sltp_active_step_move_pips);
|
|
g_sltp_manager.SetTpProgressStopSettings(input_sltp_use_tp_progress_stop,
|
|
input_sltp_tp_progress_trigger_percent,
|
|
input_sltp_tp_progress_sl_lock_percent);
|
|
g_sltp_manager.SetHighVolatilityLimitSettings(input_sltp_use_high_volatility_limit);
|
|
|
|
g_sltp_settings_valid = g_sltp_manager.ValidateSettings();
|
|
return g_sltp_settings_valid;
|
|
}
|
|
|
|
bool SeedBuyPosition(const ulong magic,
|
|
const string comment)
|
|
{
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(_Symbol, tick))
|
|
{
|
|
Print("Harness tick read failed: err=", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
const double sl = NormalizeDouble(tick.bid - 5000.0 * point, digits);
|
|
const double tp = NormalizeDouble(tick.bid + 10000.0 * point, digits);
|
|
|
|
g_test_trade.SetExpertMagicNumber(magic);
|
|
ResetLastError();
|
|
if(!g_test_trade.Buy(NormalizedVolume(input_test_lot_size), _Symbol, 0.0, sl, tp, comment))
|
|
{
|
|
Print("Harness BUY seed failed: magic=", magic,
|
|
" retcode=", g_test_trade.ResultRetcode(),
|
|
" err=", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
Print("Harness BUY seed succeeded: magic=", magic,
|
|
" ticket=", g_test_trade.ResultOrder(),
|
|
" comment=", comment);
|
|
return true;
|
|
}
|
|
|
|
bool SeedSellPosition(const ulong magic,
|
|
const string comment)
|
|
{
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(_Symbol, tick))
|
|
{
|
|
Print("Harness tick read failed: err=", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
const double sl = NormalizeDouble(tick.ask + 5000.0 * point, digits);
|
|
const double tp = NormalizeDouble(tick.ask - 10000.0 * point, digits);
|
|
|
|
g_test_trade.SetExpertMagicNumber(magic);
|
|
ResetLastError();
|
|
if(!g_test_trade.Sell(NormalizedVolume(input_test_lot_size), _Symbol, 0.0, sl, tp, comment))
|
|
{
|
|
Print("Harness SELL seed failed: magic=", magic,
|
|
" retcode=", g_test_trade.ResultRetcode(),
|
|
" err=", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
Print("Harness SELL seed succeeded: magic=", magic,
|
|
" ticket=", g_test_trade.ResultOrder(),
|
|
" comment=", comment);
|
|
return true;
|
|
}
|
|
|
|
void SeedTestPositions()
|
|
{
|
|
if(g_seed_done)
|
|
return;
|
|
|
|
g_test_trade.SetDeviationInPoints((int)input_slippage_points);
|
|
g_test_trade.SetTypeFillingBySymbol(_Symbol);
|
|
|
|
if(input_seed_managed_buy)
|
|
SeedBuyPosition((ulong)input_managed_magic_number, "HIT PM TEST managed BUY");
|
|
if(input_seed_managed_sell)
|
|
SeedSellPosition((ulong)input_managed_magic_number, "HIT PM TEST managed SELL");
|
|
if(input_seed_control_position)
|
|
SeedBuyPosition((ulong)input_control_magic_number, "HIT PM TEST control magic");
|
|
|
|
g_seed_done = true;
|
|
}
|
|
|
|
void ManageTargetPositions()
|
|
{
|
|
g_position_lifecycle.SetCloseAfterSeconds(EffectiveCloseAfterSeconds());
|
|
g_position_lifecycle.CloseExpiredPositions();
|
|
|
|
if(!input_sltp_manager_enabled || !g_sltp_settings_valid)
|
|
return;
|
|
|
|
g_sltp_manager.ManagePositions();
|
|
g_sltp_manager.HighVolatilityLimit();
|
|
}
|
|
|
|
int OnInit()
|
|
{
|
|
if(!MQLInfoInteger(MQL_TESTER))
|
|
{
|
|
Print("HITPositionManagerTestHarness is for Strategy Tester only.");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
if(!IsHedgingAccount())
|
|
{
|
|
Print("HITPositionManagerTestHarness requires a retail hedging account. account_margin_mode=",
|
|
AccountInfoInteger(ACCOUNT_MARGIN_MODE));
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
if(!ConfigureManagers())
|
|
return INIT_FAILED;
|
|
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
void OnTick()
|
|
{
|
|
SeedTestPositions();
|
|
ManageTargetPositions();
|
|
}
|