Inclustion of example EA
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// ENUM: CUSTOM_MAX_TYPE
|
||||
// ---------------------------------------------------------------------
|
||||
// Defines the types of custom performance criteria that can be used
|
||||
// for calculating max optimization targets.
|
||||
//
|
||||
// Values:
|
||||
// - CM_WIN_LOSS_RATIO : Use win/loss ratio.
|
||||
// - CM_WIN_PERCENT : Use win percentage.
|
||||
// ---------------------------------------------------------------------
|
||||
enum CUSTOM_MAX_TYPE {
|
||||
CM_WIN_LOSS_RATIO,
|
||||
CM_WIN_PERCENT
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// CLASS: CustomMax
|
||||
// ---------------------------------------------------------------------
|
||||
// Calculates custom performance criteria for use in optimizations.
|
||||
// ---------------------------------------------------------------------
|
||||
class CustomMax : public CObject {
|
||||
protected:
|
||||
double custom_criteria;
|
||||
|
||||
double win_loss_ratio(int min_required_trades);
|
||||
double win_percent_min_trades(int min_required_trades);
|
||||
|
||||
public:
|
||||
double calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_trades = 0);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Calculates the selected custom criteria metric.
|
||||
//
|
||||
// Parameters:
|
||||
// - cm_type : The selected criteria type to calculate.
|
||||
// - min_trades : Optional minimum number of trades (default 0).
|
||||
//
|
||||
// Logic:
|
||||
// - Calls the appropriate private method based on cm_type.
|
||||
// - Returns the resulting metric (or 0 if invalid).
|
||||
// ---------------------------------------------------------------------
|
||||
double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_trades) {
|
||||
switch(cm_type) {
|
||||
case CM_WIN_LOSS_RATIO:
|
||||
custom_criteria = win_loss_ratio(min_trades);
|
||||
break;
|
||||
case CM_WIN_PERCENT:
|
||||
custom_criteria = win_percent_min_trades(min_trades);
|
||||
break;
|
||||
default:
|
||||
custom_criteria = 0;
|
||||
break;
|
||||
}
|
||||
return custom_criteria;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Calculates win/loss ratio with a minimum trade count check.
|
||||
//
|
||||
// Parameters:
|
||||
// - min_required_trades : Minimum number of trades required.
|
||||
//
|
||||
// Logic:
|
||||
// - Returns wins / losses if minimum is met.
|
||||
// - Prevents division by zero.
|
||||
// ---------------------------------------------------------------------
|
||||
double CustomMax::win_loss_ratio(int min_required_trades) {
|
||||
double wins = TesterStatistics(STAT_PROFIT_TRADES);
|
||||
double losses = TesterStatistics(STAT_LOSS_TRADES);
|
||||
double total_trades = TesterStatistics(STAT_TRADES);
|
||||
|
||||
if ((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0)
|
||||
return 0;
|
||||
if (losses == 0)
|
||||
return 0;
|
||||
|
||||
return wins / losses;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Calculates win percentage with a minimum trade count check.
|
||||
//
|
||||
// Parameters:
|
||||
// - min_required_trades : Minimum number of trades required.
|
||||
//
|
||||
// Logic:
|
||||
// - Returns (wins / total) * 100 if minimum is met.
|
||||
// - Filters out invalid math results.
|
||||
// ---------------------------------------------------------------------
|
||||
double CustomMax::win_percent_min_trades(int min_required_trades) {
|
||||
double wins = TesterStatistics(STAT_PROFIT_TRADES);
|
||||
double total_trades = TesterStatistics(STAT_TRADES);
|
||||
|
||||
if ((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0)
|
||||
return 0;
|
||||
|
||||
double result = (wins / total_trades) * 100;
|
||||
if (!MathIsValidNumber(result))
|
||||
return 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// ---------------------------------------------------------------------
|
||||
// ENUM: MODE_SPLIT_DATA
|
||||
// ---------------------------------------------------------------------
|
||||
// Defines how to split data during testing based on time attributes.
|
||||
//
|
||||
// Values:
|
||||
// - NO_SPLIT : Do not split, always return true.
|
||||
// - ODD_YEARS : Include only odd-numbered years.
|
||||
// - EVEN_YEARS : Include only even-numbered years.
|
||||
// - ODD_MONTHS : Include only odd-numbered months.
|
||||
// - EVEN_MONTHS : Include only even-numbered months.
|
||||
// - ODD_WEEKS : Include only odd-numbered weeks.
|
||||
// - EVEN_WEEKS : Include only even-numbered weeks.
|
||||
// ---------------------------------------------------------------------
|
||||
enum MODE_SPLIT_DATA {
|
||||
NO_SPLIT,
|
||||
ODD_YEARS,
|
||||
EVEN_YEARS,
|
||||
ODD_MONTHS,
|
||||
EVEN_MONTHS,
|
||||
ODD_WEEKS,
|
||||
EVEN_WEEKS
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// CLASS: TestDataSplit
|
||||
// ---------------------------------------------------------------------
|
||||
// Provides logic to determine whether the current date falls within
|
||||
// a selected split group for testing or optimization purposes.
|
||||
// ---------------------------------------------------------------------
|
||||
class TestDataSplit {
|
||||
public:
|
||||
bool in_test_period(MODE_SPLIT_DATA data_split_method);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Determines whether the current time falls in the selected group.
|
||||
//
|
||||
// Parameters:
|
||||
// - data_split_method : Enum value defining the split strategy.
|
||||
//
|
||||
// Logic:
|
||||
// - Extracts current date components (year, month, ISO week).
|
||||
// - Returns true if current time matches the given split rule.
|
||||
// ---------------------------------------------------------------------
|
||||
bool TestDataSplit::in_test_period(MODE_SPLIT_DATA data_split_method) {
|
||||
string result[];
|
||||
string string_tc = TimeToString(TimeCurrent());
|
||||
|
||||
// Extract components from datetime string (assumes YYYY.MM.DD format)
|
||||
ushort u_sep = StringGetCharacter(".", 0);
|
||||
StringSplit(string_tc, u_sep, result);
|
||||
|
||||
bool odd_year = int(result[0]) % 2;
|
||||
bool odd_month = int(result[1]) % 2;
|
||||
|
||||
// Calculate week of the year (approximate ISO week)
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(TimeCurrent(), dt);
|
||||
int i_day = (dt.day_of_week + 6) % 7 + 1; // Convert to 1=Mon,...,7=Sun
|
||||
int i_week = (dt.day_of_year - i_day + 10) / 7; // Approximate ISO week number
|
||||
bool odd_week = i_week % 2;
|
||||
|
||||
// Split logic depending on mode
|
||||
if (data_split_method == NO_SPLIT)
|
||||
return true;
|
||||
if (data_split_method == ODD_YEARS && odd_year)
|
||||
return true;
|
||||
if (data_split_method == EVEN_YEARS && !odd_year)
|
||||
return true;
|
||||
if (data_split_method == ODD_MONTHS && odd_month)
|
||||
return true;
|
||||
if (data_split_method == EVEN_MONTHS && !odd_month)
|
||||
return true;
|
||||
if (data_split_method == ODD_WEEKS && odd_week)
|
||||
return true;
|
||||
if (data_split_method == EVEN_WEEKS && !odd_week)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
#include <Trade/Trade.mqh>
|
||||
#include <MyLibs/utils/AtrHandleManager.mqh>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// GLOBALS
|
||||
// ---------------------------------------------------------------------
|
||||
CTrade trade;
|
||||
AtrHandleManager atr_manager;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// CLASS: AdjustPosition
|
||||
// ---------------------------------------------------------------------
|
||||
// Provides methods to manage stop-loss logic for runner trades.
|
||||
// Includes breakeven, trailing stop (fixed and ATR), and virtual TP SLs.
|
||||
// ---------------------------------------------------------------------
|
||||
class AdjustPosition {
|
||||
public:
|
||||
void set_breakeven_sl(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_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:
|
||||
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);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sets SL to breakeven for all matching runner trades.
|
||||
// ---------------------------------------------------------------------
|
||||
void AdjustPosition::set_breakeven_sl(string symbol, int runner_magic_no, double buffer_points) {
|
||||
int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
double buffer_price = buffer_points * _Point;
|
||||
|
||||
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) != runner_magic_no) continue;
|
||||
|
||||
long order_type = PositionGetInteger(POSITION_TYPE);
|
||||
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double sl = PositionGetDouble(POSITION_SL);
|
||||
double tp = PositionGetDouble(POSITION_TP);
|
||||
|
||||
set_breakeven_sl_for_ticket(symbol, ticket, order_type, entry, sl, tp, digits, buffer_price, false);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sets SL to breakeven if virtual TP (in comment) was hit.
|
||||
// ---------------------------------------------------------------------
|
||||
void AdjustPosition::set_breakeven_if_profit_target_hit(string symbol, int runner_magic_no, double buffer_points) {
|
||||
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||||
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
double buffer_price = buffer_points * _Point;
|
||||
double price_margin = 50 * _Point;
|
||||
|
||||
bool has_runner = false;
|
||||
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) == runner_magic_no) {
|
||||
has_runner = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_runner) return;
|
||||
|
||||
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) != runner_magic_no) continue;
|
||||
|
||||
long order_type = PositionGetInteger(POSITION_TYPE);
|
||||
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double sl = PositionGetDouble(POSITION_SL);
|
||||
double tp = PositionGetDouble(POSITION_TP);
|
||||
string comment = PositionGetString(POSITION_COMMENT);
|
||||
|
||||
double virtual_tp = 0.0;
|
||||
if (StringFind(comment, "runner_tp:") == 0) {
|
||||
string tp_str = StringSubstr(comment, StringLen("runner_tp:"));
|
||||
virtual_tp = StringToDouble(tp_str);
|
||||
}
|
||||
|
||||
if (virtual_tp <= 0.0) continue;
|
||||
if (tp > 0.0)
|
||||
PrintFormat("Warning: Runner trade on %s (ticket %d) has TP set: %.5f", symbol, ticket, 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;
|
||||
|
||||
bool tp_hit = (order_type == POSITION_TYPE_BUY && bid >= virtual_tp) ||
|
||||
(order_type == POSITION_TYPE_SELL && ask <= virtual_tp);
|
||||
if (!tp_hit) continue;
|
||||
|
||||
double expected_sl = (order_type == POSITION_TYPE_BUY)
|
||||
? entry + buffer_price
|
||||
: entry - buffer_price;
|
||||
|
||||
if (NormalizeDouble(sl, digits) == NormalizeDouble(expected_sl, digits)) continue;
|
||||
|
||||
set_breakeven_sl_for_ticket(symbol, ticket, order_type, entry, sl, tp, digits, buffer_price, true);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sets SL for a single trade to breakeven, optionally removes TP.
|
||||
// ---------------------------------------------------------------------
|
||||
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 breakeven_sl = (order_type == POSITION_TYPE_BUY)
|
||||
? entry_price + buffer_price
|
||||
: entry_price - buffer_price;
|
||||
|
||||
if ((order_type == POSITION_TYPE_BUY && current_sl >= breakeven_sl) ||
|
||||
(order_type == POSITION_TYPE_SELL && current_sl <= breakeven_sl)) return;
|
||||
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult result;
|
||||
|
||||
request.action = TRADE_ACTION_SLTP;
|
||||
request.symbol = symbol;
|
||||
request.position = ticket;
|
||||
request.sl = NormalizeDouble(breakeven_sl, digits);
|
||||
request.tp = remove_tp ? 0.0 : current_tp;
|
||||
request.magic = (int) PositionGetInteger(POSITION_MAGIC);
|
||||
|
||||
if (!OrderSend(request, result))
|
||||
Print("Failed to adjust runner: ", symbol, ". Error: ", result.retcode);
|
||||
else if (remove_tp)
|
||||
Print("Runner upgraded to trailing: SL at breakeven, TP removed for ", symbol);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sets a fixed SL price for all runner trades.
|
||||
// ---------------------------------------------------------------------
|
||||
void AdjustPosition::set_fixed_sl(string symbol, int runner_magic_no, double fixed_sl_price) {
|
||||
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) != runner_magic_no) continue;
|
||||
|
||||
double current_sl = PositionGetDouble(POSITION_SL);
|
||||
double current_tp = PositionGetDouble(POSITION_TP);
|
||||
if (current_sl == fixed_sl_price) continue;
|
||||
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult result;
|
||||
|
||||
request.action = TRADE_ACTION_SLTP;
|
||||
request.symbol = symbol;
|
||||
request.position = ticket;
|
||||
request.sl = fixed_sl_price;
|
||||
request.tp = current_tp;
|
||||
request.magic = runner_magic_no;
|
||||
|
||||
if (!OrderSend(request, result))
|
||||
Print("Failed to set fixed SL for runner on ", symbol, ". Error: ", result.retcode);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Applies a fixed-point trailing stop to runner trades.
|
||||
// ---------------------------------------------------------------------
|
||||
void AdjustPosition::set_trailing_sl(string symbol, int runner_magic_no, double sl_offset_points) {
|
||||
int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
double offset = sl_offset_points * _Point;
|
||||
|
||||
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) != runner_magic_no) continue;
|
||||
|
||||
long type = PositionGetInteger(POSITION_TYPE);
|
||||
double current_sl = PositionGetDouble(POSITION_SL);
|
||||
double current_tp = PositionGetDouble(POSITION_TP);
|
||||
double price = (type == POSITION_TYPE_BUY)
|
||||
? SymbolInfoDouble(symbol, SYMBOL_BID)
|
||||
: SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||||
|
||||
double sl = (type == POSITION_TYPE_BUY) ? price - offset : price + offset;
|
||||
if ((type == POSITION_TYPE_BUY && sl <= current_sl) ||
|
||||
(type == POSITION_TYPE_SELL && sl >= current_sl)) continue;
|
||||
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult result;
|
||||
|
||||
request.action = TRADE_ACTION_SLTP;
|
||||
request.symbol = symbol;
|
||||
request.position = ticket;
|
||||
request.sl = NormalizeDouble(sl, digits);
|
||||
request.tp = current_tp;
|
||||
request.magic = runner_magic_no;
|
||||
|
||||
if (!OrderSend(request, result))
|
||||
Print("Failed to update trailing SL for runner on ", symbol, ". Error: ", result.retcode);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Applies an ATR-based trailing stop to runner trades.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
#include <MyLibs/Utils/MarketDataUtils.mqh>
|
||||
#include <MyLibs/Utils/TimeZones.mqh>
|
||||
#include <MyLibs/Utils/AtrHandleManager.mqh>
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// CLASS: CalculatePositionData
|
||||
// ---------------------------------------------------------------------
|
||||
// Provides core logic for computing stop loss, take profit, lot size,
|
||||
// and trading costs based on symbol, price, and risk parameters.
|
||||
// ---------------------------------------------------------------------
|
||||
class CalculatePositionData : public CObject {
|
||||
protected:
|
||||
CTrade trade;
|
||||
CPositionInfo position;
|
||||
MarketDataUtils mdu;
|
||||
AtrHandleManager atr_manager;
|
||||
|
||||
bool check_lots(double& lots, string symbol);
|
||||
bool normalise_price(double price, double& normalizedPrice, string symbol);
|
||||
|
||||
public:
|
||||
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, ENUM_TIMEFRAMES atr_tf);
|
||||
double calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var);
|
||||
double calculate_trading_cost(string symbol, ulong ticket);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Calculates stop loss based on selected method.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol for the trade.
|
||||
// - price : Entry price.
|
||||
// - order_side: 1 = Buy, 2 = Sell.
|
||||
// - mode_sl : SL method ("NO_STOPLOSS", "SL_FIXED_PIPS", etc).
|
||||
// - sl_var : SL parameter (pips, %, ATR multiplier, or absolute).
|
||||
// - atr_tf : Timeframe for ATR.
|
||||
//
|
||||
// Returns:
|
||||
// - Calculated SL price, or 0 if invalid.
|
||||
// ---------------------------------------------------------------------
|
||||
double CalculatePositionData::calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_tf) {
|
||||
double sl = 0;
|
||||
|
||||
if (mode_sl == "NO_STOPLOSS") return 0;
|
||||
|
||||
if (mode_sl == "SL_FIXED_PIPS") {
|
||||
double adj_point = mdu.adjusted_point(symbol);
|
||||
sl = (order_side == 1) ? price - sl_var * adj_point : price + sl_var * adj_point;
|
||||
if (!normalise_price(sl, sl, symbol)) return 0;
|
||||
}
|
||||
|
||||
if (mode_sl == "SL_FIXED_PERCENT") {
|
||||
sl = (order_side == 1) ? price - (sl_var * price / 100.0) : price + (sl_var * price / 100.0);
|
||||
if (!normalise_price(sl, sl, symbol)) return 0;
|
||||
}
|
||||
|
||||
if (mode_sl == "SL_ATR_MULTIPLE") {
|
||||
double atr = atr_manager.get_atr_value(symbol, atr_tf, 14);
|
||||
if (atr == EMPTY_VALUE) return 0;
|
||||
sl = (order_side == 1) ? price - atr * sl_var : price + atr * sl_var;
|
||||
if (!normalise_price(sl, sl, symbol)) return 0;
|
||||
}
|
||||
|
||||
if (mode_sl == "SL_SPECIFIED_VALUE") {
|
||||
double adj_point = mdu.adjusted_point(symbol);
|
||||
double limit_sl = (order_side == 1) ? price - 10 * adj_point : price + 10 * adj_point;
|
||||
sl = (order_side == 1) ? fmax(sl_var, limit_sl) : fmin(sl_var, limit_sl);
|
||||
if (!normalise_price(sl, sl, symbol)) return 0;
|
||||
}
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Calculates take profit based on selected method.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol for the trade.
|
||||
// - price : Entry price.
|
||||
// - stoploss : SL value (used in TP/SL ratio mode).
|
||||
// - order_side: 1 = Buy, -1 = Sell.
|
||||
// - mode_tp : TP method ("NO_TAKE_PROFIT", "TP_FIXED_PIPS", etc).
|
||||
// - tp_var : TP parameter (pips, %, ATR multiplier, SL multiple).
|
||||
// - atr_tf : Timeframe for ATR.
|
||||
//
|
||||
// Returns:
|
||||
// - Calculated TP price, or 0 if invalid.
|
||||
// ---------------------------------------------------------------------
|
||||
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;
|
||||
|
||||
if (mode_tp == "NO_TAKE_PROFIT") return 0;
|
||||
|
||||
if (mode_tp == "TP_FIXED_PIPS") {
|
||||
double adj_point = mdu.adjusted_point(symbol);
|
||||
tp = (order_side == 1) ? price + tp_var * adj_point : price - tp_var * adj_point;
|
||||
if (!normalise_price(tp, tp, symbol)) return 0;
|
||||
}
|
||||
|
||||
if (mode_tp == "TP_FIXED_PERCENT") {
|
||||
tp = (order_side == 1) ? price + tp_var * price / 100.0 : price - tp_var * price / 100.0;
|
||||
if (!normalise_price(tp, tp, symbol)) return 0;
|
||||
}
|
||||
|
||||
if (mode_tp == "TP_ATR_MULTIPLE") {
|
||||
double atr = atr_manager.get_atr_value(symbol, atr_tf, 14);
|
||||
if (atr == EMPTY_VALUE) return 0;
|
||||
tp = (order_side == 1) ? price + atr * tp_var : price - atr * tp_var;
|
||||
if (!normalise_price(tp, tp, symbol)) return 0;
|
||||
}
|
||||
|
||||
if (mode_tp == "TP_SL_MULTIPLE") {
|
||||
double sl_size = (order_side == 1) ? price - stoploss : stoploss - price;
|
||||
tp = (order_side == 1) ? price + tp_var * sl_size : price - tp_var * sl_size;
|
||||
if (!normalise_price(tp, tp, symbol)) return 0;
|
||||
}
|
||||
|
||||
if (mode_tp == "TP_SPECIFIED_VALUE") {
|
||||
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 (!normalise_price(tp, tp, symbol)) return 0;
|
||||
}
|
||||
|
||||
return tp;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Calculates lot size based on selected lot mode.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol for the trade.
|
||||
// - sl_distance: SL distance in points.
|
||||
// - price : Current price.
|
||||
// - mode_lot : Lot mode ("LOT_MODE_FIXED", "LOT_MODE_PCT_RISK", etc).
|
||||
// - lot_var : Value for lot calculation.
|
||||
//
|
||||
// Returns:
|
||||
// - Computed lot size (rounded and validated).
|
||||
// ---------------------------------------------------------------------
|
||||
double CalculatePositionData::calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var) {
|
||||
double lots = 0;
|
||||
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||||
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||||
double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
|
||||
double account_value = fmin(fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)), AccountInfoDouble(ACCOUNT_MARGIN_FREE));
|
||||
double risk_money = account_value * lot_var / 100.0;
|
||||
|
||||
if (mode_lot == "LOT_MODE_FIXED") {
|
||||
lots = lot_var;
|
||||
}
|
||||
|
||||
if (mode_lot == "LOT_MODE_PCT_RISK") {
|
||||
double money_per_step = (sl_distance / tick_size) * tick_value * volume_step;
|
||||
lots = MathFloor(risk_money / money_per_step) * volume_step;
|
||||
}
|
||||
|
||||
if (mode_lot == "LOT_MODE_PCT_ACCOUNT") {
|
||||
double money_per_step = (price / tick_size) * tick_value * volume_step;
|
||||
lots = MathFloor(risk_money / money_per_step) * volume_step;
|
||||
}
|
||||
|
||||
if (!check_lots(lots, symbol)) return 0;
|
||||
return lots;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Validates and adjusts lot size to symbol constraints.
|
||||
//
|
||||
// Parameters:
|
||||
// - lots : Input/output lot size.
|
||||
// - symbol : Trading symbol.
|
||||
//
|
||||
// Returns:
|
||||
// - true if lots are valid after correction.
|
||||
// ---------------------------------------------------------------------
|
||||
bool CalculatePositionData::check_lots(double& lots, string symbol) {
|
||||
double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
|
||||
if (lots < min) {
|
||||
lots = min;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lots > max) {
|
||||
Print("Lot size exceeds max for ", symbol);
|
||||
return false;
|
||||
}
|
||||
|
||||
lots = (int)MathFloor(lots / step) * step;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Normalizes price to the nearest valid tick size.
|
||||
//
|
||||
// Parameters:
|
||||
// - price : Raw price.
|
||||
// - normalizedPrice: Output normalized price.
|
||||
// - symbol : Trading symbol.
|
||||
//
|
||||
// Returns:
|
||||
// - true if successful, false if tick size lookup failed.
|
||||
// ---------------------------------------------------------------------
|
||||
bool CalculatePositionData::normalise_price(double price, double& normalizedPrice, string symbol) {
|
||||
double tick_size;
|
||||
if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE, tick_size)) {
|
||||
Print("Failed to get tick size for ", symbol);
|
||||
return false;
|
||||
}
|
||||
|
||||
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
normalizedPrice = NormalizeDouble(MathRound(price / tick_size) * tick_size, digits);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
#include <MyLibs/Orders/CalculatePositionData.mqh>
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
class EntryOrders {
|
||||
protected:
|
||||
CTrade trade;
|
||||
CalculatePositionData calc;
|
||||
|
||||
public:
|
||||
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,
|
||||
double tp_var, string _lot_mode, double lot_var, long _magic_number);
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
long _magic_number);
|
||||
|
||||
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,
|
||||
long _magic_number);
|
||||
|
||||
bool open_runner_buy_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);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Counts open positions by symbol, side, and magic number.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to check.
|
||||
// - order_side : 1 = Buy, 2 = Sell, 0 = Any.
|
||||
// - _magic_number : Magic number to filter.
|
||||
//
|
||||
// Returns:
|
||||
// - Number of matching open positions.
|
||||
// ---------------------------------------------------------------------
|
||||
int EntryOrders::count_open_positions(string symbol, int order_side, long _magic_number) {
|
||||
int count = 0;
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) {
|
||||
int type = (int) PositionGetInteger(POSITION_TYPE);
|
||||
if (order_side == 0 || (order_side == 1 && type == POSITION_TYPE_BUY) || (order_side == 2 && type == POSITION_TYPE_SELL)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Opens a market BUY position.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to trade.
|
||||
// - condition : If false, trade will not execute.
|
||||
// - atr_period : Timeframe for ATR-based SL/TP.
|
||||
// - _sl_mode : SL calculation method.
|
||||
// - sl_var : SL variable (e.g., pips or ATR multiplier).
|
||||
// - _tp_mode : TP calculation method.
|
||||
// - tp_var : TP variable.
|
||||
// - _lot_mode : Lot calculation method.
|
||||
// - lot_var : Lot sizing variable.
|
||||
// - _magic_number : Magic number for trade.
|
||||
//
|
||||
// Returns:
|
||||
// - True if trade was placed successfully.
|
||||
// ---------------------------------------------------------------------
|
||||
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) {
|
||||
if (!condition) return false;
|
||||
double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||||
if (count_open_positions(symbol, 1, _magic_number) > 0) return false;
|
||||
|
||||
double stop_loss = calc.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period);
|
||||
double take_profit = calc.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
|
||||
double sl_distance = current_price - stop_loss;
|
||||
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);
|
||||
string comment = "Magic Number: " + IntegerToString(_magic_number);
|
||||
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;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Opens a market SELL position.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to trade.
|
||||
// - condition : If false, trade will not execute.
|
||||
// - atr_period : Timeframe for ATR-based SL/TP.
|
||||
// - _sl_mode : SL calculation method.
|
||||
// - sl_var : SL variable (e.g., pips or ATR multiplier).
|
||||
// - _tp_mode : TP calculation method.
|
||||
// - tp_var : TP variable.
|
||||
// - _lot_mode : Lot calculation method.
|
||||
// - lot_var : Lot sizing variable.
|
||||
// - _magic_number : Magic number for trade.
|
||||
//
|
||||
// Returns:
|
||||
// - True if trade was placed successfully.
|
||||
// ---------------------------------------------------------------------
|
||||
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) {
|
||||
if (!condition) return false;
|
||||
double current_price = SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
if (count_open_positions(symbol, 2, _magic_number) > 0) return false;
|
||||
|
||||
double stop_loss = calc.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period);
|
||||
double take_profit = calc.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
|
||||
double sl_distance = stop_loss - current_price;
|
||||
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);
|
||||
string comment = "Magic Number: " + IntegerToString(_magic_number);
|
||||
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;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Opens a pending BUY STOP order.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to trade.
|
||||
// - condition : If false, order will not be placed.
|
||||
// - entry_price : Trigger price for Buy Stop.
|
||||
// - expiration : Expiration time for pending order.
|
||||
// - atr_period : Timeframe for ATR-based SL/TP.
|
||||
// - _sl_mode : SL calculation method.
|
||||
// - sl_var : SL variable.
|
||||
// - _tp_mode : TP calculation method.
|
||||
// - tp_var : TP variable.
|
||||
// - _lot_mode : Lot calculation method.
|
||||
// - lot_var : Lot sizing variable.
|
||||
// - _magic_number : Magic number for order.
|
||||
//
|
||||
// Returns:
|
||||
// - True if order was placed successfully.
|
||||
// ---------------------------------------------------------------------
|
||||
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,
|
||||
long _magic_number) {
|
||||
if (!condition) return false;
|
||||
if (count_open_positions(symbol, 1, _magic_number) > 0) return false;
|
||||
|
||||
double stop_loss = calc.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period);
|
||||
double take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
|
||||
double sl_distance = entry_price - stop_loss;
|
||||
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);
|
||||
string comment = "Magic Number: " + IntegerToString(_magic_number);
|
||||
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;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Opens a pending SELL STOP order.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to trade.
|
||||
// - condition : If false, order will not be placed.
|
||||
// - entry_price : Trigger price for Sell Stop.
|
||||
// - expiration : Expiration time for pending order.
|
||||
// - atr_period : Timeframe for ATR-based SL/TP.
|
||||
// - _sl_mode : SL calculation method.
|
||||
// - sl_var : SL variable.
|
||||
// - _tp_mode : TP calculation method.
|
||||
// - tp_var : TP variable.
|
||||
// - _lot_mode : Lot calculation method.
|
||||
// - lot_var : Lot sizing variable.
|
||||
// - _magic_number : Magic number for order.
|
||||
//
|
||||
// Returns:
|
||||
// - True if order was placed successfully.
|
||||
// ---------------------------------------------------------------------
|
||||
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,
|
||||
long _magic_number) {
|
||||
if (!condition) return false;
|
||||
if (count_open_positions(symbol, 2, _magic_number) > 0) return false;
|
||||
|
||||
double stop_loss = calc.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period);
|
||||
double take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
|
||||
double sl_distance = stop_loss - entry_price;
|
||||
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);
|
||||
string comment = "Magic Number: " + IntegerToString(_magic_number);
|
||||
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;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Opens a market BUY runner with virtual TP in comment.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to trade.
|
||||
// - condition : If false, trade will not execute.
|
||||
// - atr_period : Timeframe for ATR-based SL.
|
||||
// - _sl_mode : SL calculation method.
|
||||
// - sl_var : SL variable.
|
||||
// - _tp_mode : TP calculation method.
|
||||
// - tp_var : TP variable (used for virtual TP).
|
||||
// - _lot_mode : Lot calculation method.
|
||||
// - lot_var : Lot sizing variable.
|
||||
// - _magic_number : Magic number for trade.
|
||||
//
|
||||
// Returns:
|
||||
// - True if trade was placed successfully.
|
||||
// ---------------------------------------------------------------------
|
||||
bool EntryOrders::open_runner_buy_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) {
|
||||
if (!condition) return false;
|
||||
double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||||
if (count_open_positions(symbol, 1, _magic_number) > 0) return false;
|
||||
|
||||
double stop_loss = calc.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period);
|
||||
double virtual_tp = calc.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
|
||||
double sl_distance = current_price - stop_loss;
|
||||
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);
|
||||
string comment = StringFormat("runner_tp:%.5f", virtual_tp);
|
||||
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;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Opens a market SELL runner with virtual TP in comment.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to trade.
|
||||
// - condition : If false, trade will not execute.
|
||||
// - atr_period : Timeframe for ATR-based SL.
|
||||
// - _sl_mode : SL calculation method.
|
||||
// - sl_var : SL variable.
|
||||
// - _tp_mode : TP calculation method.
|
||||
// - tp_var : TP variable (used for virtual TP).
|
||||
// - _lot_mode : Lot calculation method.
|
||||
// - lot_var : Lot sizing variable.
|
||||
// - _magic_number : Magic number for trade.
|
||||
//
|
||||
// Returns:
|
||||
// - True if trade was placed successfully.
|
||||
// ---------------------------------------------------------------------
|
||||
bool EntryOrders::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) {
|
||||
if (!condition) return false;
|
||||
double current_price = SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
if (count_open_positions(symbol, 2, _magic_number) > 0) return false;
|
||||
|
||||
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 sl_distance = stop_loss - current_price;
|
||||
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);
|
||||
string comment = StringFormat("runner_tp:%.5f", virtual_tp);
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
#include <MyLibs/Orders/CalculatePositionData.mqh>
|
||||
#include <MyLibs/Utils/TimeZones.mqh>
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
class ExitOrders {
|
||||
protected:
|
||||
CTrade trade;
|
||||
TimeZones tz;
|
||||
CalculatePositionData calc;
|
||||
|
||||
ulong posTicket;
|
||||
long position_open_time;
|
||||
long first_allowed_close_time;
|
||||
|
||||
public:
|
||||
bool close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long _magic_number);
|
||||
bool close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long _magic_number);
|
||||
bool daily_timed_exit(string symbol, datetime exit_time, int delay_days, long _magic_number);
|
||||
bool daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long _magic_number);
|
||||
bool first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long _magic_number);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Closes BUY positions on condition + after a number of bars (if non 0) .
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to evaluate positions for.
|
||||
// - condition : If true, closes position immediately.
|
||||
// - close_bars : Minimum number of bars before auto close.
|
||||
// - close_bar_period : Timeframe to count bars on.
|
||||
// - _magic_number : Magic number to identify the trade group.
|
||||
//
|
||||
// Returns:
|
||||
// - True after evaluation and any attempted closes.
|
||||
// ---------------------------------------------------------------------
|
||||
bool ExitOrders::close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long _magic_number) {
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
posTicket = PositionGetTicket(i);
|
||||
|
||||
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) {
|
||||
int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1;
|
||||
|
||||
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
|
||||
if (condition || (close_bars > 0 && time_difference >= close_bars)) {
|
||||
trade.PositionClose(posTicket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Closes SELL positions on condition + after a number of bars (if non 0) .
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to evaluate positions for.
|
||||
// - condition : If true, closes position immediately.
|
||||
// - close_bars : Minimum number of bars before auto close.
|
||||
// - close_bar_period : Timeframe to count bars on.
|
||||
// - _magic_number : Magic number to identify the trade group.
|
||||
//
|
||||
// Returns:
|
||||
// - True after evaluation and any attempted closes.
|
||||
// ---------------------------------------------------------------------
|
||||
bool ExitOrders::close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long _magic_number) {
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
posTicket = PositionGetTicket(i);
|
||||
|
||||
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) {
|
||||
int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1;
|
||||
|
||||
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) {
|
||||
if (condition || (close_bars > 0 && time_difference >= close_bars)) {
|
||||
trade.PositionClose(posTicket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Closes position after a fixed exit time and delay in days.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to evaluate.
|
||||
// - exit_time : Time of day when exit is permitted.
|
||||
// - delay_days : Number of full days before close allowed.
|
||||
// - _magic_number : Magic number to identify the trade group.
|
||||
//
|
||||
// Returns:
|
||||
// - True after evaluation and any attempted closes.
|
||||
// ---------------------------------------------------------------------
|
||||
bool ExitOrders::daily_timed_exit(string symbol, datetime exit_time, int delay_days, long _magic_number) {
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
posTicket = PositionGetTicket(i);
|
||||
position_open_time = PositionGetInteger(POSITION_TIME);
|
||||
|
||||
if ((int) position_open_time > 0) {
|
||||
first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1));
|
||||
|
||||
if (TimeCurrent() > first_allowed_close_time && TimeCurrent() >= exit_time) {
|
||||
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) {
|
||||
trade.PositionClose(posTicket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Closes a position only if it's profitable after a given time.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to evaluate.
|
||||
// - close_bar_period : Bar timeframe for bar-close evaluation.
|
||||
// - exit_time : Time of day when profit exit is checked.
|
||||
// - cw_tzone : Clockwork time zone for exit conversion.
|
||||
// - delay_days : Minimum days to wait before closing.
|
||||
// - _magic_number : Magic number to identify the trade group.
|
||||
//
|
||||
// Returns:
|
||||
// - True after evaluation and any attempted closes.
|
||||
// ---------------------------------------------------------------------
|
||||
bool ExitOrders::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days,
|
||||
long _magic_number) {
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
posTicket = PositionGetTicket(i);
|
||||
position_open_time = PositionGetInteger(POSITION_TIME);
|
||||
|
||||
if ((int) position_open_time > 0) {
|
||||
first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1));
|
||||
|
||||
if (TimeCurrent() > first_allowed_close_time) {
|
||||
datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker");
|
||||
|
||||
if (TimeCurrent() >= broker_close_time && PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
PositionGetInteger(POSITION_MAGIC) == _magic_number) {
|
||||
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double spread = SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
double bar_close = iClose(_Symbol, close_bar_period, 1); // shift 1 because 0 is live candle
|
||||
double trading_cost = calc.calculate_trading_cost(symbol, posTicket);
|
||||
|
||||
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY &&
|
||||
bar_close > (position_open_price + spread + trading_cost)) {
|
||||
trade.PositionClose(posTicket);
|
||||
}
|
||||
|
||||
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL &&
|
||||
bar_close < (position_open_price - spread - trading_cost)) {
|
||||
trade.PositionClose(posTicket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Closes position on first profitable bar after one bar completes.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to evaluate.
|
||||
// - close_bar_period : Timeframe for bar-close evaluation.
|
||||
// - _magic_number : Magic number to identify the trade group.
|
||||
//
|
||||
// Returns:
|
||||
// - True after evaluation and any attempted closes.
|
||||
// ---------------------------------------------------------------------
|
||||
bool ExitOrders::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long _magic_number) {
|
||||
position_open_time = PositionGetInteger(POSITION_TIME);
|
||||
first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period);
|
||||
|
||||
if ((int) position_open_time > 0 && TimeCurrent() > first_allowed_close_time) {
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
posTicket = PositionGetTicket(i);
|
||||
|
||||
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) {
|
||||
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double spread = SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||
double bar_close = iClose(_Symbol, close_bar_period, 1);
|
||||
double trading_cost = calc.calculate_trading_cost(symbol, posTicket);
|
||||
|
||||
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && bar_close > (position_open_price + spread + trading_cost)) {
|
||||
trade.PositionClose(posTicket);
|
||||
}
|
||||
|
||||
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && bar_close < (position_open_price - spread - trading_cost)) {
|
||||
trade.PositionClose(posTicket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#include <Trade/OrderInfo.mqh>
|
||||
#include <Trade/PositionInfo.mqh>
|
||||
|
||||
class OrderTracker {
|
||||
protected:
|
||||
COrderInfo m_order;
|
||||
CPositionInfo m_position;
|
||||
|
||||
public:
|
||||
int count_open_positions(string symbol, int order_side, long magic_number);
|
||||
int count_all_positions(string symbol, long magic_number);
|
||||
int count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic);
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Counts the number of open BUY or SELL positions for a given symbol.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Trading symbol (e.g., "EURUSD").
|
||||
// - order_side : 1 = BUY, 2 = SELL.
|
||||
// - magic_number : Magic number identifying strategy group.
|
||||
//
|
||||
// Returns:
|
||||
// - Number of matching open positions.
|
||||
// ---------------------------------------------------------------------
|
||||
int OrderTracker::count_open_positions(string symbol, int order_side, long magic_number) {
|
||||
int count = 0;
|
||||
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
|
||||
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||
if (order_side == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
|
||||
count++;
|
||||
}
|
||||
|
||||
if (order_side == 2 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Counts all open positions for a symbol regardless of direction.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Trading symbol.
|
||||
// - magic_number : Magic number identifying strategy group.
|
||||
//
|
||||
// Returns:
|
||||
// - Total number of matching positions.
|
||||
// ---------------------------------------------------------------------
|
||||
int OrderTracker::count_all_positions(string symbol, long magic_number) {
|
||||
int count = 0;
|
||||
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
|
||||
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Counts pending orders of a specific type for a symbol and magic number.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Trading symbol.
|
||||
// - order_type : Type of pending order (e.g., ORDER_TYPE_BUY_STOP).
|
||||
// - magic : Magic number identifying strategy group.
|
||||
//
|
||||
// Returns:
|
||||
// - Number of matching pending orders.
|
||||
// ---------------------------------------------------------------------
|
||||
int OrderTracker::count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic) {
|
||||
int count = 0;
|
||||
|
||||
for (int i = OrdersTotal() - 1; i >= 0; i--) {
|
||||
if (m_order.SelectByIndex(i)) {
|
||||
if (OrderGetInteger(ORDER_MAGIC) == magic && OrderGetString(ORDER_SYMBOL) == symbol) {
|
||||
if (m_order.OrderType() == order_type) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
class StopLogic {
|
||||
public:
|
||||
double sl_specified_value_switch(string sl_mode, double inp_sl_var, double value);
|
||||
double tp_specified_value_switch(string tp_mode, double inp_tp_var, double value);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Selects stop loss value based on SL mode.
|
||||
//
|
||||
// Parameters:
|
||||
// - sl_mode : Stop loss mode ("SL_SPECIFIED_VALUE", etc).
|
||||
// - inp_sl_var : User-input SL value (pips, percent, etc).
|
||||
// - value : Directly specified SL value.
|
||||
//
|
||||
// Returns:
|
||||
// - `value` if SL mode is "SL_SPECIFIED_VALUE", otherwise `inp_sl_var`.
|
||||
// ---------------------------------------------------------------------
|
||||
double StopLogic::sl_specified_value_switch(string sl_mode, double inp_sl_var, double value) {
|
||||
if (sl_mode == "SL_SPECIFIED_VALUE") {
|
||||
return value;
|
||||
} else {
|
||||
return inp_sl_var;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Selects take profit value based on TP mode.
|
||||
//
|
||||
// Parameters:
|
||||
// - tp_mode : Take profit mode ("TP_SPECIFIED_VALUE", etc).
|
||||
// - inp_tp_var : User-input TP value (pips, percent, etc).
|
||||
// - value : Directly specified TP value.
|
||||
//
|
||||
// Returns:
|
||||
// - `value` if TP mode is "TP_SPECIFIED_VALUE", otherwise `inp_tp_var`.
|
||||
// ---------------------------------------------------------------------
|
||||
double StopLogic::tp_specified_value_switch(string tp_mode, double inp_tp_var, double value) {
|
||||
if (tp_mode == "TP_SPECIFIED_VALUE") {
|
||||
return value;
|
||||
} else {
|
||||
return inp_tp_var;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
#property library
|
||||
#include <Trade/Trade.mqh>
|
||||
#include <MyLibs/Utils/MarketDataUtils.mqh>
|
||||
|
||||
class DrawdownControl : public CObject {
|
||||
protected:
|
||||
CTrade trade;
|
||||
MarketDataUtils m_utils;
|
||||
|
||||
string data_file;
|
||||
double daily_max_dd_per;
|
||||
string daily_reset_time;
|
||||
bool print_statments;
|
||||
|
||||
double acc_max_dd_per;
|
||||
double equaty_control_high;
|
||||
double equaty_control_low;
|
||||
|
||||
|
||||
double daily_equity_start;
|
||||
double daily_max_dd_target;
|
||||
bool daily_dd_limit_reached;
|
||||
|
||||
bool write_global_var_data();
|
||||
bool print_messages();
|
||||
|
||||
public:
|
||||
void init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true);
|
||||
bool determine_daily_dd_limit();
|
||||
double lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20);
|
||||
double lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Initializes the drawdown control with input parameters and reads
|
||||
// or resets persistent drawdown state.
|
||||
//
|
||||
// Parameters:
|
||||
// - inp_data_file : Filename for drawdown data storage.
|
||||
// - inp_acc_max_dd_per : Max absolute drawdown percentage.
|
||||
// - inp_daily_max_dd_per : Max daily drawdown percentage.
|
||||
// - inp_daily_reset_time : Reset time (e.g., "00:00").
|
||||
// - inp_print_statments : Optional flag to print debug info.
|
||||
// ---------------------------------------------------------------------
|
||||
void DrawdownControl::init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true) {
|
||||
|
||||
data_file = inp_data_file;
|
||||
acc_max_dd_per = inp_acc_max_dd_per;
|
||||
daily_max_dd_per = inp_daily_max_dd_per;
|
||||
daily_reset_time = inp_daily_reset_time;
|
||||
print_statments = inp_print_statments;
|
||||
|
||||
// If no data file exisits, create one and set global vairiables:
|
||||
if(FileIsExist(data_file) == false) {
|
||||
daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100));
|
||||
daily_dd_limit_reached = false;
|
||||
equaty_control_high = 9999999;
|
||||
equaty_control_low = 0;
|
||||
write_global_var_data();
|
||||
}
|
||||
// If file exisits read file:
|
||||
if(FileIsExist(data_file) == true) {
|
||||
|
||||
int file_handle = FileOpen(data_file, FILE_READ | FILE_ANSI | FILE_TXT);
|
||||
if(file_handle == INVALID_HANDLE) {
|
||||
Print("Error opening file: ", data_file);
|
||||
}
|
||||
|
||||
// If data file is older than 24h 10min create a new file and reset global vars:
|
||||
long modifided_date = FileGetInteger(file_handle, FILE_MODIFY_DATE);
|
||||
long time_delta = ((long)TimeCurrent() - modifided_date) / 60;
|
||||
|
||||
if(time_delta >= 1450) {
|
||||
daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100));
|
||||
daily_dd_limit_reached = false;
|
||||
equaty_control_high = equaty_control_high;
|
||||
equaty_control_low = equaty_control_low;
|
||||
write_global_var_data();
|
||||
Print(data_file, " is older than 24h and 10min; global vars reset!");
|
||||
}
|
||||
// If data file is younger than 24h+10 min read data and set global vars:
|
||||
else {
|
||||
daily_equity_start = (double)FileReadString(file_handle, 0);
|
||||
daily_max_dd_target = (double)FileReadString(file_handle, 1);
|
||||
daily_dd_limit_reached = FileReadBool(file_handle);
|
||||
equaty_control_high = (double)FileReadString(file_handle, 3);
|
||||
equaty_control_low = (double)FileReadString(file_handle, 4);;
|
||||
}
|
||||
FileClose(file_handle);
|
||||
}
|
||||
print_messages();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Checks if current equity has breached the daily drawdown threshold.
|
||||
// If so, closes all trades and cancels orders.
|
||||
//
|
||||
// Returns:
|
||||
// - true if daily drawdown limit has been reached.
|
||||
// ---------------------------------------------------------------------
|
||||
bool DrawdownControl::determine_daily_dd_limit() {
|
||||
|
||||
// Reset max equity at the start of each day:
|
||||
string ct = TimeToString(TimeCurrent(), TIME_MINUTES);
|
||||
if(ct == daily_reset_time) {
|
||||
daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
daily_max_dd_target = (daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100)));
|
||||
daily_dd_limit_reached = false;
|
||||
write_global_var_data();
|
||||
print_messages();
|
||||
}
|
||||
|
||||
// If in drawdown close all positions and delete orders
|
||||
if(daily_dd_limit_reached || AccountInfoDouble(ACCOUNT_EQUITY) <= daily_max_dd_target) {
|
||||
|
||||
if(daily_dd_limit_reached == false) {
|
||||
daily_dd_limit_reached = true;
|
||||
write_global_var_data();
|
||||
print_messages();
|
||||
}
|
||||
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
trade.PositionClose(ticket);
|
||||
}
|
||||
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--) {
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
trade.OrderDelete(ticket);
|
||||
}
|
||||
}
|
||||
return daily_dd_limit_reached;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Calculates a corrected lot multiplier based on account drawdown.
|
||||
//
|
||||
// Parameters:
|
||||
// - acc_equity_start : Starting equity reference.
|
||||
// - min_lot_factor : Minimum lot scaling factor.
|
||||
// - max_lot_factor : Maximum lot scaling factor.
|
||||
// - dynm_lot_factor : Enable trailing dynamic lot logic.
|
||||
// - dlf_trail_per : Percent buffer for dynamic trail.
|
||||
//
|
||||
// Returns:
|
||||
// - Scaled lot factor between min and max bounds.
|
||||
// ---------------------------------------------------------------------
|
||||
double DrawdownControl::lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20) {
|
||||
|
||||
double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE));
|
||||
double lot_factor;
|
||||
|
||||
// Interpolate to find lot factor between given min and max values.
|
||||
if (account_value < acc_equity_start){
|
||||
|
||||
double acc_equity_min = acc_equity_start - (acc_equity_start * (acc_max_dd_per / 100));
|
||||
double y1 = min_lot_factor;
|
||||
double y2 = max_lot_factor;
|
||||
double x1 = acc_equity_min;
|
||||
double x = account_value;
|
||||
double x2 = acc_equity_start;
|
||||
lot_factor = y1 + (x - x1) * ((y2 - y1) / (x2 - x1));
|
||||
}
|
||||
|
||||
else if(account_value >= acc_equity_start) {
|
||||
|
||||
if(dynm_lot_factor=true){
|
||||
lot_factor = lot_correction_dynamic(dlf_trail_per, min_lot_factor, max_lot_factor);
|
||||
}
|
||||
|
||||
else {
|
||||
lot_factor = max_lot_factor;
|
||||
}
|
||||
}
|
||||
return max_lot_factor;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Computes dynamic lot factor based on trailing equity bounds.
|
||||
//
|
||||
// Parameters:
|
||||
// - acc_dd_percent : Dynamic trailing buffer in percent.
|
||||
// - min_lot_factor : Minimum lot factor.
|
||||
// - max_lot_factor : Maximum lot factor.
|
||||
//
|
||||
// Returns:
|
||||
// - Interpolated lot factor.
|
||||
// ---------------------------------------------------------------------
|
||||
double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor) {
|
||||
|
||||
double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE));
|
||||
double trail_point = account_value - (account_value * (acc_dd_percent / 100));
|
||||
|
||||
if(equaty_control_low < trail_point){
|
||||
equaty_control_low = trail_point;
|
||||
}
|
||||
|
||||
if(equaty_control_high < account_value){
|
||||
equaty_control_high = account_value;
|
||||
}
|
||||
|
||||
if(account_value < equaty_control_low){
|
||||
equaty_control_low = account_value;
|
||||
equaty_control_high = account_value + (account_value * (acc_dd_percent / 100));
|
||||
}
|
||||
|
||||
// back-up to file every hour:
|
||||
if(m_utils.is_new_bar(_Symbol, PERIOD_H1) == true){
|
||||
write_global_var_data();
|
||||
}
|
||||
|
||||
// Linear interpolation:
|
||||
double y1 = min_lot_factor;
|
||||
double y2 = max_lot_factor;
|
||||
double x1 = equaty_control_low;
|
||||
double x = account_value;
|
||||
double x2 = equaty_control_high;
|
||||
|
||||
double y = y1 + (x - x1) * ((y2 - y1) / (x2 - x1));
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
bool DrawdownControl::write_global_var_data() {
|
||||
int file_handle = FileOpen(data_file, FILE_WRITE | FILE_ANSI | FILE_TXT);
|
||||
FileWrite(file_handle, daily_equity_start);
|
||||
FileWrite(file_handle, daily_max_dd_target);
|
||||
FileWrite(file_handle, daily_dd_limit_reached);
|
||||
FileClose(file_handle);
|
||||
Print(data_file, " written");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DrawdownControl::print_messages() {
|
||||
if(print_statments == true) {
|
||||
Print("TimeCurrent(): ", TimeToString(TimeCurrent()));
|
||||
Print("Daily Equity Start: ", (int)daily_equity_start);
|
||||
Print("Current Equity: ", (int)AccountInfoDouble(ACCOUNT_EQUITY));
|
||||
Print("Daily Drawdown Limit: ", (int)daily_max_dd_target, " (", daily_max_dd_per, "%) of DES");
|
||||
Print("Daily Drawdown Limit Hit: ", daily_dd_limit_reached);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
#property library
|
||||
#include <Trade/Trade.mqh>
|
||||
#include <MyLibs/Utils/TimeZones.mqh>
|
||||
|
||||
class RangeCalculator : public CObject{
|
||||
|
||||
protected:
|
||||
TimeZones tz;
|
||||
|
||||
bool days_initlised;
|
||||
bool range_initlised;
|
||||
string symbol;
|
||||
ENUM_TIMEFRAMES calc_period;
|
||||
|
||||
string inp_r_start_string;
|
||||
int r_duration;
|
||||
int r_expire;
|
||||
int r_close;
|
||||
string inp_timezone;
|
||||
|
||||
bool sun;
|
||||
bool mon;
|
||||
bool tue;
|
||||
bool wed;
|
||||
bool thu;
|
||||
bool fri;
|
||||
bool sat;
|
||||
bool plot_range;
|
||||
datetime start_time; // Start of the range
|
||||
datetime end_time; // end of the range
|
||||
datetime order_expire_time; // end of the range
|
||||
datetime close_time; // Close time
|
||||
double high; // high of the range
|
||||
double low; // low of the range
|
||||
double mid; // mid of the range
|
||||
bool f_entry; // flag if we are inside of the range
|
||||
bool f_high_breakout; // flag if a high breakout occurred
|
||||
bool f_low_breakout; // flag if a low breakout occurred
|
||||
bool above_last;
|
||||
bool above_current;
|
||||
bool below_last;
|
||||
bool below_current;
|
||||
|
||||
// private functions
|
||||
void update_objects();
|
||||
void draw_objects();
|
||||
void define_new_range();
|
||||
bool convert_input_time_strings(string t1, string t2, string t3, string t4);
|
||||
|
||||
|
||||
public:
|
||||
void calculate_range();
|
||||
double get_range_high();
|
||||
double get_range_low();
|
||||
double get_range_mid();
|
||||
datetime get_range_start();
|
||||
datetime get_range_end();
|
||||
datetime get_order_expire_time();
|
||||
datetime get_range_close();
|
||||
bool get_range_high_breakout();
|
||||
bool get_range_low_breakout();
|
||||
bool initilise_range(string inp_symbol, ENUM_TIMEFRAMES _calc_period, string t0, string t1, string t2, string t3, string time_zone, bool plot_range_inp);
|
||||
void range_days(bool _inp_sun, bool _inp_mon, bool _inp_tue, bool _inp_wed, bool _inp_thu, bool _inp_fri, bool _inp_sat);
|
||||
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sets the allowed days for range calculation.
|
||||
//
|
||||
// Parameters:
|
||||
// - _inp_sun : Allow Sunday.
|
||||
// - _inp_mon : Allow Monday.
|
||||
// - _inp_tue : Allow Tuesday.
|
||||
// - _inp_wed : Allow Wednesday.
|
||||
// - _inp_thu : Allow Thursday.
|
||||
// - _inp_fri : Allow Friday.
|
||||
// - _inp_sat : Allow Saturday.
|
||||
// ---------------------------------------------------------------------
|
||||
void RangeCalculator::range_days(bool _inp_sun, bool _inp_mon, bool _inp_tue, bool _inp_wed, bool _inp_thu, bool _inp_fri, bool _inp_sat){
|
||||
sun = _inp_sun;
|
||||
mon = _inp_mon;
|
||||
tue = _inp_tue;
|
||||
wed = _inp_wed;
|
||||
thu = _inp_thu;
|
||||
fri = _inp_fri;
|
||||
sat = _inp_sat;
|
||||
days_initlised = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Initializes the range parameters.
|
||||
//
|
||||
// Parameters:
|
||||
// - inp_symbol : The symbol for the range.
|
||||
// - _calc_period : Timeframe for range calculation.
|
||||
// - t1 : Start time string.
|
||||
// - t2 : End time string.
|
||||
// - t3 : Expiry time string.
|
||||
// - t4 : Close time string.
|
||||
// - time_zone : Timezone name.
|
||||
// - plot_range_inp : Whether to plot the range.
|
||||
//
|
||||
// Returns:
|
||||
// - true if initialization was successful; false otherwise.
|
||||
// ---------------------------------------------------------------------
|
||||
bool RangeCalculator::initilise_range(string inp_symbol, ENUM_TIMEFRAMES _calc_period, string t1, string t2, string t3, string t4, string time_zone, bool plot_range_inp){
|
||||
inp_r_start_string = t1;
|
||||
inp_timezone = time_zone;
|
||||
symbol = inp_symbol;
|
||||
calc_period =_calc_period;
|
||||
plot_range = plot_range_inp;
|
||||
start_time = 0;
|
||||
end_time = 0;
|
||||
close_time = 0;
|
||||
high = 0;
|
||||
low = DBL_MAX;
|
||||
mid = 0;
|
||||
f_entry = false;
|
||||
f_high_breakout = false;
|
||||
f_low_breakout = false;
|
||||
above_last = false;
|
||||
above_current= false;
|
||||
below_last= false;
|
||||
below_current= false;
|
||||
if(!days_initlised){
|
||||
sun = true;
|
||||
mon = true;
|
||||
tue = true;
|
||||
wed = true;
|
||||
thu = true;
|
||||
fri = true;
|
||||
sat = true;
|
||||
}
|
||||
range_initlised = true;
|
||||
|
||||
bool corret_inputs = convert_input_time_strings(t1, t2, t3, t4);
|
||||
if(corret_inputs = false){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Converts time input strings to time deltas for range definition.
|
||||
//
|
||||
// Parameters:
|
||||
// - t1 : Start time string.
|
||||
// - t2 : End time string.
|
||||
// - t3 : Expiry time string.
|
||||
// - t4 : Close time string.
|
||||
//
|
||||
// Returns:
|
||||
// - true if times were converted successfully; false on error.
|
||||
// ---------------------------------------------------------------------
|
||||
bool RangeCalculator::convert_input_time_strings(string t1, string t2, string t3, string t4){
|
||||
|
||||
datetime _t1 = StringToTime(t1);
|
||||
datetime _t2 = StringToTime(t2);
|
||||
datetime _t3 = StringToTime(t3);
|
||||
datetime _t4 = StringToTime(t4);
|
||||
|
||||
|
||||
if(_t1 > _t2){
|
||||
_t2 = _t2 + PeriodSeconds(PERIOD_D1);
|
||||
_t3 = _t3 + PeriodSeconds(PERIOD_D1);
|
||||
_t4 = _t4 + PeriodSeconds(PERIOD_D1);
|
||||
}
|
||||
|
||||
if(_t2 > _t3){
|
||||
_t3 = _t3 + PeriodSeconds(PERIOD_D1);
|
||||
_t4 = _t4 + PeriodSeconds(PERIOD_D1);
|
||||
}
|
||||
|
||||
if(_t3 > _t4){
|
||||
_t4 = _t4 + PeriodSeconds(PERIOD_D1);
|
||||
}
|
||||
|
||||
r_duration = (int)(_t2 - _t1);
|
||||
r_expire = (int)(_t3 - _t1);
|
||||
r_close = (int)(_t4 - _t1);
|
||||
|
||||
if(_t4 - _t1 >= PeriodSeconds(PERIOD_D1)){
|
||||
Alert("INCORRECT RANGE INPUTS!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the high value of the current range.
|
||||
//
|
||||
// Returns:
|
||||
// - High price of the range.
|
||||
// ---------------------------------------------------------------------
|
||||
double RangeCalculator::get_range_high(){
|
||||
return high;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the low value of the current range.
|
||||
//
|
||||
// Returns:
|
||||
// - Low price of the range.
|
||||
// ---------------------------------------------------------------------
|
||||
double RangeCalculator::get_range_low(){
|
||||
return low;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the mid value of the current range.
|
||||
//
|
||||
// Returns:
|
||||
// - Mid price of the range.
|
||||
// ---------------------------------------------------------------------
|
||||
double RangeCalculator::get_range_mid(){
|
||||
return mid;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the start time of the current range.
|
||||
//
|
||||
// Returns:
|
||||
// - Range start time.
|
||||
// ---------------------------------------------------------------------
|
||||
datetime RangeCalculator::get_range_start(){
|
||||
return start_time;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the end time of the current range.
|
||||
//
|
||||
// Returns:
|
||||
// - Range end time.
|
||||
// ---------------------------------------------------------------------
|
||||
datetime RangeCalculator::get_range_end(){
|
||||
return end_time;
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the expiration time for range-based orders.
|
||||
//
|
||||
// Returns:
|
||||
// - Order expiration time.
|
||||
// ---------------------------------------------------------------------
|
||||
datetime RangeCalculator::get_order_expire_time(){
|
||||
return order_expire_time;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the close time of the current range.
|
||||
//
|
||||
// Returns:
|
||||
// - Range close time.
|
||||
// ---------------------------------------------------------------------
|
||||
datetime RangeCalculator::get_range_close(){
|
||||
return close_time;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the high breakout flag of the current range.
|
||||
//
|
||||
// Returns:
|
||||
// - true if high breakout occurred; false otherwise.
|
||||
// ---------------------------------------------------------------------
|
||||
bool RangeCalculator::get_range_high_breakout(){
|
||||
return f_high_breakout;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the low breakout flag of the current range.
|
||||
//
|
||||
// Returns:
|
||||
// - true if low breakout occurred; false otherwise.
|
||||
// ---------------------------------------------------------------------
|
||||
bool RangeCalculator::get_range_low_breakout(){
|
||||
return f_low_breakout;
|
||||
};
|
||||
|
||||
|
||||
void RangeCalculator::calculate_range(){
|
||||
|
||||
f_high_breakout = false;
|
||||
f_low_breakout = false;
|
||||
|
||||
double last_bar_high = iHigh(symbol, calc_period, 1); // shift 1 because 0 = live candle:
|
||||
double last_bar_low = iLow(symbol, calc_period, 1); // shift 1 because 0 = live candle:
|
||||
|
||||
// range calculation
|
||||
if(TimeCurrent() >= start_time && TimeCurrent() <= end_time){
|
||||
|
||||
// set flag
|
||||
f_entry = true;
|
||||
|
||||
// new high
|
||||
if(last_bar_high > high){
|
||||
high = last_bar_high;
|
||||
mid = (high + low)/2;
|
||||
if(plot_range){
|
||||
update_objects();
|
||||
}
|
||||
}
|
||||
|
||||
// new low
|
||||
if(last_bar_low < low){
|
||||
low = last_bar_low;
|
||||
mid = (high + low)/2;
|
||||
if(plot_range){
|
||||
update_objects();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate new reange if
|
||||
if( (TimeCurrent() >= close_time) // close time reached
|
||||
|| (end_time == 0) // range not calculated yet
|
||||
|| (end_time !=0 && TimeCurrent() > end_time && !f_entry) // there was a range calculated but no tick inside.
|
||||
){
|
||||
define_new_range();
|
||||
}
|
||||
|
||||
// check if we are after the range end
|
||||
if(TimeCurrent() >= end_time && end_time > 0 && f_entry){
|
||||
|
||||
if(!f_high_breakout && last_bar_high >= high){
|
||||
above_last = above_current;
|
||||
above_current= true;
|
||||
|
||||
if(above_last==false && above_current == true){
|
||||
f_high_breakout = true;
|
||||
}
|
||||
else(f_high_breakout = false);
|
||||
}
|
||||
|
||||
if(!f_low_breakout && last_bar_low >= low){
|
||||
below_last = below_current;
|
||||
below_current = true;
|
||||
if(below_last == false && below_current == true){
|
||||
f_low_breakout = true;
|
||||
}
|
||||
else(f_low_breakout = false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void RangeCalculator::define_new_range(){
|
||||
|
||||
// reset range vars
|
||||
start_time = 0;
|
||||
end_time = 0;
|
||||
order_expire_time = 0;
|
||||
close_time = 0;
|
||||
high = 0;
|
||||
low = INT_MAX;
|
||||
mid = 0;
|
||||
f_entry = false;
|
||||
|
||||
// calculate range start time:
|
||||
datetime r_st = StringToTime(inp_r_start_string);
|
||||
start_time = tz.timezone_conversions(inp_timezone, r_st, "Broker");
|
||||
|
||||
|
||||
for(int i=0; i<8; i++){
|
||||
|
||||
MqlDateTime tmp;
|
||||
TimeToStruct(start_time,tmp);
|
||||
int dow = tmp.day_of_week;
|
||||
|
||||
if(TimeCurrent()>=start_time
|
||||
|| (dow==0 && !sun)
|
||||
|| (dow==1 && !mon)
|
||||
|| (dow==2 && !tue)
|
||||
|| (dow==3 && !wed)
|
||||
|| (dow==4 && !thu)
|
||||
|| (dow==5 && !fri)
|
||||
|| (dow==6 && !sat)
|
||||
){
|
||||
start_time += PeriodSeconds(PERIOD_D1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
end_time = start_time + r_duration;
|
||||
order_expire_time = start_time + r_expire;
|
||||
close_time = start_time + r_close;
|
||||
|
||||
if(plot_range){
|
||||
draw_objects();
|
||||
}
|
||||
}
|
||||
|
||||
void RangeCalculator::update_objects(){
|
||||
|
||||
string name = "Range Mid " + (string)start_time;
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, mid);
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, mid);
|
||||
// ObjectSetString(NULL, name , OBJPROP_TOOLTIP, "Range Mid");
|
||||
|
||||
name = "Order expire " + (string)order_expire_time;
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high);
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low);
|
||||
|
||||
name = "Range start " + (string)start_time;
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high);
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low);
|
||||
|
||||
name = "Range end " + (string)end_time;
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high);
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low);
|
||||
|
||||
datetime rct = r_close>=0 ? close_time : INT_MAX;
|
||||
name = "Range close " + (string)rct;
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high);
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low);
|
||||
|
||||
name = "Range High " + (string)rct;
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high);
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, high);
|
||||
|
||||
name = "Range Low " + (string)rct;
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, low);
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low);
|
||||
|
||||
name = "range box "+ (string)start_time;
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high);
|
||||
ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low);
|
||||
ObjectSetDouble(NULL, name +" ", OBJPROP_PRICE,0, high);
|
||||
ObjectSetDouble(NULL, name +" ", OBJPROP_PRICE,1, low);
|
||||
|
||||
}
|
||||
|
||||
void RangeCalculator::draw_objects(){
|
||||
|
||||
datetime rct = r_close>=0 ? close_time : INT_MAX;
|
||||
|
||||
// Range mid line
|
||||
string name = "Range Mid " + (string)start_time;;
|
||||
ObjectCreate(NULL, name, OBJ_TREND, 0, start_time, mid, rct, mid);
|
||||
ObjectSetString(NULL, name , OBJPROP_TOOLTIP, "Range Mid" + (string)mid);
|
||||
ObjectSetInteger(NULL, name, OBJPROP_COLOR, clrGray);
|
||||
ObjectSetInteger(NULL, name, OBJPROP_WIDTH, 1);
|
||||
ObjectSetInteger(NULL, name, OBJPROP_STYLE, STYLE_DOT);
|
||||
|
||||
// order lines
|
||||
string name2 = "Order expire " + (string)order_expire_time;
|
||||
ObjectCreate(NULL, name2, OBJ_TREND, 0, order_expire_time, low, order_expire_time, high);
|
||||
ObjectSetString(NULL, name2, OBJPROP_TOOLTIP, "start of the range \n" + TimeToString(order_expire_time,TIME_DATE|TIME_MINUTES));
|
||||
ObjectSetInteger(NULL, name2, OBJPROP_COLOR, C'139,41,41');
|
||||
ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2);
|
||||
ObjectSetInteger(NULL, name2,OBJPROP_BACK, true);
|
||||
|
||||
name2 = "Range start " + (string)start_time;
|
||||
ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, low, start_time, high);
|
||||
ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack);
|
||||
ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2);
|
||||
ObjectSetInteger(NULL, name2,OBJPROP_BACK, true);
|
||||
|
||||
name2 = "Range end " + (string)end_time;
|
||||
ObjectCreate(NULL, name2, OBJ_TREND, 0, end_time, low, end_time, high);
|
||||
ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack);
|
||||
ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2);
|
||||
ObjectSetInteger(NULL, name2,OBJPROP_BACK, true);
|
||||
|
||||
name2 = "Range close " + (string)rct;
|
||||
ObjectCreate(NULL, name2, OBJ_TREND, 0, rct, low, rct, high);
|
||||
ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack);
|
||||
ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2);
|
||||
ObjectSetInteger(NULL, name2,OBJPROP_BACK, true);
|
||||
|
||||
name2 = "Range High " + (string)rct;
|
||||
ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, high, rct, high);
|
||||
ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack);
|
||||
ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2);
|
||||
ObjectSetInteger(NULL, name2,OBJPROP_BACK, true);
|
||||
|
||||
name2 = "Range Low " + (string)rct;
|
||||
ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, low, rct, low);
|
||||
ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack);
|
||||
ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2);
|
||||
ObjectSetInteger(NULL, name2 ,OBJPROP_BACK, true);
|
||||
|
||||
// Box
|
||||
name = "range box " + (string)start_time;
|
||||
ObjectCreate(NULL, name, OBJ_RECTANGLE, 0, start_time, high, end_time, low);
|
||||
ObjectSetString(NULL,name,OBJPROP_TOOLTIP,"\n");
|
||||
ObjectSetInteger(NULL, name,OBJPROP_COLOR, C'128,177,173');
|
||||
ObjectSetInteger(NULL, name,OBJPROP_FILL, true);
|
||||
ObjectSetInteger(NULL, name,OBJPROP_BACK, true);
|
||||
|
||||
ObjectCreate(NULL, name + " ", OBJ_RECTANGLE, 0, end_time, high, rct, low);
|
||||
ObjectSetString(NULL, name+ " ", OBJPROP_TOOLTIP, "\n");
|
||||
ObjectSetInteger(NULL, name + " ",OBJPROP_FILL, true);
|
||||
ObjectSetInteger(NULL, name + " ",OBJPROP_COLOR, C'165,220,215' );
|
||||
ObjectSetInteger(NULL, name + " ",OBJPROP_BACK, true);
|
||||
|
||||
ChartRedraw();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#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:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns a valid ATR handle for the given symbol, timeframe, and period.
|
||||
// Creates and caches the handle if not already available.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Trading symbol (e.g., "EURUSD").
|
||||
// - tf : Timeframe (e.g., PERIOD_H1).
|
||||
// - period : ATR period (e.g., 14).
|
||||
//
|
||||
// Returns:
|
||||
// - The ATR indicator handle, or INVALID_HANDLE if failed.
|
||||
// ---------------------------------------------------------------------
|
||||
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;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Gets the ATR value for a given symbol, timeframe, and period.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Trading symbol (e.g., "EURUSD").
|
||||
// - tf : Timeframe to use (e.g., PERIOD_H1).
|
||||
// - period : ATR period to calculate.
|
||||
// - shift : Bar shift to read the value from (default is 1 for last closed bar. NEVER use 0!).
|
||||
//
|
||||
// Returns:
|
||||
// - ATR value at the given shift, or EMPTY_VALUE on 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];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Releases all cached ATR handles and clears the internal cache.
|
||||
//
|
||||
// Logic:
|
||||
// - Calls IndicatorRelease for each handle.
|
||||
// - Clears the `cache` array.
|
||||
// ---------------------------------------------------------------------
|
||||
void release_handles() {
|
||||
for (int i = 0; i < ArraySize(cache); i++) {
|
||||
if (cache[i].handle != INVALID_HANDLE)
|
||||
IndicatorRelease(cache[i].handle);
|
||||
}
|
||||
ArrayResize(cache, 0);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <Object.mqh>
|
||||
|
||||
class ChartUtils : public CObject {
|
||||
public:
|
||||
void draw_line(double value, string name, color clr = clrBlack);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Draws or updates a horizontal line on the chart at the given price level.
|
||||
//
|
||||
// Parameters:
|
||||
// - value : Price level at which to draw the line.
|
||||
// - name : Unique name for the line object.
|
||||
// - clr : Line color (default is black).
|
||||
//
|
||||
// Logic:
|
||||
// - If the object doesn't exist, it creates a new horizontal line.
|
||||
// - If the object exists, it moves it to the new price level.
|
||||
// - Calls ChartRedraw to update the chart visually.
|
||||
// ---------------------------------------------------------------------
|
||||
void ChartUtils::draw_line(double value, string name, color clr) {
|
||||
if (ObjectFind(0, name) < 0) {
|
||||
ResetLastError();
|
||||
|
||||
if (!ObjectCreate(0, name, OBJ_HLINE, 0, 0, value)) {
|
||||
Print(__FUNCTION__, ": failed to create a horizontal line! Error code = ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
|
||||
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
|
||||
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
|
||||
}
|
||||
|
||||
ResetLastError();
|
||||
|
||||
if (!ObjectMove(0, name, 0, 0, value)) {
|
||||
Print(__FUNCTION__, ": failed to move the horizontal line! Error code = ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
ChartRedraw();
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
enum LOT_MODE{
|
||||
LOT_MODE_FIXED, // Fixed Lot Size
|
||||
LOT_MODE_PCT_ACCOUNT, // Percent of Account (fixed)
|
||||
LOT_MODE_PCT_RISK // Percent of Account at Risk (from SL)
|
||||
};
|
||||
enum SL_MODE{
|
||||
SL_FIXED_PIPS, // Fixed Pips
|
||||
SL_FIXED_PERCENT, // Fixed Percent
|
||||
SL_ATR_MULTIPLE, // ATR Multiple
|
||||
SL_SPECIFIED_VALUE, // Bespoke calculation in code
|
||||
NO_STOPLOSS, // No Stop-loss
|
||||
SL_BREAKEVEN, // Breakeven
|
||||
};
|
||||
enum TP_MODE{
|
||||
TP_FIXED_PIPS, // Fixed Pips
|
||||
TP_FIXED_PERCENT, // Fixed Percent
|
||||
TP_ATR_MULTIPLE, // ATR Multiple
|
||||
TP_SL_MULTIPLE, // Multiple of Risk (from sl)
|
||||
TP_SPECIFIED_VALUE, // Bespoke calculation in code
|
||||
NO_TAKE_PROFIT, // No Take-Profit
|
||||
};
|
||||
|
||||
enum TSL_MODE{
|
||||
TSL_ATR_MULTIPLE, // ATR Multiple
|
||||
TSL_FIXED_PIPS, // Fixed Pips
|
||||
TSL_FIXED_PERCENT, // Fixed Percent
|
||||
};
|
||||
|
||||
enum TIME_ZONES{
|
||||
NY, // New York
|
||||
Lon, // London
|
||||
Ffm, // Frankfurt
|
||||
Syd, // Sidney
|
||||
Mosc, // Moscow
|
||||
Tok, // Tokyo - no DST
|
||||
};
|
||||
enum MULTI_SYM_MODE{
|
||||
MULTI_SYM_CHART, // Chart Symbol only
|
||||
MULTI_SYM_FX_B5, // FX Benchmark 5
|
||||
MULTI_SYM_FX_28 // FX 28 Majors
|
||||
};
|
||||
@@ -0,0 +1,148 @@
|
||||
class MarketDataUtils {
|
||||
public:
|
||||
bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time = "00:10");
|
||||
double get_latest_buffer_value(int handle);
|
||||
double get_buffer_value(int handle, int shift);
|
||||
double adjusted_point(string symbol);
|
||||
double get_bid_ask_price(string symbol, int price_side);
|
||||
|
||||
protected:
|
||||
datetime previousTimes[]; // Stores last recorded open time per key
|
||||
string bar_keys[]; // Keys are symbol+TF combinations, e.g. "EURUSD_PERIOD_H1"
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Performs linear search on a string array.
|
||||
//
|
||||
// Parameters:
|
||||
// - arr : Array of strings.
|
||||
// - target : Target string to find.
|
||||
//
|
||||
// Returns:
|
||||
// - Index of the target, or -1 if not found.
|
||||
// ---------------------------------------------------------------------
|
||||
int LinearSearch(string& arr[], string target) {
|
||||
for (int i = 0; i < ArraySize(arr); i++) {
|
||||
if (arr[i] == target) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Implementation of is_new_bar. Tracks the open time of the last bar.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to check.
|
||||
// - time_frame : Timeframe to check.
|
||||
// - daily_start_time: Time string for daily bar sync.
|
||||
//
|
||||
// Returns:
|
||||
// - true if a new bar has formed, false otherwise.
|
||||
// ---------------------------------------------------------------------
|
||||
bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) {
|
||||
datetime bar_open_time = iTime(symbol, time_frame, 0); // Current open time
|
||||
string key = symbol + "_" + EnumToString(time_frame);
|
||||
|
||||
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) {
|
||||
if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) {
|
||||
if (TimeCurrent() > StringToTime(daily_start_time)) {
|
||||
previousTimes[idx] = bar_open_time;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
previousTimes[idx] = bar_open_time;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Implementation of get_buffer_value.
|
||||
//
|
||||
// Parameters:
|
||||
// - handle : Indicator handle.
|
||||
// - shift : Shift index for historical bars.
|
||||
//
|
||||
// Returns:
|
||||
// - The buffer value, or EMPTY_VALUE if error.
|
||||
// ---------------------------------------------------------------------
|
||||
double MarketDataUtils::get_buffer_value(int handle, int shift) {
|
||||
double val[];
|
||||
ArraySetAsSeries(val, true);
|
||||
|
||||
int copied = CopyBuffer(handle, 0, shift, 1, val);
|
||||
if (copied <= 0) {
|
||||
Print("CopyBuffer failed: handle=", handle, " shift=", shift);
|
||||
return EMPTY_VALUE;
|
||||
}
|
||||
|
||||
if (val[0] == EMPTY_VALUE) {
|
||||
Print("EMPTY_VALUE returned for buffer at shift=", shift);
|
||||
return EMPTY_VALUE;
|
||||
}
|
||||
|
||||
return val[0];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Gets the latest (live) value from buffer (shift = 0).
|
||||
//
|
||||
// Parameters:
|
||||
// - handle : Indicator handle.
|
||||
//
|
||||
// Returns:
|
||||
// - Buffer value at shift 0 or EMPTY_VALUE if failed.
|
||||
// ---------------------------------------------------------------------
|
||||
double MarketDataUtils::get_latest_buffer_value(int handle) {
|
||||
return get_buffer_value(handle, 0);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Computes adjusted point value considering fractional pip brokers.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol name.
|
||||
//
|
||||
// Returns:
|
||||
// - Adjusted point multiplier.
|
||||
// ---------------------------------------------------------------------
|
||||
double MarketDataUtils::adjusted_point(string symbol) {
|
||||
int symbol_digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
int digits_adjust = (symbol_digits == 3 || symbol_digits == 5) ? 10 : 1;
|
||||
double point_val = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
return point_val * digits_adjust;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns bid or ask price for a given symbol.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol name.
|
||||
// - price_side : 1 = Ask, 2 = Bid.
|
||||
//
|
||||
// Returns:
|
||||
// - Price value or 0.0 if input is invalid.
|
||||
// ---------------------------------------------------------------------
|
||||
double MarketDataUtils::get_bid_ask_price(string symbol, int price_side) {
|
||||
int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), digits);
|
||||
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), digits);
|
||||
|
||||
if (price_side == 1) return ask;
|
||||
if (price_side == 2) return bid;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#include <MyLibs/Utils/SignalStateTracker.mqh>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// This class manages a collection of SignalStateTracker instances — one per symbol.
|
||||
// It allows you to track signals separately for each symbol in a multi-symbol EA.
|
||||
// ---------------------------------------------------------------------
|
||||
class MultiSymbolSignalTracker {
|
||||
private:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Internal struct to associate a symbol with a SignalStateTracker.
|
||||
// ---------------------------------------------------------------------
|
||||
struct SymbolTracker {
|
||||
string symbol;
|
||||
SignalStateTracker* tracker;
|
||||
};
|
||||
|
||||
SymbolTracker trackers[]; // Dynamic array of symbol-tracker mappings
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Finds the index of the symbol in the tracker array.
|
||||
// ---------------------------------------------------------------------
|
||||
int find_index(const string& symbol) {
|
||||
for (int i = 0; i < ArraySize(trackers); i++) {
|
||||
if (trackers[i].symbol == symbol)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Destructor. Cleans up allocated memory when the object is destroyed.
|
||||
// ---------------------------------------------------------------------
|
||||
~MultiSymbolSignalTracker() {
|
||||
clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Retrieves the SignalStateTracker instance for a given symbol.
|
||||
// Creates and stores a new one if it doesn't exist yet.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol for which to retrieve the signal tracker.
|
||||
//
|
||||
// Returns:
|
||||
// - Pointer to the SignalStateTracker instance.
|
||||
// ---------------------------------------------------------------------
|
||||
SignalStateTracker* get_tracker(const string& symbol) {
|
||||
int idx = find_index(symbol);
|
||||
if (idx != -1)
|
||||
return trackers[idx].tracker;
|
||||
|
||||
// Create new tracker
|
||||
SignalStateTracker* tracker = new SignalStateTracker();
|
||||
|
||||
SymbolTracker item;
|
||||
item.symbol = symbol;
|
||||
item.tracker = tracker;
|
||||
|
||||
ArrayResize(trackers, ArraySize(trackers) + 1);
|
||||
trackers[ArraySize(trackers) - 1] = item;
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Clears all SignalStateTracker instances and resets the internal array.
|
||||
// Should be called in `OnDeinit()` to avoid memory leaks.
|
||||
//
|
||||
// Logic:
|
||||
// - Deletes each dynamically allocated SignalStateTracker.
|
||||
// - Resets array size to 0.
|
||||
// ---------------------------------------------------------------------
|
||||
void clear() {
|
||||
for (int i = 0; i < ArraySize(trackers); i++) {
|
||||
delete trackers[i].tracker;
|
||||
}
|
||||
ArrayResize(trackers, 0);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
#include <MyLibs/Utils/AtrHandleManager.mqh>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// ResourceManager
|
||||
//
|
||||
// Tracks and releases indicator handles (e.g., iMA, iRSI, iCCI).
|
||||
// Delegates ATR handle management to an external AtrHandleManager instance.
|
||||
//
|
||||
// Usage:
|
||||
// - Register indicator handles via `register_handle()`.
|
||||
// - Call `release_all_handles()` to release all cached handles.
|
||||
// ---------------------------------------------------------------------
|
||||
class ResourceManager {
|
||||
public:
|
||||
|
||||
AtrHandleManager* atr_manager;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Registers a generic indicator handle to be released later.
|
||||
//
|
||||
// Parameters:
|
||||
// - handle : A valid (non-INVALID_HANDLE) indicator handle.
|
||||
//
|
||||
// Logic:
|
||||
// - If the handle is valid, it is added to an internal list.
|
||||
// ---------------------------------------------------------------------
|
||||
void register_handle(int handle) {
|
||||
if (handle != INVALID_HANDLE)
|
||||
add_handle(handle);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Releases all tracked indicator resources.
|
||||
//
|
||||
// Logic:
|
||||
// - Releases generic handles tracked via `register_handle()`.
|
||||
// - Also invokes `atr_manager.release_handles()` if assigned.
|
||||
// ---------------------------------------------------------------------
|
||||
void release_all_handles() {
|
||||
release_internal_handles(); // ATR manager
|
||||
release_tracked_handles(); // Generic indicator handles
|
||||
}
|
||||
|
||||
private:
|
||||
int handles[];
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Adds a handle to the internal tracking list.
|
||||
// ---------------------------------------------------------------------
|
||||
void add_handle(int handle) {
|
||||
int size = ArraySize(handles);
|
||||
ArrayResize(handles, size + 1);
|
||||
handles[size] = handle;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Releases all tracked indicator handles (iMA, iRSI, etc.).
|
||||
// ---------------------------------------------------------------------
|
||||
void release_tracked_handles() {
|
||||
for (int i = 0; i < ArraySize(handles); i++) {
|
||||
if (handles[i] != INVALID_HANDLE)
|
||||
IndicatorRelease(handles[i]);
|
||||
}
|
||||
ArrayFree(handles);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Releases any cached ATR handles using the external AtrHandleManager.
|
||||
// ---------------------------------------------------------------------
|
||||
void release_internal_handles() {
|
||||
if (atr_manager != NULL)
|
||||
atr_manager.release_handles();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
class SignalStateTracker {
|
||||
private:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Index of the last long signal detected (default -1000 when unset).
|
||||
// ---------------------------------------------------------------------
|
||||
int last_signal_long;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Index of the last short signal detected (default -1000 when unset).
|
||||
// ---------------------------------------------------------------------
|
||||
int last_signal_short;
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Constructor initializes the signal tracker to a reset state.
|
||||
//
|
||||
// Logic:
|
||||
// - Sets both long and short signal indices to -1000.
|
||||
// ---------------------------------------------------------------------
|
||||
SignalStateTracker() {
|
||||
reset();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Resets the signal tracker.
|
||||
//
|
||||
// Logic:
|
||||
// - Sets `last_signal_long` and `last_signal_short` to -1000,
|
||||
// representing no signal recorded.
|
||||
// ---------------------------------------------------------------------
|
||||
void reset() {
|
||||
last_signal_long = -1000;
|
||||
last_signal_short = -1000;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Updates internal state based on whether long/short signals occurred.
|
||||
//
|
||||
// Parameters:
|
||||
// - signal_long : True if a long signal occurred this bar.
|
||||
// - signal_short : True if a short signal occurred this bar.
|
||||
//
|
||||
// Logic:
|
||||
// - If a signal is detected, sets the index to 1 (bar 1).
|
||||
// - Otherwise, increments the previous value if it was positive.
|
||||
// ---------------------------------------------------------------------
|
||||
void update_signal_tracker(bool signal_long, bool signal_short) {
|
||||
if (signal_long) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Checks if a long signal occurred within the last N bars.
|
||||
//
|
||||
// Parameters:
|
||||
// - max_bars : Number of bars to look back for the signal.
|
||||
//
|
||||
// Returns:
|
||||
// - True if a long signal occurred within `max_bars` bars.
|
||||
// ---------------------------------------------------------------------
|
||||
bool long_signal_recent(int max_bars) const {
|
||||
return has_long_signal() && (1 - last_signal_long <= max_bars);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Checks if a short signal occurred within the last N bars.
|
||||
//
|
||||
// Parameters:
|
||||
// - max_bars : Number of bars to look back for the signal.
|
||||
//
|
||||
// Returns:
|
||||
// - True if a short signal occurred within `max_bars` bars.
|
||||
// ---------------------------------------------------------------------
|
||||
bool short_signal_recent(int max_bars) const {
|
||||
return has_short_signal() && (1 - last_signal_short <= max_bars);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Indicates if any long signal has ever been recorded.
|
||||
//
|
||||
// Returns:
|
||||
// - True if a long signal index is not equal to -1000.
|
||||
// ---------------------------------------------------------------------
|
||||
bool has_long_signal() const {
|
||||
return last_signal_long != -1000;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Indicates if any short signal has ever been recorded.
|
||||
//
|
||||
// Returns:
|
||||
// - True if a short signal index is not equal to -1000.
|
||||
// ---------------------------------------------------------------------
|
||||
bool has_short_signal() const {
|
||||
return last_signal_short != -1000;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the last bar index at which a long signal occurred.
|
||||
//
|
||||
// Returns:
|
||||
// - Integer index representing bars since long signal.
|
||||
// ---------------------------------------------------------------------
|
||||
int get_long_signal() const {
|
||||
return last_signal_long;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the last bar index at which a short signal occurred.
|
||||
//
|
||||
// Returns:
|
||||
// - Integer index representing bars since short signal.
|
||||
// ---------------------------------------------------------------------
|
||||
int get_short_signal() const {
|
||||
return last_signal_short;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
#property library
|
||||
#include <MyLibs/Utils/DealingWithTime.mqh>
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
class TimeZones : public CObject {
|
||||
protected:
|
||||
string dt_s;
|
||||
int len;
|
||||
string dt_string;
|
||||
datetime tC, tGMT, tNY, tLon, tFfm, tMosc, tSyd, tTok;
|
||||
datetime tz_time;
|
||||
string tz_date;
|
||||
datetime time_start;
|
||||
datetime time_end;
|
||||
bool is_time;
|
||||
datetime tGIVEN;
|
||||
datetime tREQ;
|
||||
datetime tzt;
|
||||
datetime tz_req;
|
||||
double required_close;
|
||||
|
||||
double ny_daily_close_protected(string symbol, int shift_days, bool print_data = false);
|
||||
|
||||
public:
|
||||
string get_date_string_from_datetime(datetime dt);
|
||||
datetime get_timezone_time(string time_zone, bool print_time);
|
||||
datetime timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required);
|
||||
double ny_daily_close(string symbol, int shift_days, bool print_data = false);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Converts a datetime to a string excluding seconds.
|
||||
//
|
||||
// Parameters:
|
||||
// - dt : Datetime object.
|
||||
//
|
||||
// Returns:
|
||||
// - A string in the format "yyyy.mm.dd hh:mi".
|
||||
// ---------------------------------------------------------------------
|
||||
string TimeZones::get_date_string_from_datetime(datetime dt) {
|
||||
dt_s = TimeToString(dt);
|
||||
len = StringLen(dt_s);
|
||||
dt_string = StringSubstr(dt_s, 0, len - 5);
|
||||
return dt_string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Gets the current time in the specified time zone.
|
||||
//
|
||||
// Parameters:
|
||||
// - time_zone : One of "NY", "Lon", "Ffm", "Syd", "Mosc", "Tok".
|
||||
// - print_time : If true, logs various times for debugging.
|
||||
//
|
||||
// Returns:
|
||||
// - Current time in the specified time zone.
|
||||
// ---------------------------------------------------------------------
|
||||
datetime TimeZones::get_timezone_time(string time_zone, bool print_time) {
|
||||
checkTimeOffset(TimeCurrent()); // Adjust DST
|
||||
|
||||
tC = TimeCurrent();
|
||||
tGMT = TimeCurrent() + OffsetBroker.actOffset;
|
||||
tNY = tGMT - (NYShift + DST_USD);
|
||||
tLon = tGMT - (LondonShift + DST_EUR);
|
||||
tFfm = tGMT - (FfmShift + DST_EUR);
|
||||
tSyd = tGMT - (SidneyShift + DST_AUD);
|
||||
tMosc = tGMT - (MoskwaShift + DST_RUS);
|
||||
tTok = tGMT - (TokyoShift);
|
||||
|
||||
if (print_time) {
|
||||
Print("----------------------------------");
|
||||
Print("Broker: ", tC);
|
||||
Print("GMT: ", tGMT);
|
||||
Print("time in New York: ", tNY);
|
||||
Print("time in London: ", tLon);
|
||||
Print("time in Frankfurt: ", tFfm);
|
||||
Print("time in Sidney: ", tSyd);
|
||||
Print("time in Moscow: ", tMosc);
|
||||
Print("time in Tokyo: ", tTok);
|
||||
}
|
||||
|
||||
if (time_zone == "NY") return tNY;
|
||||
if (time_zone == "Lon") return tLon;
|
||||
if (time_zone == "Ffm") return tFfm;
|
||||
if (time_zone == "Syd") return tSyd;
|
||||
if (time_zone == "Mosc") return tMosc;
|
||||
if (time_zone == "Tok") return tTok;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Converts a datetime from one timezone to another.
|
||||
//
|
||||
// Parameters:
|
||||
// - time_zone_known : Original timezone of the datetime.
|
||||
// - time_given : The datetime to convert.
|
||||
// - time_zone_required : Desired output timezone.
|
||||
//
|
||||
// Returns:
|
||||
// - The equivalent datetime in the target timezone.
|
||||
// ---------------------------------------------------------------------
|
||||
datetime TimeZones::timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required) {
|
||||
tGIVEN = time_given;
|
||||
checkTimeOffset(tGIVEN); // Adjust DST
|
||||
|
||||
// Step 1: Convert known timezone to GMT
|
||||
if (time_zone_known == "GMT") tGMT = tGIVEN;
|
||||
if (time_zone_known == "Broker") tGMT = tGIVEN + OffsetBroker.actOffset;
|
||||
if (time_zone_known == "NY") tGMT = tGIVEN + (NYShift + DST_USD);
|
||||
if (time_zone_known == "Lon") tGMT = tGIVEN + (LondonShift + DST_EUR);
|
||||
if (time_zone_known == "Ffm") tGMT = tGIVEN + (FfmShift + DST_EUR);
|
||||
if (time_zone_known == "Syd") tGMT = tGIVEN + (SidneyShift + DST_AUD);
|
||||
if (time_zone_known == "Mosc") tGMT = tGIVEN + (MoskwaShift + DST_RUS);
|
||||
if (time_zone_known == "Tok") tGMT = tGIVEN + (TokyoShift);
|
||||
|
||||
// Step 2: Convert GMT to required timezone
|
||||
if (time_zone_required == "GMT") tREQ = tGMT;
|
||||
if (time_zone_required == "Broker") tREQ = tGMT - OffsetBroker.actOffset;
|
||||
if (time_zone_required == "NY") tREQ = tGMT - (NYShift + DST_USD);
|
||||
if (time_zone_required == "Lon") tREQ = tGMT - (LondonShift + DST_EUR);
|
||||
if (time_zone_required == "Ffm") tREQ = tGMT - (FfmShift + DST_EUR);
|
||||
if (time_zone_required == "Syd") tREQ = tGMT - (SidneyShift + DST_AUD);
|
||||
if (time_zone_required == "Mosc") tREQ = tGMT - (MoskwaShift + DST_RUS);
|
||||
if (time_zone_required == "Tok") tREQ = tGMT - (TokyoShift);
|
||||
|
||||
return tREQ;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the most recent NY daily close price.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Trading symbol.
|
||||
// - shift_days : How many NY daily closes back to return.
|
||||
// - print_data : If true, logs debug information.
|
||||
//
|
||||
// Returns:
|
||||
// - The NY close price.
|
||||
// ---------------------------------------------------------------------
|
||||
double TimeZones::ny_daily_close(string symbol, int shift_days, bool print_data) {
|
||||
required_close = ny_daily_close_protected(symbol, shift_days, print_data);
|
||||
return required_close;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Internal implementation to compute NY daily close price.
|
||||
//
|
||||
// Logic:
|
||||
// - Defines NY close as 5pm NY time = 00:00 broker + 17H back.
|
||||
// - Adjusts for day shifts if required.
|
||||
// - Returns the close price of the NY daily session.
|
||||
// ---------------------------------------------------------------------
|
||||
double TimeZones::ny_daily_close_protected(string symbol, int shift_days, bool print_data) {
|
||||
datetime time_5pm = iTime(symbol, PERIOD_D1, 0) - (PeriodSeconds(PERIOD_H1) * 7);
|
||||
datetime ny_close_in_brokers_time = timezone_conversions("NY", time_5pm, "Broker");
|
||||
datetime ny_close_time = ny_close_in_brokers_time + PeriodSeconds(PERIOD_D1);
|
||||
|
||||
if (TimeCurrent() < ny_close_time) ny_close_time -= PeriodSeconds(PERIOD_D1);
|
||||
|
||||
int shift = iBarShift(symbol, PERIOD_H1, ny_close_time, false) + 1;
|
||||
shift += (24 * (shift_days - 1));
|
||||
|
||||
double ny_close = iClose(symbol, PERIOD_H1, shift);
|
||||
double br_close = iClose(symbol, PERIOD_H1, 1);
|
||||
|
||||
if (print_data) {
|
||||
Print("shift ", shift);
|
||||
Print("time_5pm ", time_5pm);
|
||||
Print("ny_close_in_brokers_time ", ny_close_in_brokers_time);
|
||||
Print("ny_close_time ", ny_close_time);
|
||||
Print("ny_close ", ny_close);
|
||||
Print("br_close ", br_close);
|
||||
}
|
||||
|
||||
return ny_close;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#include <MyLibs/Utils/TimeZones.mqh>
|
||||
|
||||
class TradeSessionUtils {
|
||||
|
||||
protected:
|
||||
TimeZones tz; // For handling timezone conversion
|
||||
bool in_window; // Whether the current time is in the allowed window
|
||||
datetime start_time; // Session start time (converted to Broker time)
|
||||
datetime end_time; // Session end time (converted to Broker time)
|
||||
|
||||
public:
|
||||
bool trade_window(string t1, string t2, string time_zone = "Broker", bool plot_range_inp = true);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Determines whether the current time is inside a defined trade session.
|
||||
//
|
||||
// Parameters:
|
||||
// - t1 : Start time string (e.g., "22:00").
|
||||
// - t2 : End time string (e.g., "01:00").
|
||||
// - time_zone : The timezone of input times (default = "Broker").
|
||||
// - plot_range_inp : If true, draws vertical lines for start/end.
|
||||
//
|
||||
// Returns:
|
||||
// - true if current time is within the session window, false otherwise.
|
||||
//
|
||||
// Logic:
|
||||
// - Handles overnight sessions (e.g. 22:00–01:00) correctly.
|
||||
// - Automatically rolls the window to the next day when expired.
|
||||
// - Uses TimeZones class to convert time to broker timezone.
|
||||
// ---------------------------------------------------------------------
|
||||
bool TradeSessionUtils::trade_window(string t1, string t2, string time_zone, bool plot_range_inp) {
|
||||
datetime _t1 = StringToTime(t1); // Convert string to datetime
|
||||
datetime _t2 = StringToTime(t2); // Convert string to datetime
|
||||
|
||||
// Handle overnight windows (e.g. 22:00–01:00)
|
||||
if (_t1 > _t2) {
|
||||
_t2 = _t2 + PeriodSeconds(PERIOD_D1);
|
||||
}
|
||||
|
||||
int w_duration = (int)(_t2 - _t1); // Duration of the session in seconds
|
||||
|
||||
// Check if we're currently within the window
|
||||
if (TimeCurrent() >= start_time && TimeCurrent() <= end_time) {
|
||||
in_window = true;
|
||||
}
|
||||
|
||||
// If we've moved beyond the previous window, define a new one
|
||||
if (TimeCurrent() >= end_time) {
|
||||
in_window = false;
|
||||
|
||||
// Convert start time to broker time based on user timezone input
|
||||
start_time = tz.timezone_conversions(time_zone, StringToTime(t1), "Broker");
|
||||
|
||||
// If we've already passed today's start time, push it to tomorrow
|
||||
if (TimeCurrent() >= start_time) {
|
||||
start_time += PeriodSeconds(PERIOD_D1);
|
||||
}
|
||||
|
||||
// End time is relative to updated start time
|
||||
end_time = start_time + w_duration;
|
||||
|
||||
// Plot vertical lines if requested
|
||||
if (plot_range_inp) {
|
||||
string name = "Start Time" + (string)start_time;
|
||||
if (start_time > 0) {
|
||||
ObjectCreate(NULL, name, OBJ_VLINE, 0, start_time, 0);
|
||||
ObjectSetInteger(NULL, name, OBJPROP_COLOR, clrBlue);
|
||||
ObjectSetInteger(NULL, name, OBJPROP_BACK, true);
|
||||
}
|
||||
|
||||
name = "End Time" + (string)end_time;
|
||||
if (end_time > 0) {
|
||||
ObjectCreate(NULL, name, OBJ_VLINE, 0, end_time, 0);
|
||||
ObjectSetInteger(NULL, name, OBJPROP_COLOR, C'56,108,26');
|
||||
ObjectSetInteger(NULL, name, OBJPROP_BACK, true);
|
||||
}
|
||||
|
||||
ChartRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
return in_window;
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
#include <MyLibs/Utils/MarketDataUtils.mqh>
|
||||
#include <MyLibs/Utils/AtrHandleManager.mqh>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// CLASS: AtrBands
|
||||
// ---------------------------------------------------------------------
|
||||
// Provides ATR-based upper/lower/middle band calculations and checks.
|
||||
// Useful for volatility-based stop placement, trend filters, or overlays.
|
||||
// ---------------------------------------------------------------------
|
||||
class AtrBands {
|
||||
private:
|
||||
MarketDataUtils market_data_utils;
|
||||
AtrHandleManager atr_manager;
|
||||
|
||||
double get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift);
|
||||
|
||||
public:
|
||||
double upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the upper ATR band level.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to use.
|
||||
// - trendline_var : Base trendline value (e.g., MA).
|
||||
// - atr_period : ATR period.
|
||||
// - tf : Timeframe for ATR.
|
||||
// - mult : ATR multiplier.
|
||||
// - shift : Bar shift to evaluate.
|
||||
// ---------------------------------------------------------------------
|
||||
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);
|
||||
return trendline_var + atr * mult;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the lower ATR band level.
|
||||
//
|
||||
// Parameters:
|
||||
// - Same as upper_band, except lower band logic.
|
||||
// ---------------------------------------------------------------------
|
||||
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);
|
||||
return trendline_var - atr * mult;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Returns the middle band, which is simply the trendline.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol.
|
||||
// - trendline_var : The trendline value.
|
||||
// - tf : Timeframe (unused here).
|
||||
// - shift : Shift index (unused here).
|
||||
// ---------------------------------------------------------------------
|
||||
double AtrBands::middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift) {
|
||||
return trendline_var;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Checks if price is between trendline and upper band.
|
||||
//
|
||||
// Logic:
|
||||
// - Retrieves trendline and price.
|
||||
// - Compares if price lies between trendline and upper band.
|
||||
// ---------------------------------------------------------------------
|
||||
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 trendline_var = market_data_utils.get_buffer_value(handle, shift);
|
||||
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;
|
||||
return (price > trendline_var && price < upper);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Checks if price is between trendline and lower band.
|
||||
//
|
||||
// Logic:
|
||||
// - Retrieves trendline and price.
|
||||
// - Compares if price lies between trendline and lower band.
|
||||
// ---------------------------------------------------------------------
|
||||
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 trendline_var = market_data_utils.get_buffer_value(handle, shift);
|
||||
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;
|
||||
return (price < trendline_var && price > lower);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Detects if price has crossed below the upper band.
|
||||
//
|
||||
// Logic:
|
||||
// - Checks crossover from above to below upper band between bars [shift+1] and [shift].
|
||||
// ---------------------------------------------------------------------
|
||||
bool AtrBands::crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) {
|
||||
double price = iClose(symbol, tf, shift);
|
||||
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 upper = upper_band(symbol, trendline_var, atr_period, tf, mult, shift);
|
||||
double prev_upper = upper_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Detects if price has crossed above the lower band.
|
||||
//
|
||||
// Logic:
|
||||
// - Checks crossover from below to above lower band between bars [shift+1] and [shift].
|
||||
// ---------------------------------------------------------------------
|
||||
bool AtrBands::crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) {
|
||||
double price = iClose(symbol, tf, shift);
|
||||
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 lower = lower_band(symbol, trendline_var, atr_period, tf, mult, shift);
|
||||
double prev_lower = lower_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Plots the ATR bands as OBJ_TREND lines on chart.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to draw on.
|
||||
// - handle : Trendline handle (e.g., MA).
|
||||
// - atr_period : ATR period for bands.
|
||||
// - tf : Timeframe.
|
||||
// - mult : ATR multiplier.
|
||||
// - bars : Number of bars to plot.
|
||||
// - clr : Line color.
|
||||
// - width : Line width.
|
||||
// ---------------------------------------------------------------------
|
||||
void AtrBands::plot_bands(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int bars, color line_color, int width) {
|
||||
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();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Retrieves the ATR value via the shared ATR manager.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Symbol to calculate ATR on.
|
||||
// - atr_period : ATR calculation period.
|
||||
// - tf : Timeframe of the ATR.
|
||||
// - shift : Bar index to retrieve.
|
||||
//
|
||||
// Returns:
|
||||
// - The ATR value at the given shift.
|
||||
// ---------------------------------------------------------------------
|
||||
double AtrBands::get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift) {
|
||||
return atr_manager.get_atr_value(symbol, tf, atr_period, shift);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#include <MyLibs/Utils/MarketDataUtils.mqh>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// CLASS: TrendlineAnalyser
|
||||
// ---------------------------------------------------------------------
|
||||
// A utility class to detect price crossovers with a trendline buffer.
|
||||
// Supports both crossover detection and trend direction checks.
|
||||
// ---------------------------------------------------------------------
|
||||
class TrendlineAnalyser {
|
||||
private:
|
||||
MarketDataUtils market_data_utils;
|
||||
|
||||
public:
|
||||
void detect_cross(string symbol, int handle, bool& cross_long, bool& cross_short, int shift = 1);
|
||||
void trend_direction(string symbol, int handle, bool& direction_long, bool& direction_short, int shift = 1);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Detects whether price has crossed above (long) or below (short)
|
||||
// a trendline between the two most recent closed bars.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Trading symbol (e.g., "EURUSD").
|
||||
// - handle : Indicator handle for the trendline buffer.
|
||||
// - cross_long : Output true if bullish crossover detected.
|
||||
// - cross_short : Output true if bearish crossover detected.
|
||||
// - shift : Bar shift to evaluate (default: 1 = last closed bar).
|
||||
//
|
||||
// Logic:
|
||||
// - Retrieves price and trendline values for the current and previous bar.
|
||||
// - Checks for a crossover by comparing price vs trendline movement.
|
||||
// ---------------------------------------------------------------------
|
||||
void TrendlineAnalyser::detect_cross(string symbol, int handle, bool& cross_long, bool& cross_short, int shift) {
|
||||
cross_long = false;
|
||||
cross_short = false;
|
||||
|
||||
double price = iClose(symbol, PERIOD_CURRENT, shift);
|
||||
double prev_price = iClose(symbol, PERIOD_CURRENT, shift + 1);
|
||||
|
||||
double trendline = market_data_utils.get_buffer_value(handle, shift);
|
||||
double prev_trendline = market_data_utils.get_buffer_value(handle, shift + 1);
|
||||
|
||||
if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline == EMPTY_VALUE || prev_trendline == EMPTY_VALUE)
|
||||
return;
|
||||
|
||||
cross_long = (prev_price < prev_trendline && price > trendline);
|
||||
cross_short = (prev_price > prev_trendline && price < trendline);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Checks if price is currently above or below the trendline.
|
||||
//
|
||||
// Parameters:
|
||||
// - symbol : Trading symbol (e.g., "EURUSD").
|
||||
// - handle : Trendline indicator handle.
|
||||
// - direction_long : Output true if price is above trendline.
|
||||
// - direction_short : Output true if price is below trendline.
|
||||
// - shift : Bar index to check (default: 1).
|
||||
//
|
||||
// Logic:
|
||||
// - Retrieves price and trendline at the specified shift.
|
||||
// - Compares relative position of price to trendline.
|
||||
// ---------------------------------------------------------------------
|
||||
void TrendlineAnalyser::trend_direction(string symbol, int handle, bool& direction_long, bool& direction_short, int shift) {
|
||||
direction_long = false;
|
||||
direction_short = false;
|
||||
|
||||
double price = iClose(symbol, PERIOD_CURRENT, shift);
|
||||
double trendline = market_data_utils.get_buffer_value(handle, shift);
|
||||
|
||||
if (price == EMPTY_VALUE || trendline == EMPTY_VALUE)
|
||||
return;
|
||||
|
||||
direction_long = (price > trendline);
|
||||
direction_short = (price < trendline);
|
||||
}
|
||||
Reference in New Issue
Block a user