Consolidate Python ignore rules into root gitignore
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,619 @@
|
||||
#ifndef HIT_ENTRY_SIGNAL_MQH
|
||||
#define HIT_ENTRY_SIGNAL_MQH
|
||||
|
||||
//| エントリー判定が必要な場合のみ注文処理を実行する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。
|
||||
*
|
||||
* @param state EA全体の状態。
|
||||
* @param ctx 現在のAsk/Bid/スプレッド情報。
|
||||
*
|
||||
* 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。
|
||||
*/
|
||||
void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx)
|
||||
{
|
||||
if(!ShouldRunEntryDecision(state))
|
||||
return;
|
||||
|
||||
g_bars_H1_check = false;
|
||||
g_bars_M15_check = false;
|
||||
state.last_chk = TimeCurrent();
|
||||
|
||||
if(!ValidateEntryPreconditions(state))
|
||||
return;
|
||||
|
||||
int sent_success = SendAllowedEntryOrders(state, ctx);
|
||||
UpdateEntryRetryState(state, sent_success);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| エントリー判定を実行するタイミングか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief エントリー判定を実行するタイミングか確認します。
|
||||
*
|
||||
* @param state EA全体の状態。前回判定時刻を参照します。
|
||||
* @return H1候補が有効で、M15確定足が更新され、前回判定から一定秒数以上経過していればtrue。
|
||||
*/
|
||||
bool ShouldRunEntryDecision(EAState &state)
|
||||
{
|
||||
return (g_bars_H1_check && g_bars_M15_check && TimeCurrent() - state.last_chk >= ENTRY_RETRY_SECONDS);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| エントリー判定前の共通チェックを行う関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 新規注文前の共通条件を検証します。
|
||||
*
|
||||
* @param state EA全体の状態。
|
||||
* @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。
|
||||
*
|
||||
* `res_chk`、market_state、対象EAの注文/ポジション数上限を確認します。
|
||||
* market_state=6は相場ボラ停止ではなく、Python/CSV/API失敗時の技術エラー停止として扱います。
|
||||
*/
|
||||
bool ValidateEntryPreconditions(EAState &state)
|
||||
{
|
||||
if(state.res_chk != 1)
|
||||
{
|
||||
Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk);
|
||||
state.chk_cnt = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(state.trend_state < MARKET_LOW_VOL_RANGE || state.trend_state > MARKET_HIGH_VOL_DOWN)
|
||||
{
|
||||
Print("[Entry Skip] invalid market_state=", state.trend_state);
|
||||
state.chk_cnt = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(IsTargetCandidateExpired(state))
|
||||
{
|
||||
Print("[Entry Skip] H1 target candidate expired. loaded_at=",
|
||||
TimeToString(state.target_loaded_at, TIME_DATE | TIME_SECONDS));
|
||||
state.chk_cnt = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
int used = CountMyUsed();
|
||||
if(used >= POSITION_LIMIT)
|
||||
{
|
||||
Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H1候補価格の有効期限を判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1候補価格がENTRY_H1_LIMIT時間を超えて古くなっていないか判定します。
|
||||
*
|
||||
* @param state EA全体の状態。H1候補価格の読込時刻を参照します。
|
||||
* @return 候補価格が期限切れの場合はtrue。
|
||||
*/
|
||||
bool IsTargetCandidateExpired(EAState &state)
|
||||
{
|
||||
if(state.target_loaded_at <= 0)
|
||||
return false;
|
||||
|
||||
int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1);
|
||||
if(expiration_seconds <= 0)
|
||||
return false;
|
||||
|
||||
return (TimeCurrent() - state.target_loaded_at >= expiration_seconds);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 許可された注文タイプだけを順番に送信する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H4 market_stateで許可された注文タイプだけを順番に送信します。
|
||||
*
|
||||
* @param state EA全体の状態。各注文タイプのen/tp/slを参照します。
|
||||
* @param ctx 現在のAsk/Bid/スプレッド情報。
|
||||
* @return 送信成功した注文数。
|
||||
*/
|
||||
int SendAllowedEntryOrders(EAState &state, TickContext &ctx)
|
||||
{
|
||||
int sent_success = 0;
|
||||
|
||||
for(int t = 1; t <= 4; t++)
|
||||
{
|
||||
int used = CountMyUsed();
|
||||
if(used >= POSITION_LIMIT)
|
||||
{
|
||||
Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!IsOrderTypeAllowedByTrend(t, state.trend_state))
|
||||
{
|
||||
Print("[Skip] orderType=", t, " not allowed by market_state=", state.trend_state,
|
||||
" (", MarketStateName(state.trend_state), ")");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(TrySendEntryOrder(t, state, ctx))
|
||||
sent_success++;
|
||||
}
|
||||
|
||||
return sent_success;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプ1件分の価格検証と注文送信を行う関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプ1件分の価格検証と注文送信を行います。
|
||||
*
|
||||
* @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。
|
||||
* @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @return 注文送信に成功した場合はtrue、それ以外はfalse。
|
||||
*/
|
||||
bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx)
|
||||
{
|
||||
string entry_type = EntryTypeName(orderType);
|
||||
double cur_price = CurrentPriceForOrderType(orderType, ctx);
|
||||
|
||||
double en = state.en_price[orderType];
|
||||
double tp = state.tp_price[orderType];
|
||||
double sl = state.sl_price[orderType];
|
||||
|
||||
if(!HasValidTargetPrices(en, tp, sl))
|
||||
{
|
||||
Print("[Skip] invalid target prices. orderType=", orderType,
|
||||
" en=", en, " tp=", tp, " sl=", sl);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl);
|
||||
if(!ok)
|
||||
{
|
||||
Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!IsM15EntryTimingConfirmed(orderType, ctx, en))
|
||||
{
|
||||
Print("[No ", entry_type, "] M15 timing not confirmed. cur=", cur_price, " en=", en);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!MeetsTradeDistanceRules(orderType, ctx, en, tp, sl))
|
||||
return false;
|
||||
|
||||
Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl);
|
||||
|
||||
if(SendOrder(orderType, en, tp, sl))
|
||||
{
|
||||
Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl);
|
||||
return true;
|
||||
}
|
||||
|
||||
Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl);
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプ名を返す関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプ番号に対応する表示名を返します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @return 注文タイプの表示名。不正値の場合は"Unknown"。
|
||||
*/
|
||||
string EntryTypeName(const int orderType)
|
||||
{
|
||||
switch(orderType)
|
||||
{
|
||||
case 1:
|
||||
return "Buy Stop";
|
||||
case 2:
|
||||
return "Buy Limit";
|
||||
case 3:
|
||||
return "Sell Stop";
|
||||
case 4:
|
||||
return "Sell Limit";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプに応じた現在価格を返す関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプに応じて価格比較に使う現在価格を返します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @return 買い系注文ではAsk、売り系注文ではBid。
|
||||
*/
|
||||
double CurrentPriceForOrderType(const int orderType, TickContext &ctx)
|
||||
{
|
||||
if(orderType == 1 || orderType == 2)
|
||||
return ctx.ask;
|
||||
|
||||
return ctx.bid;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプごとの価格整合条件を判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @param en エントリー価格。
|
||||
* @param tp 利確価格。
|
||||
* @param sl 損切価格。
|
||||
* @return 注文タイプの価格条件を満たす場合はtrue。
|
||||
*/
|
||||
bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl)
|
||||
{
|
||||
switch(orderType)
|
||||
{
|
||||
case 1: // Buy Stop
|
||||
return (ctx.ask < en && tp > en && sl < en);
|
||||
|
||||
case 2: // Buy Limit
|
||||
return (ctx.ask > en && tp > en && sl < en);
|
||||
|
||||
case 3: // Sell Stop
|
||||
return (ctx.bid > en && tp < en && sl > en);
|
||||
|
||||
case 4: // Sell Limit
|
||||
return (ctx.bid < en && tp < en && sl > en);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15確定足によるエントリータイミング確認
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1候補価格に対してM15の発注タイミングが整っているか判定します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @param en H1で決めたエントリー候補価格。
|
||||
* @return M15確認条件を満たす場合はtrue。
|
||||
*
|
||||
* H4/H1の方向判断は維持し、M15では「候補価格に近い」「直近確定足が
|
||||
* 順張り/反転の根拠を持つ」ことだけを確認します。
|
||||
*/
|
||||
bool IsM15EntryTimingConfirmed(const int orderType, TickContext &ctx, const double en)
|
||||
{
|
||||
if(!use_m15_entry_filter)
|
||||
return true;
|
||||
|
||||
MqlRates rates[];
|
||||
int copied = CopyRates(_Symbol, PERIOD_M15, OHLC_START_SHIFT, M15_CONFIRM_BARS, rates);
|
||||
if(copied < 3)
|
||||
{
|
||||
Print("[M15 Filter] insufficient M15 bars. copied=", copied);
|
||||
return false;
|
||||
}
|
||||
|
||||
int last_index = copied - 1;
|
||||
int prev_index = copied - 2;
|
||||
|
||||
double avg_range = AverageM15Range(rates, copied);
|
||||
double min_zone = M15_MIN_ENTRY_ZONE_POINTS * Point();
|
||||
double entry_zone = avg_range * m15_entry_zone_atr_multiplier;
|
||||
if(entry_zone < min_zone)
|
||||
entry_zone = min_zone;
|
||||
|
||||
double cur_price = CurrentPriceForOrderType(orderType, ctx);
|
||||
if(MathAbs(cur_price - en) > entry_zone)
|
||||
{
|
||||
Print("[M15 Filter] ", EntryTypeName(orderType), " is not near entry zone. cur=",
|
||||
cur_price, " en=", en, " zone=", entry_zone);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!IsM15SignalAligned(orderType, rates[prev_index], rates[last_index], en, entry_zone))
|
||||
return false;
|
||||
|
||||
return IsM15ImbalanceConfirmationPassed(orderType, rates, copied, last_index);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15平均レンジを計算する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief M15確定足の平均レンジを計算します。
|
||||
*
|
||||
* @param rates M15のMqlRates配列。
|
||||
* @param count 使用する本数。
|
||||
* @return 平均レンジ。算出不能な場合は最小ゾーン幅を返します。
|
||||
*/
|
||||
double AverageM15Range(const MqlRates &rates[], const int count)
|
||||
{
|
||||
double total_range = 0.0;
|
||||
int used = 0;
|
||||
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
double range = rates[i].high - rates[i].low;
|
||||
if(range <= 0.0)
|
||||
continue;
|
||||
|
||||
total_range += range;
|
||||
used++;
|
||||
}
|
||||
|
||||
if(used <= 0)
|
||||
return M15_MIN_ENTRY_ZONE_POINTS * Point();
|
||||
|
||||
return total_range / used;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15平均実体を計算する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 判定対象足を含めず、直前N本のM15平均実体サイズを返します。
|
||||
*
|
||||
* @param rates M15のMqlRates配列。
|
||||
* @param current_index 判定対象の配列index。
|
||||
* @param period 平均計算本数。
|
||||
* @return 平均実体サイズ。算出不能な場合は0。
|
||||
*/
|
||||
double AverageM15BodySize(const MqlRates &rates[], const int current_index, const int period)
|
||||
{
|
||||
if(period <= 0)
|
||||
return 0.0;
|
||||
if(current_index < period)
|
||||
return 0.0;
|
||||
|
||||
double total_body = 0.0;
|
||||
for(int i = current_index - period; i < current_index; i++)
|
||||
total_body += MathAbs(rates[i].close - rates[i].open);
|
||||
|
||||
return total_body / period;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15初動確認を行う注文タイプか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 初動フォローの追加確認を適用する注文タイプを返します。
|
||||
*/
|
||||
bool RequiresM15ImbalanceConfirmation(const int orderType)
|
||||
{
|
||||
return (orderType == 1 || orderType == 3);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15インバランス初動確認
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief T1/T3順張り注文に対してM15確定足の初動またはブレイクを確認します。
|
||||
*
|
||||
* H1/Pythonで決めた方向は上書きせず、発注直前にM15の方向一致と勢いだけを確認します。
|
||||
*/
|
||||
bool IsM15ImbalanceConfirmationPassed(const int orderType, const MqlRates &rates[], const int count, const int current_index)
|
||||
{
|
||||
if(!use_m15_imbalance_confirmation)
|
||||
return true;
|
||||
if(!RequiresM15ImbalanceConfirmation(orderType))
|
||||
return true;
|
||||
|
||||
if(m15_imbalance_avg_body_period <= 0 || m15_imbalance_sensitivity <= 0.0)
|
||||
{
|
||||
Print("[M15 Imbalance] invalid settings. period=", m15_imbalance_avg_body_period,
|
||||
" sensitivity=", m15_imbalance_sensitivity);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(count <= m15_imbalance_avg_body_period || current_index <= 0)
|
||||
{
|
||||
Print("[M15 Imbalance] insufficient bars. copied=", count,
|
||||
" period=", m15_imbalance_avg_body_period);
|
||||
return false;
|
||||
}
|
||||
|
||||
double avg_body = AverageM15BodySize(rates, current_index, m15_imbalance_avg_body_period);
|
||||
double min_avg_body = m15_imbalance_min_avg_body_points * Point();
|
||||
double current_body = MathAbs(rates[current_index].close - rates[current_index].open);
|
||||
|
||||
if(avg_body <= min_avg_body)
|
||||
{
|
||||
if(use_m15_imbalance_debug_log)
|
||||
Print("[M15 Imbalance] average body too small. avg=", avg_body,
|
||||
" min=", min_avg_body, " orderType=", orderType);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bullish = (rates[current_index].close > rates[current_index].open);
|
||||
bool bearish = (rates[current_index].close < rates[current_index].open);
|
||||
bool direction_ok = (orderType == 1 && bullish) || (orderType == 3 && bearish);
|
||||
bool body_ok = (current_body > avg_body * m15_imbalance_sensitivity);
|
||||
bool break_ok = false;
|
||||
|
||||
if(orderType == 1)
|
||||
break_ok = (rates[current_index].close > rates[current_index - 1].high);
|
||||
else if(orderType == 3)
|
||||
break_ok = (rates[current_index].close < rates[current_index - 1].low);
|
||||
|
||||
bool passed = (direction_ok && (body_ok || break_ok));
|
||||
if(use_m15_imbalance_debug_log)
|
||||
{
|
||||
Print("[M15 Imbalance] orderType=", orderType,
|
||||
" avg_body=", avg_body,
|
||||
" current_body=", current_body,
|
||||
" sensitivity=", m15_imbalance_sensitivity,
|
||||
" direction_ok=", direction_ok,
|
||||
" body_ok=", body_ok,
|
||||
" break_ok=", break_ok,
|
||||
" passed=", passed);
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプごとのM15シグナル方向を判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプごとにM15確定足の勢い・反転根拠を確認します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param prev_bar 1本前のM15確定足。
|
||||
* @param last_bar 直近のM15確定足。
|
||||
* @param en H1で決めたエントリー候補価格。
|
||||
* @param entry_zone M15平均レンジから算出した候補価格付近の許容幅。
|
||||
* @return 注文タイプに沿ったM15根拠があればtrue。
|
||||
*/
|
||||
bool IsM15SignalAligned(const int orderType, const MqlRates &prev_bar, const MqlRates &last_bar, const double en, const double entry_zone)
|
||||
{
|
||||
double range = SafeBarRange(last_bar);
|
||||
double body_ratio = MathAbs(last_bar.close - last_bar.open) / range;
|
||||
double upper_wick_ratio = (last_bar.high - MathMax(last_bar.open, last_bar.close)) / range;
|
||||
double lower_wick_ratio = (MathMin(last_bar.open, last_bar.close) - last_bar.low) / range;
|
||||
|
||||
bool bullish = (last_bar.close > last_bar.open);
|
||||
bool bearish = (last_bar.close < last_bar.open);
|
||||
bool strong_body = (body_ratio >= M15_MIN_BODY_RATIO);
|
||||
bool bullish_break = (last_bar.close > prev_bar.high);
|
||||
bool bearish_break = (last_bar.close < prev_bar.low);
|
||||
bool lower_rejection = (lower_wick_ratio >= M15_REJECTION_WICK_RATIO);
|
||||
bool upper_rejection = (upper_wick_ratio >= M15_REJECTION_WICK_RATIO);
|
||||
|
||||
switch(orderType)
|
||||
{
|
||||
case 1: // Buy Stop: M15の上方向モメンタムを確認
|
||||
return (bullish && (bullish_break || strong_body) && last_bar.close <= en + entry_zone);
|
||||
|
||||
case 2: // Buy Limit: 候補価格付近で下ヒゲ反転または買い戻しを確認
|
||||
return (last_bar.low <= en + entry_zone && bullish &&
|
||||
(lower_rejection || bullish_break || strong_body));
|
||||
|
||||
case 3: // Sell Stop: M15の下方向モメンタムを確認
|
||||
return (bearish && (bearish_break || strong_body) && last_bar.close >= en - entry_zone);
|
||||
|
||||
case 4: // Sell Limit: 候補価格付近で上ヒゲ反転または売り戻しを確認
|
||||
return (last_bar.high >= en - entry_zone && bearish &&
|
||||
(upper_rejection || bearish_break || strong_body));
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ローソク足レンジを安全に取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief ゼロ除算を避けるため、最小値を持つローソク足レンジを返します。
|
||||
*
|
||||
* @param bar 対象ローソク足。
|
||||
* @return high-low。0以下の場合はPoint()を返します。
|
||||
*/
|
||||
double SafeBarRange(const MqlRates &bar)
|
||||
{
|
||||
double range = bar.high - bar.low;
|
||||
if(range <= 0.0)
|
||||
return Point();
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| brokerの最小距離制約を満たすか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief pending価格、TP、SLがstop level / freeze levelの最小距離を満たすか判定します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @param en エントリー価格。
|
||||
* @param tp 利確価格。
|
||||
* @param sl 損切価格。
|
||||
* @return 最小距離を満たす場合はtrue。
|
||||
*/
|
||||
bool MeetsTradeDistanceRules(const int orderType, TickContext &ctx, const double en, const double tp, const double sl)
|
||||
{
|
||||
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
int freeze_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||||
int min_level = stop_level;
|
||||
if(freeze_level > min_level)
|
||||
min_level = freeze_level;
|
||||
double min_distance = min_level * Point();
|
||||
|
||||
if(min_distance <= 0.0)
|
||||
return true;
|
||||
|
||||
double cur_price = CurrentPriceForOrderType(orderType, ctx);
|
||||
string entry_type = EntryTypeName(orderType);
|
||||
|
||||
if(MathAbs(en - cur_price) < min_distance)
|
||||
{
|
||||
Print("[Skip] ", entry_type, " entry is too close. cur=", cur_price,
|
||||
" en=", en, " min_distance=", min_distance);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(MathAbs(tp - en) < min_distance)
|
||||
{
|
||||
Print("[Skip] ", entry_type, " TP is too close. en=", en,
|
||||
" tp=", tp, " min_distance=", min_distance);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(MathAbs(en - sl) < min_distance)
|
||||
{
|
||||
Print("[Skip] ", entry_type, " SL is too close. en=", en,
|
||||
" sl=", sl, " min_distance=", min_distance);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文送信結果に応じてリトライ状態を更新する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。
|
||||
*
|
||||
* @param state EA全体の状態。`chk_cnt` を更新します。
|
||||
* @param sent_success 今回送信に成功した注文数。
|
||||
*
|
||||
* 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。
|
||||
*/
|
||||
void UpdateEntryRetryState(EAState &state, const int sent_success)
|
||||
{
|
||||
if(sent_success > 0)
|
||||
{
|
||||
state.chk_cnt = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
state.chk_cnt += 1;
|
||||
Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/", ENTRY_RETRY_LIMIT,
|
||||
" (wait next M15 bar)");
|
||||
|
||||
if(state.chk_cnt < ENTRY_RETRY_LIMIT)
|
||||
{
|
||||
g_bars_H1_check = true; // 次のM15確定足で再度エントリー判定を実行する
|
||||
return;
|
||||
}
|
||||
|
||||
Print("[Retry End] reached max tries. reset chk_cnt.");
|
||||
state.chk_cnt = 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,685 @@
|
||||
#ifndef HIT_ENTRY_SIGNAL_MQH
|
||||
#define HIT_ENTRY_SIGNAL_MQH
|
||||
|
||||
//| エントリー判定が必要な場合のみ注文処理を実行する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。
|
||||
*
|
||||
* @param state EA全体の状態。
|
||||
* @param ctx 現在のAsk/Bid/スプレッド情報。
|
||||
*
|
||||
* 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。
|
||||
*/
|
||||
void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx)
|
||||
{
|
||||
if(!ShouldRunEntryDecision(state))
|
||||
return;
|
||||
|
||||
g_bars_H1_check = false;
|
||||
g_bars_M15_check = false;
|
||||
state.last_chk = TimeCurrent();
|
||||
|
||||
if(!ValidateEntryPreconditions(state))
|
||||
return;
|
||||
|
||||
int sent_success = SendAllowedEntryOrders(state, ctx);
|
||||
UpdateEntryRetryState(state, sent_success);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| エントリー判定を実行するタイミングか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief エントリー判定を実行するタイミングか確認します。
|
||||
*
|
||||
* @param state EA全体の状態。前回判定時刻を参照します。
|
||||
* @return H1候補が有効で、M15確定足が更新され、前回判定から一定秒数以上経過していればtrue。
|
||||
*/
|
||||
bool ShouldRunEntryDecision(EAState &state)
|
||||
{
|
||||
return (g_bars_H1_check && g_bars_M15_check && TimeCurrent() - state.last_chk >= ENTRY_RETRY_SECONDS);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| エントリー判定前の共通チェックを行う関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 新規注文前の共通条件を検証します。
|
||||
*
|
||||
* @param state EA全体の状態。
|
||||
* @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。
|
||||
*
|
||||
* `res_chk`、market_state、対象EAの注文/ポジション数上限を確認します。
|
||||
* market_state=6は相場ボラ停止ではなく、Python/CSV/API失敗時の技術エラー停止として扱います。
|
||||
*/
|
||||
bool ValidateEntryPreconditions(EAState &state)
|
||||
{
|
||||
if(state.res_chk != 1)
|
||||
{
|
||||
Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk);
|
||||
state.chk_cnt = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(state.trend_state < MARKET_LOW_VOL_RANGE || state.trend_state > MARKET_HIGH_VOL_DOWN)
|
||||
{
|
||||
Print("[Entry Skip] invalid market_state=", state.trend_state);
|
||||
state.chk_cnt = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(IsTargetCandidateExpired(state))
|
||||
{
|
||||
Print("[Entry Skip] H1 target candidate expired. loaded_at=",
|
||||
TimeToString(state.target_loaded_at, TIME_DATE | TIME_SECONDS));
|
||||
state.chk_cnt = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(IsTargetCandidateTooOldForExecution(state))
|
||||
{
|
||||
Print("[Entry Skip] H1 target candidate is too old for new execution. age=",
|
||||
TargetCandidateAgeText(state),
|
||||
" max_age_minutes=", input_entry_max_candidate_age_minutes,
|
||||
" loaded_at=", TimeToString(state.target_loaded_at, TIME_DATE | TIME_SECONDS));
|
||||
state.chk_cnt = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
int used = CountMyUsed();
|
||||
if(used >= POSITION_LIMIT)
|
||||
{
|
||||
Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H1候補価格の有効期限を判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1候補価格がENTRY_H1_LIMIT時間を超えて古くなっていないか判定します。
|
||||
*
|
||||
* @param state EA全体の状態。H1候補価格の読込時刻を参照します。
|
||||
* @return 候補価格が期限切れの場合はtrue。
|
||||
*/
|
||||
bool IsTargetCandidateExpired(EAState &state)
|
||||
{
|
||||
if(state.target_loaded_at <= 0)
|
||||
return false;
|
||||
|
||||
int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1);
|
||||
if(expiration_seconds <= 0)
|
||||
return false;
|
||||
|
||||
return (TimeCurrent() - state.target_loaded_at >= expiration_seconds);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H1候補価格の発注許容年齢を判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief M15確認が遅れて整った古いH1候補で新規注文しないように判定します。
|
||||
*
|
||||
* @param state EA全体の状態。H1候補価格の読込時刻を参照します。
|
||||
* @return 入力で指定した最大経過分数を超えている場合はtrue。
|
||||
*/
|
||||
bool IsTargetCandidateTooOldForExecution(EAState &state)
|
||||
{
|
||||
if(input_entry_max_candidate_age_minutes <= 0)
|
||||
return false;
|
||||
if(state.target_loaded_at <= 0)
|
||||
return false;
|
||||
|
||||
int max_age_seconds = input_entry_max_candidate_age_minutes * 60;
|
||||
if(max_age_seconds <= 0)
|
||||
return false;
|
||||
|
||||
return (TargetCandidateAgeSeconds(state) > max_age_seconds);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H1候補価格の経過秒数を返す関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1候補価格をEAへ読み込んでからの経過秒数を返します。
|
||||
*/
|
||||
int TargetCandidateAgeSeconds(EAState &state)
|
||||
{
|
||||
if(state.target_loaded_at <= 0)
|
||||
return -1;
|
||||
|
||||
return (int)(TimeCurrent() - state.target_loaded_at);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H1候補価格の経過秒数をログ用文字列にする関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1候補価格の経過秒数をログへ出しやすい文字列にします。
|
||||
*/
|
||||
string TargetCandidateAgeText(EAState &state)
|
||||
{
|
||||
int age_seconds = TargetCandidateAgeSeconds(state);
|
||||
if(age_seconds < 0)
|
||||
return "unknown";
|
||||
|
||||
return IntegerToString(age_seconds) + "s";
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 許可された注文タイプだけを順番に送信する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H4 market_stateで許可された注文タイプだけを順番に送信します。
|
||||
*
|
||||
* @param state EA全体の状態。各注文タイプのen/tp/slを参照します。
|
||||
* @param ctx 現在のAsk/Bid/スプレッド情報。
|
||||
* @return 送信成功した注文数。
|
||||
*/
|
||||
int SendAllowedEntryOrders(EAState &state, TickContext &ctx)
|
||||
{
|
||||
int sent_success = 0;
|
||||
|
||||
for(int t = 1; t <= 4; t++)
|
||||
{
|
||||
int used = CountMyUsed();
|
||||
if(used >= POSITION_LIMIT)
|
||||
{
|
||||
Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!IsOrderTypeAllowedByTrend(t, state.trend_state))
|
||||
{
|
||||
Print("[Skip] orderType=", t, " not allowed by market_state=", state.trend_state,
|
||||
" (", MarketStateName(state.trend_state), ")");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(TrySendEntryOrder(t, state, ctx))
|
||||
sent_success++;
|
||||
}
|
||||
|
||||
return sent_success;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプ1件分の価格検証と注文送信を行う関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプ1件分の価格検証と注文送信を行います。
|
||||
*
|
||||
* @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。
|
||||
* @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @return 注文送信に成功した場合はtrue、それ以外はfalse。
|
||||
*/
|
||||
bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx)
|
||||
{
|
||||
string entry_type = EntryTypeName(orderType);
|
||||
double cur_price = CurrentPriceForOrderType(orderType, ctx);
|
||||
|
||||
double en = state.en_price[orderType];
|
||||
double tp = state.tp_price[orderType];
|
||||
double sl = state.sl_price[orderType];
|
||||
|
||||
if(!HasValidTargetPrices(en, tp, sl))
|
||||
{
|
||||
Print("[Skip] invalid target prices. orderType=", orderType,
|
||||
" en=", en, " tp=", tp, " sl=", sl,
|
||||
" candidate_age=", TargetCandidateAgeText(state));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl);
|
||||
if(!ok)
|
||||
{
|
||||
Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl,
|
||||
" candidate_age=", TargetCandidateAgeText(state));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!IsM15EntryTimingConfirmed(orderType, ctx, en))
|
||||
{
|
||||
Print("[No ", entry_type, "] M15 timing not confirmed. cur=", cur_price, " en=", en,
|
||||
" candidate_age=", TargetCandidateAgeText(state),
|
||||
" max_age_minutes=", input_entry_max_candidate_age_minutes);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!MeetsTradeDistanceRules(orderType, ctx, en, tp, sl))
|
||||
return false;
|
||||
|
||||
Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl);
|
||||
|
||||
if(SendOrder(orderType, en, tp, sl))
|
||||
{
|
||||
Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl);
|
||||
return true;
|
||||
}
|
||||
|
||||
Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl);
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプ名を返す関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプ番号に対応する表示名を返します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @return 注文タイプの表示名。不正値の場合は"Unknown"。
|
||||
*/
|
||||
string EntryTypeName(const int orderType)
|
||||
{
|
||||
switch(orderType)
|
||||
{
|
||||
case 1:
|
||||
return "Buy Stop";
|
||||
case 2:
|
||||
return "Buy Limit";
|
||||
case 3:
|
||||
return "Sell Stop";
|
||||
case 4:
|
||||
return "Sell Limit";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプに応じた現在価格を返す関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプに応じて価格比較に使う現在価格を返します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @return 買い系注文ではAsk、売り系注文ではBid。
|
||||
*/
|
||||
double CurrentPriceForOrderType(const int orderType, TickContext &ctx)
|
||||
{
|
||||
if(orderType == 1 || orderType == 2)
|
||||
return ctx.ask;
|
||||
|
||||
return ctx.bid;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプごとの価格整合条件を判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @param en エントリー価格。
|
||||
* @param tp 利確価格。
|
||||
* @param sl 損切価格。
|
||||
* @return 注文タイプの価格条件を満たす場合はtrue。
|
||||
*/
|
||||
bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl)
|
||||
{
|
||||
switch(orderType)
|
||||
{
|
||||
case 1: // Buy Stop
|
||||
return (ctx.ask < en && tp > en && sl < en);
|
||||
|
||||
case 2: // Buy Limit
|
||||
return (ctx.ask > en && tp > en && sl < en);
|
||||
|
||||
case 3: // Sell Stop
|
||||
return (ctx.bid > en && tp < en && sl > en);
|
||||
|
||||
case 4: // Sell Limit
|
||||
return (ctx.bid < en && tp < en && sl > en);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15確定足によるエントリータイミング確認
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1候補価格に対してM15の発注タイミングが整っているか判定します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @param en H1で決めたエントリー候補価格。
|
||||
* @return M15確認条件を満たす場合はtrue。
|
||||
*
|
||||
* H4/H1の方向判断は維持し、M15では「候補価格に近い」「直近確定足が
|
||||
* 順張り/反転の根拠を持つ」ことだけを確認します。
|
||||
*/
|
||||
bool IsM15EntryTimingConfirmed(const int orderType, TickContext &ctx, const double en)
|
||||
{
|
||||
if(!use_m15_entry_filter)
|
||||
return true;
|
||||
|
||||
MqlRates rates[];
|
||||
int copied = CopyRates(_Symbol, PERIOD_M15, OHLC_START_SHIFT, M15_CONFIRM_BARS, rates);
|
||||
if(copied < 3)
|
||||
{
|
||||
Print("[M15 Filter] insufficient M15 bars. copied=", copied);
|
||||
return false;
|
||||
}
|
||||
|
||||
int last_index = copied - 1;
|
||||
int prev_index = copied - 2;
|
||||
|
||||
double avg_range = AverageM15Range(rates, copied);
|
||||
double min_zone = M15_MIN_ENTRY_ZONE_POINTS * Point();
|
||||
double entry_zone = avg_range * m15_entry_zone_atr_multiplier;
|
||||
if(entry_zone < min_zone)
|
||||
entry_zone = min_zone;
|
||||
|
||||
double cur_price = CurrentPriceForOrderType(orderType, ctx);
|
||||
if(MathAbs(cur_price - en) > entry_zone)
|
||||
{
|
||||
Print("[M15 Filter] ", EntryTypeName(orderType), " is not near entry zone. cur=",
|
||||
cur_price, " en=", en, " zone=", entry_zone);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!IsM15SignalAligned(orderType, rates[prev_index], rates[last_index], en, entry_zone))
|
||||
return false;
|
||||
|
||||
return IsM15ImbalanceConfirmationPassed(orderType, rates, copied, last_index);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15平均レンジを計算する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief M15確定足の平均レンジを計算します。
|
||||
*
|
||||
* @param rates M15のMqlRates配列。
|
||||
* @param count 使用する本数。
|
||||
* @return 平均レンジ。算出不能な場合は最小ゾーン幅を返します。
|
||||
*/
|
||||
double AverageM15Range(const MqlRates &rates[], const int count)
|
||||
{
|
||||
double total_range = 0.0;
|
||||
int used = 0;
|
||||
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
double range = rates[i].high - rates[i].low;
|
||||
if(range <= 0.0)
|
||||
continue;
|
||||
|
||||
total_range += range;
|
||||
used++;
|
||||
}
|
||||
|
||||
if(used <= 0)
|
||||
return M15_MIN_ENTRY_ZONE_POINTS * Point();
|
||||
|
||||
return total_range / used;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15平均実体を計算する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 判定対象足を含めず、直前N本のM15平均実体サイズを返します。
|
||||
*
|
||||
* @param rates M15のMqlRates配列。
|
||||
* @param current_index 判定対象の配列index。
|
||||
* @param period 平均計算本数。
|
||||
* @return 平均実体サイズ。算出不能な場合は0。
|
||||
*/
|
||||
double AverageM15BodySize(const MqlRates &rates[], const int current_index, const int period)
|
||||
{
|
||||
if(period <= 0)
|
||||
return 0.0;
|
||||
if(current_index < period)
|
||||
return 0.0;
|
||||
|
||||
double total_body = 0.0;
|
||||
for(int i = current_index - period; i < current_index; i++)
|
||||
total_body += MathAbs(rates[i].close - rates[i].open);
|
||||
|
||||
return total_body / period;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15初動確認を行う注文タイプか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 初動フォローの追加確認を適用する注文タイプを返します。
|
||||
*/
|
||||
bool RequiresM15ImbalanceConfirmation(const int orderType)
|
||||
{
|
||||
return (orderType == 1 || orderType == 3);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15インバランス初動確認
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief T1/T3順張り注文に対してM15確定足の初動またはブレイクを確認します。
|
||||
*
|
||||
* H1/Pythonで決めた方向は上書きせず、発注直前にM15の方向一致と勢いだけを確認します。
|
||||
*/
|
||||
bool IsM15ImbalanceConfirmationPassed(const int orderType, const MqlRates &rates[], const int count, const int current_index)
|
||||
{
|
||||
if(!use_m15_imbalance_confirmation)
|
||||
return true;
|
||||
if(!RequiresM15ImbalanceConfirmation(orderType))
|
||||
return true;
|
||||
|
||||
if(m15_imbalance_avg_body_period <= 0 || m15_imbalance_sensitivity <= 0.0)
|
||||
{
|
||||
Print("[M15 Imbalance] invalid settings. period=", m15_imbalance_avg_body_period,
|
||||
" sensitivity=", m15_imbalance_sensitivity);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(count <= m15_imbalance_avg_body_period || current_index <= 0)
|
||||
{
|
||||
Print("[M15 Imbalance] insufficient bars. copied=", count,
|
||||
" period=", m15_imbalance_avg_body_period);
|
||||
return false;
|
||||
}
|
||||
|
||||
double avg_body = AverageM15BodySize(rates, current_index, m15_imbalance_avg_body_period);
|
||||
double min_avg_body = m15_imbalance_min_avg_body_points * Point();
|
||||
double current_body = MathAbs(rates[current_index].close - rates[current_index].open);
|
||||
|
||||
if(avg_body <= min_avg_body)
|
||||
{
|
||||
if(use_m15_imbalance_debug_log)
|
||||
Print("[M15 Imbalance] average body too small. avg=", avg_body,
|
||||
" min=", min_avg_body, " orderType=", orderType);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bullish = (rates[current_index].close > rates[current_index].open);
|
||||
bool bearish = (rates[current_index].close < rates[current_index].open);
|
||||
bool direction_ok = (orderType == 1 && bullish) || (orderType == 3 && bearish);
|
||||
bool body_ok = (current_body > avg_body * m15_imbalance_sensitivity);
|
||||
bool break_ok = false;
|
||||
|
||||
if(orderType == 1)
|
||||
break_ok = (rates[current_index].close > rates[current_index - 1].high);
|
||||
else if(orderType == 3)
|
||||
break_ok = (rates[current_index].close < rates[current_index - 1].low);
|
||||
|
||||
bool passed = (direction_ok && (body_ok || break_ok));
|
||||
if(use_m15_imbalance_debug_log)
|
||||
{
|
||||
Print("[M15 Imbalance] orderType=", orderType,
|
||||
" avg_body=", avg_body,
|
||||
" current_body=", current_body,
|
||||
" sensitivity=", m15_imbalance_sensitivity,
|
||||
" direction_ok=", direction_ok,
|
||||
" body_ok=", body_ok,
|
||||
" break_ok=", break_ok,
|
||||
" passed=", passed);
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプごとのM15シグナル方向を判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文タイプごとにM15確定足の勢い・反転根拠を確認します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param prev_bar 1本前のM15確定足。
|
||||
* @param last_bar 直近のM15確定足。
|
||||
* @param en H1で決めたエントリー候補価格。
|
||||
* @param entry_zone M15平均レンジから算出した候補価格付近の許容幅。
|
||||
* @return 注文タイプに沿ったM15根拠があればtrue。
|
||||
*/
|
||||
bool IsM15SignalAligned(const int orderType, const MqlRates &prev_bar, const MqlRates &last_bar, const double en, const double entry_zone)
|
||||
{
|
||||
double range = SafeBarRange(last_bar);
|
||||
double body_ratio = MathAbs(last_bar.close - last_bar.open) / range;
|
||||
double upper_wick_ratio = (last_bar.high - MathMax(last_bar.open, last_bar.close)) / range;
|
||||
double lower_wick_ratio = (MathMin(last_bar.open, last_bar.close) - last_bar.low) / range;
|
||||
|
||||
bool bullish = (last_bar.close > last_bar.open);
|
||||
bool bearish = (last_bar.close < last_bar.open);
|
||||
bool strong_body = (body_ratio >= M15_MIN_BODY_RATIO);
|
||||
bool bullish_break = (last_bar.close > prev_bar.high);
|
||||
bool bearish_break = (last_bar.close < prev_bar.low);
|
||||
bool lower_rejection = (lower_wick_ratio >= M15_REJECTION_WICK_RATIO);
|
||||
bool upper_rejection = (upper_wick_ratio >= M15_REJECTION_WICK_RATIO);
|
||||
|
||||
switch(orderType)
|
||||
{
|
||||
case 1: // Buy Stop: M15の上方向モメンタムを確認
|
||||
return (bullish && (bullish_break || strong_body) && last_bar.close <= en + entry_zone);
|
||||
|
||||
case 2: // Buy Limit: 候補価格付近で下ヒゲ反転または買い戻しを確認
|
||||
return (last_bar.low <= en + entry_zone && bullish &&
|
||||
(lower_rejection || bullish_break || strong_body));
|
||||
|
||||
case 3: // Sell Stop: M15の下方向モメンタムを確認
|
||||
return (bearish && (bearish_break || strong_body) && last_bar.close >= en - entry_zone);
|
||||
|
||||
case 4: // Sell Limit: 候補価格付近で上ヒゲ反転または売り戻しを確認
|
||||
return (last_bar.high >= en - entry_zone && bearish &&
|
||||
(upper_rejection || bearish_break || strong_body));
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ローソク足レンジを安全に取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief ゼロ除算を避けるため、最小値を持つローソク足レンジを返します。
|
||||
*
|
||||
* @param bar 対象ローソク足。
|
||||
* @return high-low。0以下の場合はPoint()を返します。
|
||||
*/
|
||||
double SafeBarRange(const MqlRates &bar)
|
||||
{
|
||||
double range = bar.high - bar.low;
|
||||
if(range <= 0.0)
|
||||
return Point();
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| brokerの最小距離制約を満たすか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief pending価格、TP、SLがstop level / freeze levelの最小距離を満たすか判定します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param ctx 現在のAsk/Bid情報。
|
||||
* @param en エントリー価格。
|
||||
* @param tp 利確価格。
|
||||
* @param sl 損切価格。
|
||||
* @return 最小距離を満たす場合はtrue。
|
||||
*/
|
||||
bool MeetsTradeDistanceRules(const int orderType, TickContext &ctx, const double en, const double tp, const double sl)
|
||||
{
|
||||
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
int freeze_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||||
int min_level = stop_level;
|
||||
if(freeze_level > min_level)
|
||||
min_level = freeze_level;
|
||||
double min_distance = min_level * Point();
|
||||
|
||||
if(min_distance <= 0.0)
|
||||
return true;
|
||||
|
||||
double cur_price = CurrentPriceForOrderType(orderType, ctx);
|
||||
string entry_type = EntryTypeName(orderType);
|
||||
|
||||
if(MathAbs(en - cur_price) < min_distance)
|
||||
{
|
||||
Print("[Skip] ", entry_type, " entry is too close. cur=", cur_price,
|
||||
" en=", en, " min_distance=", min_distance);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(MathAbs(tp - en) < min_distance)
|
||||
{
|
||||
Print("[Skip] ", entry_type, " TP is too close. en=", en,
|
||||
" tp=", tp, " min_distance=", min_distance);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(MathAbs(en - sl) < min_distance)
|
||||
{
|
||||
Print("[Skip] ", entry_type, " SL is too close. en=", en,
|
||||
" sl=", sl, " min_distance=", min_distance);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文送信結果に応じてリトライ状態を更新する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。
|
||||
*
|
||||
* @param state EA全体の状態。`chk_cnt` を更新します。
|
||||
* @param sent_success 今回送信に成功した注文数。
|
||||
*
|
||||
* 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。
|
||||
*/
|
||||
void UpdateEntryRetryState(EAState &state, const int sent_success)
|
||||
{
|
||||
if(sent_success > 0)
|
||||
{
|
||||
state.chk_cnt = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
state.chk_cnt += 1;
|
||||
Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/", ENTRY_RETRY_LIMIT,
|
||||
" (wait next M15 bar)");
|
||||
|
||||
if(state.chk_cnt < ENTRY_RETRY_LIMIT)
|
||||
{
|
||||
g_bars_H1_check = true; // 次のM15確定足で再度エントリー判定を実行する
|
||||
return;
|
||||
}
|
||||
|
||||
Print("[Retry End] reached max tries. reset chk_cnt.");
|
||||
state.chk_cnt = 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,514 @@
|
||||
#ifndef HIT_PYTHON_SIGNAL_GATEWAY_MQH
|
||||
#define HIT_PYTHON_SIGNAL_GATEWAY_MQH
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 最新のOHLCデータを取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 指定時間足のOHLCデータを取得します。
|
||||
*
|
||||
* @param times 取得したバー時刻を格納する配列。
|
||||
* @param open_prices 取得したOpen価格を格納する配列。
|
||||
* @param high_prices 取得したHigh価格を格納する配列。
|
||||
* @param low_prices 取得したLow価格を格納する配列。
|
||||
* @param close_prices 取得したClose価格を格納する配列。
|
||||
* @param tf 取得対象の時間足。
|
||||
* @param bars_count 取得するバー本数。
|
||||
* @return 取得に成功した場合はtrue、失敗した場合はfalse。
|
||||
*
|
||||
* `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。
|
||||
*/
|
||||
bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count)
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(open_prices, 0);
|
||||
ArrayResize(high_prices, 0);
|
||||
ArrayResize(low_prices, 0);
|
||||
ArrayResize(close_prices, 0);
|
||||
|
||||
if(bars_count < 2)
|
||||
return false;
|
||||
|
||||
// OHLCデータを取得
|
||||
// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。
|
||||
// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。
|
||||
MqlRates rates[];
|
||||
int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates);
|
||||
if(copied <= 0)
|
||||
{
|
||||
Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
ArrayResize(times, copied);
|
||||
ArrayResize(open_prices, copied);
|
||||
ArrayResize(high_prices, copied);
|
||||
ArrayResize(low_prices, copied);
|
||||
ArrayResize(close_prices, copied);
|
||||
|
||||
// 必要なデータを配列に格納
|
||||
for(int i = 0; i < copied; i++)
|
||||
{
|
||||
times[i] = rates[i].time;
|
||||
open_prices[i] = rates[i].open;
|
||||
high_prices[i] = rates[i].high;
|
||||
low_prices[i] = rates[i].low;
|
||||
close_prices[i] = rates[i].close;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| "ohlc.csv"を出力する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief OHLC配列をPython入力用CSVとして出力します。
|
||||
*
|
||||
* @param filename 出力するCSVファイル名。
|
||||
* @param times バー時刻配列。
|
||||
* @param open_prices Open価格配列。
|
||||
* @param high_prices High価格配列。
|
||||
* @param low_prices Low価格配列。
|
||||
* @param close_prices Close価格配列。
|
||||
* @return CSV出力に成功した場合はtrue。
|
||||
*/
|
||||
bool RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[])
|
||||
{
|
||||
// 出力先ファイル名
|
||||
// string filename = "ohlc.csv";
|
||||
|
||||
// ファイルを "書き込みモード" でオープン (テキスト/ANSI)
|
||||
int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI);
|
||||
|
||||
// ファイルが開けたかチェック
|
||||
if(fileHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print(__FUNCTION__, " : Failed to open file: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提)
|
||||
int size = ArraySize(times);
|
||||
|
||||
// ヘッダー行を追加
|
||||
FileWrite(fileHandle, "Time,Open,High,Low,Close");
|
||||
|
||||
// 小数点の桁数を取得
|
||||
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
|
||||
// フォーマット文字列を動的に生成
|
||||
string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits);
|
||||
|
||||
// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
// 時間をフォーマット
|
||||
string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES);
|
||||
|
||||
// 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整)
|
||||
string line = StringFormat(formatString,
|
||||
timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]);
|
||||
|
||||
// ファイルに書き込み (改行付き)
|
||||
FileWrite(fileHandle, line);
|
||||
}
|
||||
|
||||
// 書き込み終了後、ファイルを閉じる
|
||||
FileClose(fileHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| バッチファイル(Pythonスクリプト)を実行する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H4トレンド判定用バッチファイルを起動します。
|
||||
*
|
||||
* @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。
|
||||
*
|
||||
* 起動直前に`process_done_trend.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。
|
||||
*/
|
||||
bool ExecuteBatchTrend()
|
||||
{
|
||||
if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process))
|
||||
return false;
|
||||
|
||||
DeleteDoneFile(done_trend_file);
|
||||
return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| |
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1エントリー価格生成用バッチファイルを起動します。
|
||||
*
|
||||
* @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。
|
||||
*
|
||||
* 起動直前に`process_done_entry.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。
|
||||
*/
|
||||
bool ExecuteBatchEntry()
|
||||
{
|
||||
if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process))
|
||||
return false;
|
||||
|
||||
DeleteDoneFile(done_entry_file);
|
||||
return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process);
|
||||
}
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| "ohlc.csv"を出力後、バッチファイルの実行する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。
|
||||
*
|
||||
* @param state EA全体の状態。トレンド読込待ちフラグを更新します。
|
||||
* @return CSV出力とバッチ起動が開始できた場合はtrue。
|
||||
*/
|
||||
bool RecordOHLCAndExecuteBatch_Trend(EAState &state)
|
||||
{
|
||||
if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process))
|
||||
{
|
||||
Print("Trend Python is still running. Skip H4 CSV update.");
|
||||
return false;
|
||||
}
|
||||
|
||||
datetime times[];
|
||||
double open_prices[], high_prices[], low_prices[], close_prices[];
|
||||
|
||||
if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices,
|
||||
PERIOD_H4, HISTORY_BARS))
|
||||
{ Print("GetLatestOHLC(H4) failed."); return false; }
|
||||
|
||||
if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices))
|
||||
{ Print("RecordOHLC(H4) failed."); return false; }
|
||||
|
||||
if(!ExecuteBatchTrend())
|
||||
return false;
|
||||
|
||||
state.load_trend_flg = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| |
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。
|
||||
*
|
||||
* @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。
|
||||
* @return CSV出力とバッチ起動が開始できた場合はtrue。
|
||||
*/
|
||||
bool RecordOHLCAndExecuteBatch_Entry(EAState &state)
|
||||
{
|
||||
if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process))
|
||||
{
|
||||
Print("Entry Python is still running. Skip H1 CSV update.");
|
||||
return false;
|
||||
}
|
||||
|
||||
datetime times[];
|
||||
double open_prices[], high_prices[], low_prices[], close_prices[];
|
||||
|
||||
if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices,
|
||||
PERIOD_H1, HISTORY_BARS))
|
||||
{ Print("GetLatestOHLC(H1) failed."); return false; }
|
||||
|
||||
if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices))
|
||||
{ Print("RecordOHLC(H1) failed."); return false; }
|
||||
|
||||
if(!ExecuteBatchEntry())
|
||||
return false;
|
||||
|
||||
state.load_target_flg = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| |
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。
|
||||
*
|
||||
* @param state EA全体の状態。H4 market_stateと更新フラグを更新します。
|
||||
* @return 現状は常にtrue。
|
||||
*/
|
||||
bool GetTrendState(EAState &state)
|
||||
{
|
||||
bool ProcessDone = CheckDoneFile(done_trend_file);
|
||||
if(ProcessDone && g_ea.load_trend_flg)
|
||||
{
|
||||
int trend_state;
|
||||
LoadTrendState(trend_state);
|
||||
state.trend_state = trend_state;
|
||||
Print("market_state: ", state.trend_state, " (", MarketStateName(state.trend_state), ")");
|
||||
g_ea.load_trend_flg = false;
|
||||
g_ea.last_trend_update = TimeLocal();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| |
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief `trend_state.txt` からH4 market_state判定値を読み込みます。
|
||||
*
|
||||
* @param trend_state 読み込んだ値を格納する参照。0..5のmarket_state。6は技術エラー停止。
|
||||
*
|
||||
* ファイル未存在、読込失敗、異常値の場合は安全側として6(技術エラー停止)を設定します。
|
||||
*/
|
||||
void LoadTrendState(int &trend_state)
|
||||
{
|
||||
string filename = trend_state_file;
|
||||
trend_state = MARKET_TECHNICAL_ERROR_STOP;
|
||||
|
||||
if(FileIsExist(filename))
|
||||
{
|
||||
int filehandle = FileOpen(filename, FILE_READ | FILE_TXT);
|
||||
if(filehandle != INVALID_HANDLE)
|
||||
{
|
||||
string line = FileReadString(filehandle);
|
||||
int value = (int)StringToInteger(line);
|
||||
if(value >= MARKET_LOW_VOL_RANGE && value <= MARKET_TECHNICAL_ERROR_STOP)
|
||||
trend_state = value;
|
||||
else
|
||||
Print("Invalid market_state value: ", line, ". Use technical error stop(6).");
|
||||
FileClose(filehandle);
|
||||
}
|
||||
else
|
||||
{ Print("Failed to open trend_state.txt"); trend_state = MARKET_TECHNICAL_ERROR_STOP; }
|
||||
}
|
||||
else
|
||||
{ Print("trend_state.txt not found"); trend_state = MARKET_TECHNICAL_ERROR_STOP; }
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ターゲット価格を取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。
|
||||
*
|
||||
* @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。
|
||||
* @return 現状は常にtrue。
|
||||
*/
|
||||
bool GetTargetPrices(EAState &state)
|
||||
{
|
||||
bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更
|
||||
if(ProcessDone && g_ea.load_target_flg) // ←変更
|
||||
{
|
||||
double target_prices[];
|
||||
LoadTargetPrices(target_prices);
|
||||
|
||||
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
|
||||
// 先頭は res_chk
|
||||
state.res_chk = (int)target_prices[0];
|
||||
|
||||
// ★ 1..4 の (en,tp,sl) を読む:合計12個
|
||||
for(int t=1; t<=4; t++)
|
||||
{
|
||||
int base = 1 + (t-1)*3; // 1,4,7,10
|
||||
state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits);
|
||||
state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits);
|
||||
state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits);
|
||||
}
|
||||
|
||||
// ログ(任意)
|
||||
Print("target_prices: res=", state.res_chk,
|
||||
" | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1],
|
||||
" | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2],
|
||||
" | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3],
|
||||
" | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]);
|
||||
|
||||
LoadTargetZones(state);
|
||||
if(use_split_entry_zone && cancel_old_split_pending_on_new_zone && state.zone_res_chk == 1)
|
||||
CancelStaleSplitPendingOrders(state.zone_candidate_id);
|
||||
|
||||
g_ea.load_target_flg = false; // ←変更
|
||||
g_ea.last_target_update = TimeLocal(); // ←変更
|
||||
g_ea.target_loaded_at = TimeCurrent();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
//+------------------------------------------------------------------+
|
||||
//| "target_prices.txt"を読み込む関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief `target_prices.txt` から13個の数値を読み込みます。
|
||||
*
|
||||
* @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。
|
||||
*
|
||||
* 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。
|
||||
*/
|
||||
void LoadTargetPrices(double &target_prices[])
|
||||
{
|
||||
ArrayResize(target_prices, TARGET_SIZE);
|
||||
for(int i = 0; i < TARGET_SIZE; i++)
|
||||
target_prices[i] = DEFAULT_TARGET_PRICE;
|
||||
|
||||
string filename = target_prices_file;
|
||||
|
||||
if(FileIsExist(filename))
|
||||
{
|
||||
int filehandle = FileOpen(filename, FILE_READ | FILE_TXT);
|
||||
if(filehandle != INVALID_HANDLE)
|
||||
{
|
||||
int i = 0;
|
||||
while(!FileIsEnding(filehandle) && i < TARGET_SIZE)
|
||||
{
|
||||
string line = FileReadString(filehandle);
|
||||
target_prices[i] = StringToDouble(line);
|
||||
i++;
|
||||
}
|
||||
FileClose(filehandle);
|
||||
|
||||
if(i < TARGET_SIZE)
|
||||
{
|
||||
Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE);
|
||||
target_prices[0] = 0; // 不完全なファイルは無効扱い
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open file. Error code: ", GetLastError());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("File does not exist: ", filename);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| target_zones.txt の状態を停止値へ初期化する関数
|
||||
//+------------------------------------------------------------------+
|
||||
void ResetTargetZones(EAState &state)
|
||||
{
|
||||
state.zone_res_chk = 0;
|
||||
state.zone_candidate_id = "0";
|
||||
|
||||
for(int t = 1; t <= 4; t++)
|
||||
{
|
||||
state.zone_low[t] = 0.0;
|
||||
state.zone_high[t] = 0.0;
|
||||
state.zone_tp[t] = 0.0;
|
||||
state.zone_sl[t] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| target_zones.txt の1戦略行を読み込む関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool ParseTargetZoneLine(const string line, EAState &state, const int digits)
|
||||
{
|
||||
string parts[];
|
||||
int count = StringSplit(line, StringGetCharacter(",", 0), parts);
|
||||
if(count < 5)
|
||||
return false;
|
||||
|
||||
int strategy = (int)StringToInteger(parts[0]);
|
||||
if(strategy < 1 || strategy > 4)
|
||||
return false;
|
||||
|
||||
state.zone_low[strategy] = NormalizeDouble(StringToDouble(parts[1]), digits);
|
||||
state.zone_high[strategy] = NormalizeDouble(StringToDouble(parts[2]), digits);
|
||||
state.zone_tp[strategy] = NormalizeDouble(StringToDouble(parts[3]), digits);
|
||||
state.zone_sl[strategy] = NormalizeDouble(StringToDouble(parts[4]), digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| target_zones.txt を読み込む関数
|
||||
//+------------------------------------------------------------------+
|
||||
void LoadTargetZones(EAState &state)
|
||||
{
|
||||
ResetTargetZones(state);
|
||||
|
||||
string filename = target_zones_file;
|
||||
if(!FileIsExist(filename))
|
||||
{
|
||||
Print("target_zones.txt not found. Split entry zones disabled for this candidate.");
|
||||
return;
|
||||
}
|
||||
|
||||
int filehandle = FileOpen(filename, FILE_READ | FILE_TXT);
|
||||
if(filehandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Failed to open target_zones.txt. Error code: ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
int line_index = 0;
|
||||
int loaded_strategies = 0;
|
||||
|
||||
while(!FileIsEnding(filehandle))
|
||||
{
|
||||
string line = FileReadString(filehandle);
|
||||
if(line == "")
|
||||
continue;
|
||||
|
||||
line_index++;
|
||||
if(line_index == 1)
|
||||
{
|
||||
int schema_version = (int)StringToInteger(line);
|
||||
if(schema_version != TARGET_ZONE_SCHEMA_VERSION)
|
||||
{
|
||||
Print("target_zones schema mismatch. loaded=", schema_version,
|
||||
" required=", TARGET_ZONE_SCHEMA_VERSION);
|
||||
FileClose(filehandle);
|
||||
ResetTargetZones(state);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(line_index == 2)
|
||||
{
|
||||
state.zone_res_chk = (int)StringToInteger(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(line_index == 3)
|
||||
{
|
||||
state.zone_candidate_id = line;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ParseTargetZoneLine(line, state, digits))
|
||||
loaded_strategies++;
|
||||
}
|
||||
|
||||
FileClose(filehandle);
|
||||
|
||||
if(line_index < 7 || loaded_strategies < 4)
|
||||
{
|
||||
Print("target_zones.txt line count is short. loaded_lines=", line_index,
|
||||
" loaded_strategies=", loaded_strategies);
|
||||
ResetTargetZones(state);
|
||||
return;
|
||||
}
|
||||
|
||||
if(state.zone_res_chk != 1)
|
||||
ResetTargetZones(state);
|
||||
|
||||
Print("target_zones: res=", state.zone_res_chk,
|
||||
" id=", state.zone_candidate_id,
|
||||
" | T1 zone=", state.zone_low[1], "-", state.zone_high[1],
|
||||
" tp=", state.zone_tp[1], " sl=", state.zone_sl[1],
|
||||
" | T2 zone=", state.zone_low[2], "-", state.zone_high[2],
|
||||
" tp=", state.zone_tp[2], " sl=", state.zone_sl[2],
|
||||
" | T3 zone=", state.zone_low[3], "-", state.zone_high[3],
|
||||
" tp=", state.zone_tp[3], " sl=", state.zone_sl[3],
|
||||
" | T4 zone=", state.zone_low[4], "-", state.zone_high[4],
|
||||
" tp=", state.zone_tp[4], " sl=", state.zone_sl[4]);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,385 @@
|
||||
#ifndef HIT_PYTHON_SIGNAL_GATEWAY_MQH
|
||||
#define HIT_PYTHON_SIGNAL_GATEWAY_MQH
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 最新のOHLCデータを取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 指定時間足のOHLCデータを取得します。
|
||||
*
|
||||
* @param times 取得したバー時刻を格納する配列。
|
||||
* @param open_prices 取得したOpen価格を格納する配列。
|
||||
* @param high_prices 取得したHigh価格を格納する配列。
|
||||
* @param low_prices 取得したLow価格を格納する配列。
|
||||
* @param close_prices 取得したClose価格を格納する配列。
|
||||
* @param tf 取得対象の時間足。
|
||||
* @param bars_count 取得するバー本数。
|
||||
* @return 取得に成功した場合はtrue、失敗した場合はfalse。
|
||||
*
|
||||
* `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。
|
||||
*/
|
||||
bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count)
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(open_prices, 0);
|
||||
ArrayResize(high_prices, 0);
|
||||
ArrayResize(low_prices, 0);
|
||||
ArrayResize(close_prices, 0);
|
||||
|
||||
if(bars_count < 2)
|
||||
return false;
|
||||
|
||||
// OHLCデータを取得
|
||||
// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。
|
||||
// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。
|
||||
MqlRates rates[];
|
||||
int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates);
|
||||
if(copied <= 0)
|
||||
{
|
||||
Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
ArrayResize(times, copied);
|
||||
ArrayResize(open_prices, copied);
|
||||
ArrayResize(high_prices, copied);
|
||||
ArrayResize(low_prices, copied);
|
||||
ArrayResize(close_prices, copied);
|
||||
|
||||
// 必要なデータを配列に格納
|
||||
for(int i = 0; i < copied; i++)
|
||||
{
|
||||
times[i] = rates[i].time;
|
||||
open_prices[i] = rates[i].open;
|
||||
high_prices[i] = rates[i].high;
|
||||
low_prices[i] = rates[i].low;
|
||||
close_prices[i] = rates[i].close;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| "ohlc.csv"を出力する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief OHLC配列をPython入力用CSVとして出力します。
|
||||
*
|
||||
* @param filename 出力するCSVファイル名。
|
||||
* @param times バー時刻配列。
|
||||
* @param open_prices Open価格配列。
|
||||
* @param high_prices High価格配列。
|
||||
* @param low_prices Low価格配列。
|
||||
* @param close_prices Close価格配列。
|
||||
* @return CSV出力に成功した場合はtrue。
|
||||
*/
|
||||
bool RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[])
|
||||
{
|
||||
// 出力先ファイル名
|
||||
// string filename = "ohlc.csv";
|
||||
|
||||
// ファイルを "書き込みモード" でオープン (テキスト/ANSI)
|
||||
int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI);
|
||||
|
||||
// ファイルが開けたかチェック
|
||||
if(fileHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print(__FUNCTION__, " : Failed to open file: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提)
|
||||
int size = ArraySize(times);
|
||||
|
||||
// ヘッダー行を追加
|
||||
FileWrite(fileHandle, "Time,Open,High,Low,Close");
|
||||
|
||||
// 小数点の桁数を取得
|
||||
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
|
||||
// フォーマット文字列を動的に生成
|
||||
string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits);
|
||||
|
||||
// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
// 時間をフォーマット
|
||||
string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES);
|
||||
|
||||
// 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整)
|
||||
string line = StringFormat(formatString,
|
||||
timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]);
|
||||
|
||||
// ファイルに書き込み (改行付き)
|
||||
FileWrite(fileHandle, line);
|
||||
}
|
||||
|
||||
// 書き込み終了後、ファイルを閉じる
|
||||
FileClose(fileHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| バッチファイル(Pythonスクリプト)を実行する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H4トレンド判定用バッチファイルを起動します。
|
||||
*
|
||||
* @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。
|
||||
*
|
||||
* 起動直前に`process_done_trend.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。
|
||||
*/
|
||||
bool ExecuteBatchTrend()
|
||||
{
|
||||
if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process))
|
||||
return false;
|
||||
|
||||
DeleteDoneFile(done_trend_file);
|
||||
return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| |
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1エントリー価格生成用バッチファイルを起動します。
|
||||
*
|
||||
* @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。
|
||||
*
|
||||
* 起動直前に`process_done_entry.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。
|
||||
*/
|
||||
bool ExecuteBatchEntry()
|
||||
{
|
||||
if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process))
|
||||
return false;
|
||||
|
||||
DeleteDoneFile(done_entry_file);
|
||||
return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process);
|
||||
}
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| "ohlc.csv"を出力後、バッチファイルの実行する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。
|
||||
*
|
||||
* @param state EA全体の状態。トレンド読込待ちフラグを更新します。
|
||||
* @return CSV出力とバッチ起動が開始できた場合はtrue。
|
||||
*/
|
||||
bool RecordOHLCAndExecuteBatch_Trend(EAState &state)
|
||||
{
|
||||
if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process))
|
||||
{
|
||||
Print("Trend Python is still running. Skip H4 CSV update.");
|
||||
return false;
|
||||
}
|
||||
|
||||
datetime times[];
|
||||
double open_prices[], high_prices[], low_prices[], close_prices[];
|
||||
|
||||
if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices,
|
||||
PERIOD_H4, HISTORY_BARS))
|
||||
{ Print("GetLatestOHLC(H4) failed."); return false; }
|
||||
|
||||
if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices))
|
||||
{ Print("RecordOHLC(H4) failed."); return false; }
|
||||
|
||||
if(!ExecuteBatchTrend())
|
||||
return false;
|
||||
|
||||
state.load_trend_flg = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| |
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。
|
||||
*
|
||||
* @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。
|
||||
* @return CSV出力とバッチ起動が開始できた場合はtrue。
|
||||
*/
|
||||
bool RecordOHLCAndExecuteBatch_Entry(EAState &state)
|
||||
{
|
||||
if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process))
|
||||
{
|
||||
Print("Entry Python is still running. Skip H1 CSV update.");
|
||||
return false;
|
||||
}
|
||||
|
||||
datetime times[];
|
||||
double open_prices[], high_prices[], low_prices[], close_prices[];
|
||||
|
||||
if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices,
|
||||
PERIOD_H1, HISTORY_BARS))
|
||||
{ Print("GetLatestOHLC(H1) failed."); return false; }
|
||||
|
||||
if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices))
|
||||
{ Print("RecordOHLC(H1) failed."); return false; }
|
||||
|
||||
if(!ExecuteBatchEntry())
|
||||
return false;
|
||||
|
||||
state.load_target_flg = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| |
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。
|
||||
*
|
||||
* @param state EA全体の状態。H4 market_stateと更新フラグを更新します。
|
||||
* @return 現状は常にtrue。
|
||||
*/
|
||||
bool GetTrendState(EAState &state)
|
||||
{
|
||||
bool ProcessDone = CheckDoneFile(done_trend_file);
|
||||
if(ProcessDone && g_ea.load_trend_flg)
|
||||
{
|
||||
int trend_state;
|
||||
LoadTrendState(trend_state);
|
||||
state.trend_state = trend_state;
|
||||
Print("market_state: ", state.trend_state, " (", MarketStateName(state.trend_state), ")");
|
||||
g_ea.load_trend_flg = false;
|
||||
g_ea.last_trend_update = TimeLocal();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| |
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief `trend_state.txt` からH4 market_state判定値を読み込みます。
|
||||
*
|
||||
* @param trend_state 読み込んだ値を格納する参照。0..5のmarket_state。6は技術エラー停止。
|
||||
*
|
||||
* ファイル未存在、読込失敗、異常値の場合は安全側として6(技術エラー停止)を設定します。
|
||||
*/
|
||||
void LoadTrendState(int &trend_state)
|
||||
{
|
||||
string filename = trend_state_file;
|
||||
trend_state = MARKET_TECHNICAL_ERROR_STOP;
|
||||
|
||||
if(FileIsExist(filename))
|
||||
{
|
||||
int filehandle = FileOpen(filename, FILE_READ | FILE_TXT);
|
||||
if(filehandle != INVALID_HANDLE)
|
||||
{
|
||||
string line = FileReadString(filehandle);
|
||||
int value = (int)StringToInteger(line);
|
||||
if(value >= MARKET_LOW_VOL_RANGE && value <= MARKET_TECHNICAL_ERROR_STOP)
|
||||
trend_state = value;
|
||||
else
|
||||
Print("Invalid market_state value: ", line, ". Use technical error stop(6).");
|
||||
FileClose(filehandle);
|
||||
}
|
||||
else
|
||||
{ Print("Failed to open trend_state.txt"); trend_state = MARKET_TECHNICAL_ERROR_STOP; }
|
||||
}
|
||||
else
|
||||
{ Print("trend_state.txt not found"); trend_state = MARKET_TECHNICAL_ERROR_STOP; }
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ターゲット価格を取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。
|
||||
*
|
||||
* @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。
|
||||
* @return 現状は常にtrue。
|
||||
*/
|
||||
bool GetTargetPrices(EAState &state)
|
||||
{
|
||||
bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更
|
||||
if(ProcessDone && g_ea.load_target_flg) // ←変更
|
||||
{
|
||||
double target_prices[];
|
||||
LoadTargetPrices(target_prices);
|
||||
|
||||
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
|
||||
// 先頭は res_chk
|
||||
state.res_chk = (int)target_prices[0];
|
||||
|
||||
// ★ 1..4 の (en,tp,sl) を読む:合計12個
|
||||
for(int t=1; t<=4; t++)
|
||||
{
|
||||
int base = 1 + (t-1)*3; // 1,4,7,10
|
||||
state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits);
|
||||
state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits);
|
||||
state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits);
|
||||
}
|
||||
|
||||
// ログ(任意)
|
||||
Print("target_prices: res=", state.res_chk,
|
||||
" | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1],
|
||||
" | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2],
|
||||
" | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3],
|
||||
" | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]);
|
||||
|
||||
g_ea.load_target_flg = false; // ←変更
|
||||
g_ea.last_target_update = TimeLocal(); // ←変更
|
||||
g_ea.target_loaded_at = TimeCurrent();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
//+------------------------------------------------------------------+
|
||||
//| "target_prices.txt"を読み込む関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief `target_prices.txt` から13個の数値を読み込みます。
|
||||
*
|
||||
* @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。
|
||||
*
|
||||
* 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。
|
||||
*/
|
||||
void LoadTargetPrices(double &target_prices[])
|
||||
{
|
||||
ArrayResize(target_prices, TARGET_SIZE);
|
||||
for(int i = 0; i < TARGET_SIZE; i++)
|
||||
target_prices[i] = DEFAULT_TARGET_PRICE;
|
||||
|
||||
string filename = target_prices_file;
|
||||
|
||||
if(FileIsExist(filename))
|
||||
{
|
||||
int filehandle = FileOpen(filename, FILE_READ | FILE_TXT);
|
||||
if(filehandle != INVALID_HANDLE)
|
||||
{
|
||||
int i = 0;
|
||||
while(!FileIsEnding(filehandle) && i < TARGET_SIZE)
|
||||
{
|
||||
string line = FileReadString(filehandle);
|
||||
target_prices[i] = StringToDouble(line);
|
||||
i++;
|
||||
}
|
||||
FileClose(filehandle);
|
||||
|
||||
if(i < TARGET_SIZE)
|
||||
{
|
||||
Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE);
|
||||
target_prices[0] = 0; // 不完全なファイルは無効扱い
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open file. Error code: ", GetLastError());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("File does not exist: ", filename);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user