Creation of Atr haldle manger and other methods

This commit is contained in:
Matt Corcoran
2025-07-11 16:30:06 +02:00
parent 7de4cf3cb6
commit ec3fc120ce
9 changed files with 516 additions and 367 deletions
+123 -45
View File
@@ -1,13 +1,20 @@
#include <Trade/Trade.mqh>
#include <MyLibs/utils/AtrHandleManager.mqh>
CTrade trade;
AtrHandleManager atr_manager;
class AdjustPosition { class AdjustPosition {
public: public:
void set_breakeven_sl(string symbol, int runner_magic_no, double buffer_points = 5); void set_breakeven_sl(string symbol, int runner_magic_no, double buffer_points = 5);
void set_breakeven_sl_if_tp_crossed(string symbol, int runner_magic_no, double buffer_points = 5); void set_breakeven_if_profit_target_hit(string symbol, int runner_magic_no, double buffer_points = 5);
void set_fixed_sl(string symbol, int runner_magic_no, double fixed_sl_price); void set_fixed_sl(string symbol, int runner_magic_no, double fixed_sl_price);
void set_trailing_sl(string symbol, int runner_magic_no, double sl_offset_points = 5); void set_trailing_sl(string symbol, int runner_magic_no, double sl_offset_points = 5);
void trailing_stop_atr(string symbol, int magic_number, ENUM_TIMEFRAMES tf = PERIOD_CURRENT, double activation_mult = 1.0,
double trail_mult = 1.0, int atr_period = 14, bool use_bar_close = false);
private: private:
void set_breakeven_sl_for_ticket(string symbol, ulong ticket, long order_type, double entry_price, double current_sl, double current_tp, void set_breakeven_sl_for_ticket(string symbol, ulong ticket, long order_type, double entry_price, double current_sl, double current_tp, int digits, double buffer_price, bool remove_tp);
int digits, double buffer_price, bool remove_tp);
}; };
// --------------------------------------------------------- // ---------------------------------------------------------
@@ -32,65 +39,136 @@ void AdjustPosition::set_breakeven_sl(string symbol, int runner_magic_no, double
} }
// --------------------------------------------------------- // ---------------------------------------------------------
/** // Check runner trades for virtual TP hits and set SL to breakeven if crossed.
* Check runner trades for virtual take-profit hits and set SL to breakeven if crossed. // Optimized to avoid unnecessary processing on every OnTimer()/OnTick() call.
* void AdjustPosition::set_breakeven_if_profit_target_hit(string symbol, int runner_magic_no, double buffer_points) {
* Runner trades are assumed to be opened with no TP and a special comment in the format: "runner_tp:1.10500".
* This method parses that comment to determine the virtual TP, and if price has crossed it, moves SL to breakeven ± buffer. // --- Get current bid/ask and symbol precision
* double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
* param symbol: Trading symbol to check positions for double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
* param runner_magic_no: Magic number assigned to runner trades int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
* param buffer_points: Buffer to add/subtract from entry price when setting SL (in points, converted internally) double buffer_price = buffer_points * _Point;
*/ double price_margin = 50 * _Point; // avoid premature checking if far from TP
void AdjustPosition::set_breakeven_sl_if_tp_crossed(string symbol, int runner_magic_no, double buffer_points) {
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); // Current ask price (for sell comparisons)
double bid = SymbolInfoDouble(symbol, SYMBOL_BID); // Current bid price (for buy comparisons)
int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); // Symbol's decimal precision
double buffer_price = buffer_points * _Point; // Convert buffer from points to price
// --- Fast check: skip if no runner trades exist for this symbol
bool has_runner = false;
for (int i = PositionsTotal() - 1; i >= 0; i--) { for (int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i); // Get the position ticket ulong ticket = PositionGetTicket(i);
if (!PositionSelectByTicket(ticket)) continue; // Select the position if (!PositionSelectByTicket(ticket)) continue;
if (PositionGetString(POSITION_SYMBOL) != symbol) continue; // Only process positions for this symbol if (PositionGetString(POSITION_SYMBOL) != symbol) continue;
if ((int) PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue; // Ensure it matches runner magic if ((int)PositionGetInteger(POSITION_MAGIC) == runner_magic_no) {
has_runner = true;
break;
}
}
if (!has_runner) return;
// --- Get position details --- // --- Loop through positions for breakeven SL
long order_type = PositionGetInteger(POSITION_TYPE); // POSITION_TYPE_BUY or POSITION_TYPE_SELL for (int i = PositionsTotal() - 1; i >= 0; i--) {
double entry = PositionGetDouble(POSITION_PRICE_OPEN); // Entry price of the trade ulong ticket = PositionGetTicket(i);
double sl = PositionGetDouble(POSITION_SL); // Current stop loss if (!PositionSelectByTicket(ticket)) continue;
double tp = PositionGetDouble(POSITION_TP); // Should be 0 for runners if (PositionGetString(POSITION_SYMBOL) != symbol) continue;
string comment = PositionGetString(POSITION_COMMENT); // Read comment to check for virtual TP if ((int)PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue;
// --- Parse virtual TP from comment --- long order_type = PositionGetInteger(POSITION_TYPE);
// Format expected: "runner_tp:1.10500" double entry = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
string comment = PositionGetString(POSITION_COMMENT);
// --- Parse virtual TP from comment: expected format "runner_tp:1.10500"
double virtual_tp = 0.0; double virtual_tp = 0.0;
if (StringFind(comment, "runner_tp:") == 0) { if (StringFind(comment, "runner_tp:") == 0) {
string tp_str = StringSubstr(comment, StringLen("runner_tp:")); // Extract number part string tp_str = StringSubstr(comment, StringLen("runner_tp:"));
virtual_tp = StringToDouble(tp_str); // Convert to double virtual_tp = StringToDouble(tp_str);
} }
// Skip if no virtual TP was found or invalid if (virtual_tp <= 0.0) continue; // no valid virtual TP
if (virtual_tp <= 0.0) continue;
// Warn if a TP is still set (runner should not have one)
if (tp > 0.0) { if (tp > 0.0) {
PrintFormat("Warning: Runner trade on %s (ticket %d) has a non-zero TP: %.5f (should be 0)", symbol, ticket, tp); PrintFormat("Warning: Runner trade on %s (ticket %d) has TP set: %.5f", symbol, ticket, tp);
} }
// --- Check if price has hit virtual TP --- // --- Skip early if price not near virtual TP
if (order_type == POSITION_TYPE_BUY && bid < virtual_tp - price_margin) continue;
if (order_type == POSITION_TYPE_SELL && ask > virtual_tp + price_margin) continue;
// --- Check if virtual TP was hit
bool tp_hit = false; bool tp_hit = false;
if (order_type == POSITION_TYPE_BUY && bid >= virtual_tp) // For buys, bid must be ≥ virtual TP if (order_type == POSITION_TYPE_BUY && bid >= virtual_tp) tp_hit = true;
tp_hit = true; if (order_type == POSITION_TYPE_SELL && ask <= virtual_tp) tp_hit = true;
if (order_type == POSITION_TYPE_SELL && ask <= virtual_tp) // For sells, ask must be ≤ virtual TP
tp_hit = true;
if (!tp_hit) continue; if (!tp_hit) continue;
// --- Set breakeven SL if TP was hit --- // Calculate expected breakeven SL
// Move SL to entry ± buffer. Leave TP unchanged (but should be 0.0 at init for runners) double expected_sl = (order_type == POSITION_TYPE_BUY)
? entry + buffer_price
: entry - buffer_price;
// Skip if SL already set to breakeven
if (NormalizeDouble(sl, digits) == NormalizeDouble(expected_sl, digits)) continue;
// --- Set breakeven SL
set_breakeven_sl_for_ticket(symbol, ticket, order_type, entry, sl, tp, digits, buffer_price, true); set_breakeven_sl_for_ticket(symbol, ticket, order_type, entry, sl, tp, digits, buffer_price, true);
} }
} }
// ---------------------------------------------------------------------
// TRAILING STOP ATR LOGIC
// ---------------------------------------------------------------------
// This function updates the stop-loss of runner trades based on ATR.
// It only applies to trades with the given magic number and symbol.
//
// Parameters:
// - symbol : The trading symbol.
// - _magic_number : Magic number to identify trades.
// - tf : Timeframe used for ATR calculation.
// - activation_mult: Multiplier to determine when to activate trailing.
// - trail_mult : Multiplier to determine trailing distance.
// - atr_period : ATR period to use.
// - use_bar_close : If true, use bar close instead of live price.
//
// Logic:
// - Trailing starts only after activation distance is reached.
// - SL is only updated if it moves closer to price (i.e., improves).
// ---------------------------------------------------------------------
void AdjustPosition::trailing_stop_atr(string symbol, int _magic_number, ENUM_TIMEFRAMES tf, double activation_mult, double trail_mult, int atr_period, bool use_bar_close) {
double atr = atr_manager.get_atr_value(symbol, tf, atr_period);
if (atr == EMPTY_VALUE) return;
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS);
for (int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
if (!PositionSelectByTicket(ticket)) continue;
if (PositionGetString(POSITION_SYMBOL) != symbol) continue;
if ((int) PositionGetInteger(POSITION_MAGIC) != _magic_number) continue;
long type = PositionGetInteger(POSITION_TYPE);
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double price = use_bar_close ? iClose(symbol, tf, 1) : (type == POSITION_TYPE_BUY ? bid : ask);
double trail_distance = atr * trail_mult;
double activation_distance = atr * activation_mult;
bool should_trail = (type == POSITION_TYPE_BUY && price >= entry + activation_distance) || (type == POSITION_TYPE_SELL && price <= entry - activation_distance);
if (!should_trail) continue;
double new_sl = (type == POSITION_TYPE_BUY) ? price - trail_distance : price + trail_distance;
new_sl = NormalizeDouble(new_sl, digits);
if ((type == POSITION_TYPE_BUY && sl >= new_sl) || (type == POSITION_TYPE_SELL && sl <= new_sl)) continue;
if (!trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP)))
PrintFormat("Trailing SL update failed for %s ticket=%d", symbol, ticket);
else
PrintFormat("Trailing SL updated: %s ticket=%d new SL=%.5f", symbol, ticket, new_sl);
}
}
// --------------------------------------------------------- // ---------------------------------------------------------
void AdjustPosition::set_breakeven_sl_for_ticket(string symbol, ulong ticket, long order_type, double entry_price, double current_sl, void AdjustPosition::set_breakeven_sl_for_ticket(string symbol, ulong ticket, long order_type, double entry_price, double current_sl,
double current_tp, int digits, double buffer_price, bool remove_tp) { double current_tp, int digits, double buffer_price, bool remove_tp) {
@@ -112,7 +190,7 @@ void AdjustPosition::set_breakeven_sl_for_ticket(string symbol, ulong ticket, lo
if (!OrderSend(request, result)) { if (!OrderSend(request, result)) {
Print("Failed to adjust runner: ", symbol, ". Error: ", result.retcode); Print("Failed to adjust runner: ", symbol, ". Error: ", result.retcode);
} else if (remove_tp) { } else if (remove_tp) {
Print("Runner upgraded to trailing: SL at breakeven, TP removed for ", symbol); Print("Runner upgraded to trailing: SL at breakeven, TP removed for ", symbol);
} }
} }
+72 -200
View File
@@ -1,303 +1,175 @@
#include <MyLibs/Utils/MarketDataUtils.mqh> #include <MyLibs/Utils/MarketDataUtils.mqh>
#include <MyLibs/Utils/TimeZones.mqh> #include <MyLibs/Utils/TimeZones.mqh>
#include <MyLibs/Utils/AtrHandleManager.mqh>
#include <Trade/Trade.mqh> #include <Trade/Trade.mqh>
class CalculatePositionData : public CObject { class CalculatePositionData : public CObject {
protected: protected:
CTrade trade; CTrade trade;
CPositionInfo position; CPositionInfo position;
MarketDataUtils mdu; MarketDataUtils mdu;
AtrHandleManager atr_manager;
bool check_lots(double& lots, string symbol); bool check_lots(double& lots, string symbol);
bool normalise_price(double price, double& normalizedPrice, string symbol); bool normalise_price(double price, double& normalizedPrice, string symbol);
public: public:
double calculate_stoploss(string symbol, double price, int order_side, string _sl_mode, double sl_var, ENUM_TIMEFRAMES atr_period); double calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_tf);
double calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double tp_var, double calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double tp_var, ENUM_TIMEFRAMES atr_tf);
ENUM_TIMEFRAMES atr_period);
double calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var); double calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var);
double calculate_trading_cost(string symbol, ulong position_ticket); double calculate_trading_cost(string symbol, ulong ticket);
}; };
double CalculatePositionData::calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, //+------------------------------------------------------------------+
ENUM_TIMEFRAMES atr_period) {
// order_side int must be 1 for BUY or 2 for
double CalculatePositionData::calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_tf) {
double sl = 0; double sl = 0;
if (mode_sl == "NO_STOPLOSS") { if (mode_sl == "NO_STOPLOSS") return 0;
sl = 0;
}
if (mode_sl == "SL_BREAKEVEN") {
// https://www.youtube.com/watch?v=idPulZ3_iR0
Alert("Not implemented yet yet");
}
if (mode_sl == "SL_FIXED_PIPS") { if (mode_sl == "SL_FIXED_PIPS") {
// pips/poins = https://www.mql5.com/en/forum/187757
double adj_point = mdu.adjusted_point(symbol); double adj_point = mdu.adjusted_point(symbol);
sl = (order_side == 1) ? price - sl_var * adj_point : price + sl_var * adj_point;
if (order_side == 1) { if (!normalise_price(sl, sl, symbol)) return 0;
sl = price - sl_var * adj_point;
if (!normalise_price(sl, sl, symbol)) {
return false;
}
}
if (order_side == 2) {
sl = price + sl_var * adj_point;
if (!normalise_price(sl, sl, symbol)) {
return false;
}
}
} }
if (mode_sl == "SL_FIXED_PERCENT") { if (mode_sl == "SL_FIXED_PERCENT") {
if (order_side == 1) { sl = (order_side == 1) ? price - (sl_var * price / 100.0) : price + (sl_var * price / 100.0);
sl = (-1.0 * sl_var * price / 100.00) + price; if (!normalise_price(sl, sl, symbol)) return 0;
if (!normalise_price(sl, sl, symbol)) {
return false;
}
}
if (order_side == 2) {
sl = sl_var * price / 100.00 + price;
if (!normalise_price(sl, sl, symbol)) {
return false;
}
}
} }
if (mode_sl == "SL_ATR_MULTIPLE") { if (mode_sl == "SL_ATR_MULTIPLE") {
int _atr_handle = iATR(symbol, atr_period, 14); double atr = atr_manager.get_atr_value(symbol, atr_tf, 14);
double atr[]; if (atr == EMPTY_VALUE) return 0;
ArraySetAsSeries(atr, true); sl = (order_side == 1) ? price - atr * sl_var : price + atr * sl_var;
CopyBuffer(_atr_handle, MAIN_LINE, 1, 1, atr); if (!normalise_price(sl, sl, symbol)) return 0;
if (order_side == 1) {
sl = price - (atr[0] * sl_var);
if (!normalise_price(sl, sl, symbol)) {
return false;
}
}
if (order_side == 2) {
sl = price + (atr[0] * sl_var);
if (!normalise_price(sl, sl, symbol)) {
return false;
}
}
} }
if (mode_sl == "SL_SPECIFIED_VALUE") { if (mode_sl == "SL_SPECIFIED_VALUE") {
double adj_point = mdu.adjusted_point(symbol); double adj_point = mdu.adjusted_point(symbol);
double limit_sl = (order_side == 1) ? price - 10 * adj_point : price + 10 * adj_point;
if (order_side == 1) { sl = (order_side == 1) ? fmax(sl_var, limit_sl) : fmin(sl_var, limit_sl);
double pip_50_sl = price - 10 * adj_point; if (!normalise_price(sl, sl, symbol)) return 0;
if (sl_var >= pip_50_sl) {
sl = pip_50_sl;
} else
sl = sl_var;
if (!normalise_price(sl, sl, symbol)) {
return false;
}
}
if (order_side == 2) {
double pip_50_sl = price + 10 * adj_point;
if (sl_var <= pip_50_sl) {
sl = pip_50_sl;
} else
sl = sl_var;
sl = sl = sl_var;
if (!normalise_price(sl, sl, symbol)) {
return false;
}
}
} }
return sl; return sl;
} }
double CalculatePositionData::calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, //+------------------------------------------------------------------+
double _tp_var, ENUM_TIMEFRAMES atr_period) {
// order_side int must be 1 for BUY or 2 for SELL
double CalculatePositionData::calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double tp_var, ENUM_TIMEFRAMES atr_tf) {
double tp = 0; double tp = 0;
if (mode_tp == "NO_TAKE_PROFIT") { if (mode_tp == "NO_TAKE_PROFIT") return 0;
tp = 0;
}
if (mode_tp == "TP_FIXED_PIPS") { if (mode_tp == "TP_FIXED_PIPS") {
double adj_point = mdu.adjusted_point(symbol); double adj_point = mdu.adjusted_point(symbol);
if (order_side == 1) { tp = (order_side == 1) ? price + tp_var * adj_point : price - tp_var * adj_point;
tp = price + _tp_var * adj_point; if (!normalise_price(tp, tp, symbol)) return 0;
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
if (order_side == 2) {
tp = price - _tp_var * adj_point;
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
} }
if (mode_tp == "TP_FIXED_PERCENT") { if (mode_tp == "TP_FIXED_PERCENT") {
if (order_side == 1) { tp = (order_side == 1) ? price + tp_var * price / 100.0 : price - tp_var * price / 100.0;
tp = _tp_var * price / 100.00 + price; if (!normalise_price(tp, tp, symbol)) return 0;
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
if (order_side == 2) {
tp = (-1 * _tp_var * price / 100.00) + price;
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
} }
if (mode_tp == "TP_ATR_MULTIPLE") { if (mode_tp == "TP_ATR_MULTIPLE") {
int _atr_handle = iATR(symbol, atr_period, 14); double atr = atr_manager.get_atr_value(symbol, atr_tf, 14);
double atr[]; if (atr == EMPTY_VALUE) return 0;
ArraySetAsSeries(atr, true); tp = (order_side == 1) ? price + atr * tp_var : price - atr * tp_var;
CopyBuffer(_atr_handle, MAIN_LINE, 1, 1, atr); if (!normalise_price(tp, tp, symbol)) return 0;
if (order_side == 1) {
tp = price + (atr[0] * _tp_var);
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
if (order_side == 2) {
tp = price - (atr[0] * _tp_var);
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
} }
if (mode_tp == "TP_SL_MULTIPLE") { if (mode_tp == "TP_SL_MULTIPLE") {
if (order_side == 1) { double sl_size = (order_side == 1) ? price - stoploss : stoploss - price;
double sl_size = price - stoploss; tp = (order_side == 1) ? price + tp_var * sl_size : price - tp_var * sl_size;
tp = price + (_tp_var * sl_size); if (!normalise_price(tp, tp, symbol)) return 0;
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
if (order_side == 2) {
double sl_size = stoploss - price;
tp = price - (_tp_var * sl_size);
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
} }
if (mode_tp == "TP_SPECIFIED_VALUE") { if (mode_tp == "TP_SPECIFIED_VALUE") {
if (_tp_var != 0) { double adj_point = mdu.adjusted_point(symbol);
double adj_point = mdu.adjusted_point(symbol); double limit_tp = (order_side == 1) ? price + 10 * adj_point : price - 10 * adj_point;
tp = (order_side == 1) ? fmin(tp_var, limit_tp) : fmax(tp_var, limit_tp);
if (order_side == 1) { if (!normalise_price(tp, tp, symbol)) return 0;
double pip_limit = price + 10 * adj_point;
if (_tp_var <= pip_limit) {
tp = pip_limit;
} else
tp = _tp_var;
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
if (order_side == 2) {
double pip_limit = price - 10 * adj_point;
if (_tp_var >= pip_limit) {
tp = pip_limit;
} else
tp = _tp_var;
tp = tp = _tp_var;
if (!normalise_price(tp, tp, symbol)) {
return false;
}
}
}
} }
return tp; return tp;
} }
//+------------------------------------------------------------------+
double CalculatePositionData::calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var) { double CalculatePositionData::calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var) {
double lots = 0; double lots = 0;
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
double account_value = double account_value = fmin(fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)), AccountInfoDouble(ACCOUNT_MARGIN_FREE));
fmin(fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)), AccountInfoDouble(ACCOUNT_MARGIN_FREE)); double risk_money = account_value * lot_var / 100.0;
double risk_money = account_value * lot_var / 100;
if (mode_lot == "LOT_MODE_FIXED") { if (mode_lot == "LOT_MODE_FIXED") {
lots = lot_var; lots = lot_var;
} }
if (mode_lot == "LOT_MODE_PCT_RISK") { if (mode_lot == "LOT_MODE_PCT_RISK") {
double money_lot_step = (sl_distance / tick_size) * tick_value * volume_step; double money_per_step = (sl_distance / tick_size) * tick_value * volume_step;
lots = MathFloor(risk_money / money_lot_step) * volume_step; lots = MathFloor(risk_money / money_per_step) * volume_step;
} }
if (mode_lot == "LOT_MODE_PCT_ACCOUNT") { if (mode_lot == "LOT_MODE_PCT_ACCOUNT") {
double money_lot_step = (price / tick_size) * tick_value * volume_step; double money_per_step = (price / tick_size) * tick_value * volume_step;
lots = MathFloor(risk_money / money_lot_step) * volume_step; lots = MathFloor(risk_money / money_per_step) * volume_step;
} }
if (!check_lots(lots, symbol)) { if (!check_lots(lots, symbol)) return 0;
return false;
}
return lots; return lots;
} }
//+------------------------------------------------------------------+
bool CalculatePositionData::check_lots(double& lots, string symbol) { bool CalculatePositionData::check_lots(double& lots, string symbol) {
double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if (lots < min) { if (lots < min) {
Print("Lot size will be set to minimum allowed volume");
lots = min; lots = min;
return true; return true;
} }
if (lots > max) { if (lots > max) {
Print("Lot size greater than maximum allowed volume. lots:", lots, "max:", max); Print("Lot size exceeds max for ", symbol);
return false; return false;
} }
lots = (int) MathFloor(lots / step) * step; lots = (int)MathFloor(lots / step) * step;
return true; return true;
} }
//+------------------------------------------------------------------+
bool CalculatePositionData::normalise_price(double price, double& normalizedPrice, string symbol) { bool CalculatePositionData::normalise_price(double price, double& normalizedPrice, string symbol) {
double tickSize; double tick_size;
if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE, tickSize)) { if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE, tick_size)) {
Print("Failed to get tick size"); Print("Failed to get tick size for ", symbol);
return false; return false;
} }
int symbol_digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS);
normalizedPrice = NormalizeDouble(MathRound(price / tickSize) * tickSize, symbol_digits); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
normalizedPrice = NormalizeDouble(MathRound(price / tick_size) * tick_size, digits);
return true; return true;
} }
double CalculatePositionData::calculate_trading_cost(string symbol, ulong position_ticket) { //+------------------------------------------------------------------+
position.SelectByTicket(position_ticket);
double swap = PositionGetDouble(POSITION_SWAP); // double CalculatePositionData::calculate_trading_cost(string symbol, ulong ticket) {
double commission = PositionGetDouble(POSITION_COMMISSION); // position.SelectByTicket(ticket);
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
double lots = PositionGetDouble(POSITION_VOLUME);
double trading_cost = -1 * ((commission + swap) / tick_value * tick_size / lots);
return trading_cost; // double swap = PositionGetDouble(POSITION_SWAP);
} // double commission = PositionGetDouble(POSITION_COMMISSION);
// double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
// double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
// double lots = PositionGetDouble(POSITION_VOLUME);
// return -1.0 * ((commission + swap) / tick_value * tick_size / lots);
// }
+55 -17
View File
@@ -2,11 +2,11 @@
#include <Trade/Trade.mqh> #include <Trade/Trade.mqh>
class EntryOrders { class EntryOrders {
protected: protected:
CTrade trade; CTrade trade;
CalculatePositionData calc; CalculatePositionData calc;
public: public:
int count_open_positions(string symbol, int order_side, long _magic_number); int count_open_positions(string symbol, int order_side, long _magic_number);
bool open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, bool open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode,
@@ -15,11 +15,11 @@ class EntryOrders {
bool open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, bool open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode,
double tp_var, string _lot_mode, double lot_var, long _magic_number); double tp_var, string _lot_mode, double lot_var, long _magic_number);
bool open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, bool open_buy_stop_order(string symbol, bool condition, double entry_price, datetime expiration, ENUM_TIMEFRAMES atr_period,
string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,
long _magic_number); long _magic_number);
bool open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, bool open_sell_stop_order(string symbol, bool condition, double entry_price, datetime expiration, ENUM_TIMEFRAMES atr_period,
string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,
long _magic_number); long _magic_number);
@@ -28,6 +28,7 @@ class EntryOrders {
bool open_runner_sell_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, bool open_runner_sell_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var,
string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number); string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number);
}; };
int EntryOrders::count_open_positions(string symbol, int order_side, long _magic_number) { int EntryOrders::count_open_positions(string symbol, int order_side, long _magic_number) {
@@ -36,8 +37,6 @@ int EntryOrders::count_open_positions(string symbol, int order_side, long _magic
ulong ticket = PositionGetTicket(i); ulong ticket = PositionGetTicket(i);
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) { if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) {
int type = (int) PositionGetInteger(POSITION_TYPE); int type = (int) PositionGetInteger(POSITION_TYPE);
// Count: order_side == 0 (both sides), order_side == 1 (longs only), order_side == 2 (shorts only)
if (order_side == 0 || (order_side == 1 && type == POSITION_TYPE_BUY) || (order_side == 2 && type == POSITION_TYPE_SELL)) { if (order_side == 0 || (order_side == 1 && type == POSITION_TYPE_BUY) || (order_side == 2 && type == POSITION_TYPE_SELL)) {
count++; count++;
} }
@@ -49,7 +48,6 @@ int EntryOrders::count_open_positions(string symbol, int order_side, long _magic
bool EntryOrders::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, bool EntryOrders::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var,
string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) { string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) {
if (!condition) return false; if (!condition) return false;
double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK);
if (count_open_positions(symbol, 1, _magic_number) > 0) return false; if (count_open_positions(symbol, 1, _magic_number) > 0) return false;
@@ -58,15 +56,21 @@ bool EntryOrders::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES
double sl_distance = current_price - stop_loss; double sl_distance = current_price - stop_loss;
double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var);
if (lots <= 0) {
Print("Lot calculation failed for ", symbol);
return false;
}
trade.SetExpertMagicNumber(_magic_number); trade.SetExpertMagicNumber(_magic_number);
string comment = "Magic Number: " + IntegerToString(_magic_number); string comment = "Magic Number: " + IntegerToString(_magic_number);
return trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, take_profit, comment); bool result = trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, take_profit, comment);
if (!result) Print("Trade open failed for BUY ", symbol);
return result;
} }
bool EntryOrders::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, bool EntryOrders::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var,
string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) { string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) {
if (!condition) return false; if (!condition) return false;
double current_price = SymbolInfoDouble(symbol, SYMBOL_BID); double current_price = SymbolInfoDouble(symbol, SYMBOL_BID);
if (count_open_positions(symbol, 2, _magic_number) > 0) return false; if (count_open_positions(symbol, 2, _magic_number) > 0) return false;
@@ -75,12 +79,19 @@ bool EntryOrders::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAME
double sl_distance = stop_loss - current_price; double sl_distance = stop_loss - current_price;
double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var);
if (lots <= 0) {
Print("Lot calculation failed for ", symbol);
return false;
}
trade.SetExpertMagicNumber(_magic_number); trade.SetExpertMagicNumber(_magic_number);
string comment = "Magic Number: " + IntegerToString(_magic_number); string comment = "Magic Number: " + IntegerToString(_magic_number);
return trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, take_profit, comment); bool result = trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, take_profit, comment);
if (!result) Print("Trade open failed for SELL ", symbol);
return result;
} }
bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entry_price, datetime expiration, ENUM_TIMEFRAMES atr_period,
string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,
long _magic_number) { long _magic_number) {
if (!condition) return false; if (!condition) return false;
@@ -91,12 +102,19 @@ bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entr
double sl_distance = entry_price - stop_loss; double sl_distance = entry_price - stop_loss;
double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var); double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var);
if (lots <= 0) {
Print("Lot calculation failed for BUY STOP ", symbol);
return false;
}
trade.SetExpertMagicNumber(_magic_number); trade.SetExpertMagicNumber(_magic_number);
string comment = "Magic Number: " + IntegerToString(_magic_number); string comment = "Magic Number: " + IntegerToString(_magic_number);
return trade.BuyStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment); bool result = trade.BuyStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, expiration, comment);
if (!result) Print("BuyStop order failed for ", symbol);
return result;
} }
bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double entry_price, datetime expiration, ENUM_TIMEFRAMES atr_period,
string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,
long _magic_number) { long _magic_number) {
if (!condition) return false; if (!condition) return false;
@@ -107,9 +125,16 @@ bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double ent
double sl_distance = stop_loss - entry_price; double sl_distance = stop_loss - entry_price;
double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var); double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var);
if (lots <= 0) {
Print("Lot calculation failed for SELL STOP ", symbol);
return false;
}
trade.SetExpertMagicNumber(_magic_number); trade.SetExpertMagicNumber(_magic_number);
string comment = "Magic Number: " + IntegerToString(_magic_number); string comment = "Magic Number: " + IntegerToString(_magic_number);
return trade.SellStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment); bool result = trade.SellStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, expiration, comment);
if (!result) Print("SellStop order failed for ", symbol);
return result;
} }
bool EntryOrders::open_runner_buy_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, bool EntryOrders::open_runner_buy_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode,
@@ -124,9 +149,16 @@ bool EntryOrders::open_runner_buy_order_with_virtual_tp(string symbol, bool cond
double sl_distance = current_price - stop_loss; double sl_distance = current_price - stop_loss;
double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var);
if (lots <= 0) {
Print("Lot calculation failed for runner BUY ", symbol);
return false;
}
trade.SetExpertMagicNumber(_magic_number); trade.SetExpertMagicNumber(_magic_number);
string comment = StringFormat("runner_tp:%.5f", virtual_tp); string comment = StringFormat("runner_tp:%.5f", virtual_tp);
return trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, 0.0, comment); bool result = trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, 0.0, comment);
if (!result) Print("Runner BUY order failed for ", symbol);
return result;
} }
bool EntryOrders::open_runner_sell_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, bool EntryOrders::open_runner_sell_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode,
@@ -138,11 +170,17 @@ bool EntryOrders::open_runner_sell_order_with_virtual_tp(string symbol, bool con
double stop_loss = calc.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); double stop_loss = calc.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period);
double virtual_tp = calc.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period); double virtual_tp = calc.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
double sl_distance = stop_loss - current_price; double sl_distance = stop_loss - current_price;
double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var);
if (lots <= 0) {
Print("Lot calculation failed for runner SELL ", symbol);
return false;
}
trade.SetExpertMagicNumber(_magic_number); trade.SetExpertMagicNumber(_magic_number);
string comment = StringFormat("runner_tp:%.5f", virtual_tp); string comment = StringFormat("runner_tp:%.5f", virtual_tp);
return trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, 0.0, comment); bool result = trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, 0.0, comment);
if (!result) Print("Runner SELL order failed for ", symbol);
return result;
} }
+63
View File
@@ -0,0 +1,63 @@
#include <Trade/SymbolInfo.mqh>
class AtrHandleManager {
private:
struct AtrEntry {
string symbol;
ENUM_TIMEFRAMES tf;
int period;
int handle;
};
AtrEntry cache[]; // internal cache of ATR handles
public:
// Get or create an ATR handle for a specific symbol/timeframe/period
int get_atr_handle(string symbol, ENUM_TIMEFRAMES tf, int period) {
for (int i = 0; i < ArraySize(cache); i++) {
if (cache[i].symbol == symbol && cache[i].tf == tf && cache[i].period == period)
return cache[i].handle;
}
int handle = iATR(symbol, tf, period);
if (handle == INVALID_HANDLE) {
Print("Failed to create ATR handle for ", symbol);
return INVALID_HANDLE;
}
AtrEntry entry = { symbol, tf, period, handle };
ArrayResize(cache, ArraySize(cache) + 1);
cache[ArraySize(cache) - 1] = entry;
return handle;
}
// Get ATR value from buffer (returns EMPTY_VALUE if failure)
double get_atr_value(string symbol, ENUM_TIMEFRAMES tf, int period, int shift = 1) {
int handle = get_atr_handle(symbol, tf, period);
if (handle == INVALID_HANDLE) {
PrintFormat("Invalid ATR handle for %s (TF=%d, Period=%d)", symbol, tf, period);
return EMPTY_VALUE;
}
double buffer[];
ArraySetAsSeries(buffer, true);
if (CopyBuffer(handle, 0, shift, 1, buffer) != 1 || buffer[0] == EMPTY_VALUE) {
PrintFormat("Failed to read ATR buffer for %s shift=%d", symbol, shift);
return EMPTY_VALUE;
}
return buffer[0];
}
// Release all handles in the cache
void release_handles() {
for (int i = 0; i < ArraySize(cache); i++) {
if (cache[i].handle != INVALID_HANDLE)
IndicatorRelease(cache[i].handle);
}
ArrayResize(cache, 0);
}
};
+39 -12
View File
@@ -7,23 +7,44 @@ public:
double get_bid_ask_price(string symbol, int price_side); double get_bid_ask_price(string symbol, int price_side);
protected: protected:
datetime previousTime; // Stores the last recorded bar open time datetime previousTimes[]; // Stores last recorded open time per key
datetime bar_open_time; // Stores the current bar's open time string bar_keys[]; // Keys are symbol+TF combinations, e.g. "EURUSD_PERIOD_H1"
}; };
// Helper function to find index of a key in an array
int LinearSearch(string &arr[], string target) {
for (int i = 0; i < ArraySize(arr); i++) {
if (arr[i] == target)
return i;
}
return -1; // Not found
}
// Checks if a new bar has opened on the given timeframe and symbol // Checks if a new bar has opened on the given timeframe and symbol
bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) { bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) {
bar_open_time = iTime(symbol, time_frame, 0); // Current open time datetime bar_open_time = iTime(symbol, time_frame, 0); // Current open time
string key = symbol + "_" + EnumToString(time_frame);
if (previousTime != bar_open_time) { int idx = LinearSearch(bar_keys, key);
if (idx == -1) {
int new_size = ArraySize(bar_keys) + 1;
ArrayResize(bar_keys, new_size);
ArrayResize(previousTimes, new_size);
idx = new_size - 1;
bar_keys[idx] = key;
previousTimes[idx] = 0;
}
if (previousTimes[idx] != bar_open_time) {
// For daily timeframe, wait for specific time (e.g., 00:10) before triggering // For daily timeframe, wait for specific time (e.g., 00:10) before triggering
if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) { if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) {
if (TimeCurrent() > StringToTime(daily_start_time)) { if (TimeCurrent() > StringToTime(daily_start_time)) {
previousTime = bar_open_time; previousTimes[idx] = bar_open_time;
return true; return true;
} }
} else { } else {
previousTime = bar_open_time; previousTimes[idx] = bar_open_time;
return true; return true;
} }
} }
@@ -35,17 +56,23 @@ bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, stri
// shift = 1 is the most recently closed candle // shift = 1 is the most recently closed candle
// shift = 2 is the one before that, etc. // shift = 2 is the one before that, etc.
double MarketDataUtils::get_buffer_value(int handle, int shift) { double MarketDataUtils::get_buffer_value(int handle, int shift) {
double val[];
ArraySetAsSeries(val, true);
double val[]; int copied = CopyBuffer(handle, 0, shift, 1, val);
ArraySetAsSeries(val, true); if (copied <= 0) {
Print("CopyBuffer failed: handle=", handle, " shift=", shift);
return EMPTY_VALUE;
}
if (CopyBuffer(handle, 0, shift, 1, val) == 1 && val[0] != EMPTY_VALUE) if (val[0] == EMPTY_VALUE) {
return val[0]; Print("EMPTY_VALUE returned for buffer at shift=", shift);
return EMPTY_VALUE;
}
return EMPTY_VALUE; return val[0];
} }
// Adjusts the point value for symbol to account for fractional pips (e.g., 5-digit brokers) // Adjusts the point value for symbol to account for fractional pips (e.g., 5-digit brokers)
double MarketDataUtils::adjusted_point(string symbol) { double MarketDataUtils::adjusted_point(string symbol) {
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
+56
View File
@@ -0,0 +1,56 @@
#include <MyLibs/Utils/AtrHandleManager.mqh>
//+------------------------------------------------------------------+
//| ResourceManager |
//| |
//| Tracks and releases indicator handles (iMA, iRSI, etc.) |
//| Also delegates ATR handle cleanup to an external ATR manager |
//+------------------------------------------------------------------+
class ResourceManager {
public:
// --- Pointer to external ATR manager (set externally)
// Used for releasing any internally cached ATR handles
AtrHandleManager* atr_manager;
// --- Register a new indicator handle to be released later
// Only valid (non-INVALID_HANDLE) handles are stored
void register_handle(int handle) {
if (handle != INVALID_HANDLE)
add_handle(handle);
}
// --- Release all tracked resources:
void release_all_handles() {
release_internal_handles(); // ATR manager
release_tracked_handles(); // Generic indicator handles
}
private:
// --- Dynamic array of general indicator handles
int handles[];
// --- Append a valid indicator handle to the internal array
// Used by register_handle()
void add_handle(int handle) {
int size = ArraySize(handles);
ArrayResize(handles, size + 1);
handles[size] = handle;
}
// --- Release all generic indicator handles tracked internally
// This covers iMA, iRSI, iCCI, etc.
void release_tracked_handles() {
for (int i = 0; i < ArraySize(handles); i++) {
if (handles[i] != INVALID_HANDLE)
IndicatorRelease(handles[i]);
}
ArrayFree(handles);
}
// --- Release ATR handles via external AtrHandleManager
// No-op if atr_manager is not assigned
void release_internal_handles() {
if (atr_manager != NULL)
atr_manager.release_handles();
}
};
+14 -5
View File
@@ -15,11 +15,20 @@ class SignalStateTracker {
last_signal_short = -1000; last_signal_short = -1000;
} }
// Updates the signal for both long and short directions in one call, if the respective triggers are true. // Updates the signal tracker based on trigger presence per bar.
// curr_bar should refer to the last CLOSED bar (typically bar index 1). // If signal is detected, sets to 1. If not, increments previous value.
void update_signal(bool trig_long, bool trig_short, int curr_bar=1) { void update_signal_tracker(bool signal_long, bool signal_short) {
if (trig_long) last_signal_long = curr_bar; if (signal_long) {
if (trig_short) last_signal_short = curr_bar; last_signal_long = 1;
} else if (last_signal_long > 0) {
last_signal_long++;
}
if (signal_short) {
last_signal_short = 1;
} else if (last_signal_short > 0) {
last_signal_short++;
}
} }
// Returns true if a long signal occurred within the last `max_bars` bars. // Returns true if a long signal occurred within the last `max_bars` bars.
+89 -83
View File
@@ -1,139 +1,145 @@
#include <MyLibs/Utils/MarketDataUtils.mqh> #include <MyLibs/Utils/MarketDataUtils.mqh>
#include <MyLibs/Utils/AtrHandleManager.mqh>
class AtrBands { class AtrBands {
private: private:
MarketDataUtils market_data_utils; MarketDataUtils market_data_utils;
AtrHandleManager atr_manager;
public: public:
// Core band accessors // Core band accessors
double upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, double upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1);
color clr = clrDodgerBlue, int width = 1, bool plot = true); double lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1);
double middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift = 1);
double lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1,
color clr = clrDodgerBlue, int width = 1, bool plot = true);
double middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift = 1, color clr = clrDodgerBlue, int width = 1,
bool plot = true);
// Band checkers // Band checkers
bool inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, bool plot = true); bool inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1);
bool inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1);
bool crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1);
bool crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1);
bool inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, bool plot = true); // Plotting
void plot_bands(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int bars = 100, color clr = clrSkyBlue, int width = 1);
bool crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1,
bool plot = true);
bool crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, protected:
bool plot = true);
protected:
double get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift); double get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift);
void draw_line(string name, double price, color clr, int width);
}; };
// ------------------- // -------------------
// Public Methods // Public Methods
// ------------------- // -------------------
double AtrBands::upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, color clr, int width, bool plot) { double AtrBands::upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) {
double atr = get_atr(symbol, atr_period, tf, shift); double atr = get_atr(symbol, atr_period, tf, shift);
double upper = trendline_var + atr * mult; return trendline_var + atr * mult;
if (plot && shift == 1) draw_line("ATRBand_Upper_" + symbol, upper, clr, width); // persistent line
return upper;
} }
double AtrBands::lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, color clr, int width, bool plot) { double AtrBands::lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) {
double atr = get_atr(symbol, atr_period, tf, shift); double atr = get_atr(symbol, atr_period, tf, shift);
double lower = trendline_var - atr * mult; return trendline_var - atr * mult;
if (plot && shift == 1) draw_line("ATRBand_Lower_" + symbol, lower, clr, width); // persistent line
return lower;
} }
double AtrBands::middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift, color clr, int width, bool plot) { double AtrBands::middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift) {
if (plot && shift == 1) draw_line("ATRBand_Mid_" + symbol, trendline_var, clr, width); // persistent line
return trendline_var; return trendline_var;
} }
bool AtrBands::inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { bool AtrBands::inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) {
double price = iClose(symbol, tf, shift); double price = iClose(symbol, tf, shift);
double trendline_var = market_data_utils.get_buffer_value(handle, shift); double trendline_var = market_data_utils.get_buffer_value(handle, shift);
double upper = upper_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot); double upper = upper_band(symbol, trendline_var, atr_period, tf, mult, shift);
if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || upper == EMPTY_VALUE) return false; if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || upper == EMPTY_VALUE) return false;
return (price > trendline_var && price < upper); return (price > trendline_var && price < upper);
} }
bool AtrBands::inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { bool AtrBands::inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) {
double price = iClose(symbol, tf, shift); double price = iClose(symbol, tf, shift);
double trendline_var = market_data_utils.get_buffer_value(handle, shift); double trendline_var = market_data_utils.get_buffer_value(handle, shift);
double lower = lower_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot); double lower = lower_band(symbol, trendline_var, atr_period, tf, mult, shift);
if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || lower == EMPTY_VALUE) return false; if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || lower == EMPTY_VALUE) return false;
return (price < trendline_var && price > lower); return (price < trendline_var && price > lower);
} }
// pull-back inside upper atr band: bool AtrBands::crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) {
bool AtrBands::crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) {
//--- checked bar
double price = iClose(symbol, tf, shift); double price = iClose(symbol, tf, shift);
double trendline_var = market_data_utils.get_buffer_value(handle, shift);
double atr_upper_band = upper_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot);
//--- previous bar
double prev_price = iClose(symbol, tf, shift + 1); double prev_price = iClose(symbol, tf, shift + 1);
double trendline_var = market_data_utils.get_buffer_value(handle, shift);
double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1); double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1);
double prev_atr_upper_band = upper_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1, clrDodgerBlue, 1, plot);
if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || atr_upper_band == EMPTY_VALUE || prev_price == EMPTY_VALUE double upper = upper_band(symbol, trendline_var, atr_period, tf, mult, shift);
|| prev_trendline_var == EMPTY_VALUE || prev_atr_upper_band == EMPTY_VALUE) return false; double prev_upper = upper_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1);
return (prev_price > prev_atr_upper_band && price < atr_upper_band); if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || prev_trendline_var == EMPTY_VALUE) return false;
return (prev_price > prev_upper && price < upper);
} }
// pull-back inside lower atr band: bool AtrBands::crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) {
bool AtrBands::crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) {
//--- checked bar
double price = iClose(symbol, tf, shift); double price = iClose(symbol, tf, shift);
double trendline_var = market_data_utils.get_buffer_value(handle, shift);
double atr_lower_band = lower_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot);
//--- previous bar
double prev_price = iClose(symbol, tf, shift + 1); double prev_price = iClose(symbol, tf, shift + 1);
double trendline_var = market_data_utils.get_buffer_value(handle, shift);
double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1); double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1);
double prev_atr_lower_band = lower_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1, clrDodgerBlue, 1, plot);
if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || atr_lower_band == EMPTY_VALUE || prev_price == EMPTY_VALUE double lower = lower_band(symbol, trendline_var, atr_period, tf, mult, shift);
|| prev_trendline_var == EMPTY_VALUE || prev_atr_lower_band == EMPTY_VALUE) return false; double prev_lower = lower_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1);
return (prev_price < prev_atr_lower_band && price > atr_lower_band); if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || prev_trendline_var == EMPTY_VALUE) return false;
return (prev_price < prev_lower && price > lower);
} }
// ------------------- void AtrBands::plot_bands(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int bars, color line_color, int width) {
// Internal Helpers for (int i = bars; i >= 1; i--) {
// ------------------- double trendline = market_data_utils.get_buffer_value(handle, i);
if (trendline == EMPTY_VALUE) continue;
double atr = get_atr(symbol, atr_period, tf, i);
if (atr == EMPTY_VALUE) continue;
double upper = trendline + atr * mult;
double lower = trendline - atr * mult;
datetime time1 = iTime(symbol, tf, i);
datetime time2 = iTime(symbol, tf, i - 1);
string upper_name = "ATR_Upper_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES);
string lower_name = "ATR_Lower_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES);
string middle_name = "ATR_middle_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES);
if (ObjectFind(0, upper_name) < 0) {
ObjectCreate(0, upper_name, OBJ_TREND, 0, time1, upper, time2, upper);
ObjectSetInteger(0, upper_name, OBJPROP_COLOR, line_color);
ObjectSetInteger(0, upper_name, OBJPROP_WIDTH, width);
ObjectSetInteger(0, upper_name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, upper_name, OBJPROP_BACK, true);
ObjectSetInteger(0, upper_name, OBJPROP_SELECTED, false);
}
if (ObjectFind(0, lower_name) < 0) {
ObjectCreate(0, lower_name, OBJ_TREND, 0, time1, lower, time2, lower);
ObjectSetInteger(0, lower_name, OBJPROP_COLOR, line_color);
ObjectSetInteger(0, lower_name, OBJPROP_WIDTH, width);
ObjectSetInteger(0, lower_name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, lower_name, OBJPROP_BACK, true);
ObjectSetInteger(0, lower_name, OBJPROP_SELECTED, false);
}
if (ObjectFind(0, middle_name) < 0) {
ObjectCreate(0, middle_name, OBJ_TREND, 0, time1, trendline, time2, trendline);
ObjectSetInteger(0, middle_name, OBJPROP_COLOR, line_color);
ObjectSetInteger(0, middle_name, OBJPROP_WIDTH, width);
ObjectSetInteger(0, middle_name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, middle_name, OBJPROP_BACK, true);
ObjectSetInteger(0, middle_name, OBJPROP_SELECTED, false);
}
}
ChartRedraw();
}
double AtrBands::get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift) { double AtrBands::get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift) {
int handle = iATR(symbol, tf, atr_period); return atr_manager.get_atr_value(symbol, tf, atr_period, shift);
if (handle == INVALID_HANDLE) {
Print("Failed to create ATR handle for ", symbol);
return 0.0;
}
double buf[];
ArraySetAsSeries(buf, true);
if (CopyBuffer(handle, 0, shift, 1, buf) == 1 && buf[0] != EMPTY_VALUE) return buf[0];
return 0.0;
}
void AtrBands::draw_line(string name, double price, color clr, int width) {
if (ObjectFind(0, name) < 0) {
ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
} else {
ObjectSetDouble(0, name, OBJPROP_PRICE, price);
}
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
} }
+5 -5
View File
@@ -13,16 +13,16 @@ class TrendlineAnalyser {
// - handle: the indicator handle for the trendline (e.g., iMA handle) // - handle: the indicator handle for the trendline (e.g., iMA handle)
// - cross_long: output bool set to true if a bullish cross is detected // - cross_long: output bool set to true if a bullish cross is detected
// - cross_short: output bool set to true if a bearish cross is detected // - cross_short: output bool set to true if a bearish cross is detected
void detect_cross(string symbol, int handle, bool& cross_long, bool& cross_short) { void detect_cross(string symbol, int handle, bool& cross_long, bool& cross_short, int shift=1) {
cross_long = false; cross_long = false;
cross_short = false; cross_short = false;
// --- Retrieve prices and trendline values from last 2 closed bars // --- Retrieve prices and trendline values from last 2 closed bars
double price = iClose(symbol, PERIOD_CURRENT, 1); // most recent closed bar double price = iClose(symbol, PERIOD_CURRENT, shift); // most recent closed bar
double prev_price = iClose(symbol, PERIOD_CURRENT, 2); // bar before that double prev_price = iClose(symbol, PERIOD_CURRENT, shift + 1); // bar before that
double trendline = market_data_utils.get_buffer_value(handle, 1); // trendline now double trendline = market_data_utils.get_buffer_value(handle, shift); // trendline now
double prev_trendline = market_data_utils.get_buffer_value(handle, 2); // trendline before double prev_trendline = market_data_utils.get_buffer_value(handle, shift+1); // trendline before
// --- Exit early if any of the data is missing or invalid // --- Exit early if any of the data is missing or invalid
if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline == EMPTY_VALUE || prev_trendline == EMPTY_VALUE) { if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline == EMPTY_VALUE || prev_trendline == EMPTY_VALUE) {