gryps2/EA-code/handy_systems/RELEASE-CANDIDATE/84003-LULLABY for USDTRY/LULLABY for USDTRY.mq4
super.admin ae3f0ebf03 convert
2025-05-30 14:58:21 +02:00

1647 lines
58 KiB
MQL4
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//+------------------------------------------------------------------+
//| LULLABY for USDTRY.mq4 |
//| Copyright © 2024, HANDY SYSTEMS, All Rights Reserved. |
//| https://www.handy-systems.com/ |
//+------------------------------------------------------------------+
#property strict
// When compiling in PRODUCTION mode, comment out the text below.
#define _DEVELOPMENT
// When compiling in PRODUCTION mode, comment out the text above.
// To restrict the account to EA-BANK, uncomment the text below.
//#define _EA_BANK
// To restrict the account to EA-BANK, uncomment the text above.
// To use GogoJungle-style comments, uncomment the part below.
//#define _GOGOJUNGLE
// To use GogoJungle-style comments, uncomment the part above.
// To use an MQL5.com format EA, uncomment the code below.
//#define _MQL5_COM
// To use an MQL5.com format EA, uncomment the code above.
// To use LET'S REAL format EA, uncomment the code below.
//#define _LETS_REAL
// To use LET'S REAL format EA, uncomment the code above.
// To disable the logo display on the properties dialog, uncomment the line below.
//#define _DO_NOT_USE_LOGO
// To disable the logo display on the properties dialog, uncomment the line above.
#include <stdlib.mqh>
//----------EA-BANK certification begin
#ifdef _EA_BANK
#include <auth.mqh>
#endif // _EA_BANK
//----------EA-BANK certification end
#property copyright "Copyright © 2024 HANDY SYSTEMS, All Rights Reserved."
#ifndef _DO_NOT_USE_LOGO
#property icon "HANDY-SYSTEMS-LOGO.ico"
#endif // _DO_NOT_USE_LOGO
#property version "1.0" // Newly released
#ifdef _DEVELOPMENT
#define dinput input
#else
#define dinput
#endif //_DEVELOPMENT_
#ifdef _GOGOJUNGLE
const string STR_EA_ID = "XXXXX";
const string STR_EA_NAME = STR_EA_ID + ":" + "LULLABY_for_USDTRY";
#else
const string STR_EA_NAME = "LULLABY_for_USDTRY";
#endif // _GOGOJUNGLE
const string STR_OP_BUY = "BUY";
const string STR_OP_SELL = "SELL";
input int MagicNumber = 84003; // MagicNumber
input double Lots = 0.08; // Lots
// Lot control parameters
input bool CompoundMode = false; // Enable compound mode
input double RiskPercent = 2.0; // Risk percent of balance (%)
input double MinLots = 0.01; // Minimum lot size
input double MaxLots = 10.0; // Maximum lot size
input double MaxSpread = 0.0; // MaxSpread (pips, 0=auto-spread)
input double MinAllowedSwap = 0.0; // MinAllowedSwap (pips)
dinput double TakeProfit_pips = 1400.0; // TakeProfit (pips)
dinput double StopLoss_pips = 7500.0; // StopLoss (pips)
dinput double Internal_TakeProfit_pips = 770.0;
dinput double Internal_StopLoss_pips = 140.0;
// Trailing stop parameters
dinput double TrailingStopStartPips = 200.0;
dinput double TrailingStopPips = 160.0;
// Limit price cut parameters
dinput double ReLimitActionPips = 200.0;
dinput double ReLimitPips = 110.0;
// Maximum acceptable slippage
int Slippage = 120;
// Number of retry times
const int MAX_ORDER_REPEAT = 5;
// Market opening waiting time (second)
const int TIME_WAIT_FOR_MARKET_OPEN = 3600;
int xBars = 0, xxBars = 0;
double BuyStop = 0, SellStop = DBL_MAX;
double BuyLimit = DBL_MAX, SellLimit = 0;
// Maximum number of positions
int maxposition = 1;
// Whether it is operated only at the candlestick price
bool CandleStartStarting = true;
// Optimization control parameter
#define OPTIMIZE_RF 0x0001
#define OPTIMIZE_TR 0x0002
#define OPTIMIZE_WR 0x0004
// Optimization type
enum OPTYPE
{
OPTYPE_RF_ONLY = OPTIMIZE_RF, // RF (Recovery Factor)
OPTYPE_TR_ONLY = OPTIMIZE_TR, // TR (Number of trades)
OPTYPE_WR_ONLY = OPTIMIZE_WR, // WR (Win rate)
OPTYPE_RF_TR = OPTIMIZE_RF |
OPTIMIZE_TR, // RF+TR
OPTYPE_RF_WR = OPTIMIZE_RF |
OPTIMIZE_WR, // RF+WR
OPTYPE_TR_WR = OPTIMIZE_TR |
OPTIMIZE_WR, // TR+WR
OPTYPE_RF_TR_WR = OPTIMIZE_RF |
OPTIMIZE_TR |
OPTIMIZE_WR, // RF+TR+WR
OPTYPE_NONE = 0 // None
};
dinput OPTYPE OP_Type = OPTYPE_RF_ONLY; // Optimization type
// Spread contol variables
dinput double AllowSpread = 0.0; // Max Spread (pips, 0=auto-spread)
dinput double Gap_Pips = 10.0; // Price gap threshold (pips)
dinput double BaseSpreadLimit = 8.0; // Initial spread threshold (pips)
dinput double VolatilityAdjustment = 1.5; // Volatility adjustment factor (multiplier)
dinput double MaxAllowableSpread = 25.0; // Maximum allowed spread (pips)
dinput int MinimumSpreadSamples = 10; // Required samples before using statistics
dinput double ExtremeSpikeMultiplier = 2.0; // Multiple of average spread to consider extreme
dinput double WeightFactor = 2.0; // Weight factor for recent values in moving averagepips)
// Entry rule variables
dinput int BB_Period = 10; // Bollinger band calculation period
dinput double BB_Angle = 85.5; // Bollinger band angle
dinput double BB_Deviation = 0.5; // Bollinger band deviation
// Entry filter variables
// Variables for exit rules
dinput int MA_Break_Period = 60; // Moving average calculation period
dinput double SL_Acceptable_Pips = 3000.0; // Acceptable unrealized loss (pips)
// Global variables for spread monitoring
double CurrentSpread, SpreadMin = DBL_MAX, SpreadMax = 0;
double SpreadTotal = 0, SpreadAvg = 0;
double DynamicSpreadLimit = 0;
int SpreadSamples = 0;
int LastMonthBar;
#ifdef _LETS_REAL
// Define LET'S REAL authentication code
int lr_product_id=000; // PRODUCT ID
#import "wininet.dll"
int InternetAttemptConnect(int x);
int InternetOpenW(string &sAgent,int lAccessType,string &sProxyName,string &sProxyBypass,int lFlags);
int InternetConnectW(int hInternet,string &szServerName,int nServerPort,string &lpszUsername,string &lpszPassword,int dwService,int dwFlags,int dwContext);
int HttpOpenRequestW(int hConnect,string &Verb,string &ObjectName,string &Version,string &Referer,string &AcceptTypes,uint dwFlags,int dwContext);
int HttpSendRequestW(int hRequest,string &lpszHeaders,int dwHeadersLength,uchar &lpOptional[],int dwOptionalLength);
int HttpQueryInfoW(int hRequest,int dwInfoLevel,int &lpvBuffer[],int &lpdwBufferLength,int &lpdwIndex);
int InternetReadFile(int hFile,uchar &sBuffer[],int lNumBytesToRead,int &lNumberOfBytesRead);
int InternetCloseHandle(int hInet);
#import
#define OPEN_TYPE_PRECONFIG 0
#define FLAG_KEEP_CONNECTION 0x00400000
#define FLAG_PRAGMA_NOCACHE 0x00000100
#define FLAG_RELOAD 0x80000000
#define SERVICE_HTTP 3
#define CloseHandle InternetCloseHandle(lr_session);InternetCloseHandle(lr_connect);InternetCloseHandle(lr_request);InternetCloseHandle(lr_send);
string
lr_is_demo="0",
lr_platform="MT4_EA",
lr_version="1.07",
lr_host="api.lets-real.com",
lr_path="index.php?route=api/register",
lr_vers="HTTP/1.1",
lr_verb="POST",
lr_head="Content-Type: application/x-www-form-urlencoded",
lr_useragent="Mozilla",
lr_result,
lr_nill,
lr_postdata,
lr_ct,
lr_msg,
lr_msg1="Authentication Failed! ",
lr_msg2=" from lets-real.com ";
int lr_port=80,lr_session,lr_connect,lr_readfile,lr_request,lr_send,lr_auth_failure_times;
bool lr_auth_success=false,lr_auth_exec=true;
datetime lr_auth_time;
#endif // _LETS_REAL
//+------------------------------------------------------------------+
//| expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
#ifdef _EA_BANK
//----------EA-BANK certification begin
if(!auth()) {
return -1;
}
//----------EA-BANK certification end
// Display the authentication OK string
Comment("EA-BANK認証:OK"); // It means "EA-BANK authentication is OK" in Japanese
#endif // _EA_BANK
#ifdef _LETS_REAL
// Define LET'S REAL authentication code
lr_is_demo=IsDemo()?"1":"0";
if(IsTesting())
{
lr_auth_exec=false;
lr_auth_success=true;
}
if(lr_auth_exec && !IsDllsAllowed())
{
lr_msg=lr_msg1+"Please allow use of DLL "+lr_msg2;
Alert(lr_msg);
Print(lr_msg);
ExpertRemove();
return(INIT_FAILED);
}
#endif // _LETS_REAL
// Hide indicators in use
HideTestIndicators(true);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
#ifdef _EA_BANK
//----------EA-BANK certification begin
if(!auth()) {
return;
}
//----------EA-BANK certification end
#endif // _EA_BANK
#ifdef _LETS_REAL
// Define LET'S REAL authentication code
if(lr_auth_exec && TimeCurrent()>=lr_auth_time)
{
CloseHandle
string lr_p0,lr_p1,lr_p2,lr_p3;
char lr_data[];
lr_p0=IntegerToString(IsTradeAllowed());
lr_p1=AccountCompany();
lr_p2=IntegerToString(AccountNumber());
lr_p3=StringSubstr(WindowExpertName(),StringLen(WindowExpertName())-8,8);
lr_postdata="trade_allowed="+lr_p0+"&fx_company="+lr_p1+"&fx_account="+lr_p2+"&key="+lr_p3+"&ver="+lr_version+"&product_id="+IntegerToString(lr_product_id)+"&platform="+lr_platform+"&is_demo="+lr_is_demo;
StringToCharArray(lr_postdata,lr_data,0,WHOLE_ARRAY,CP_UTF8);
lr_session=InternetOpenW(lr_useragent,OPEN_TYPE_PRECONFIG,lr_nill,lr_nill,0);
lr_connect=InternetConnectW(lr_session,lr_host,lr_port,lr_nill,lr_nill,SERVICE_HTTP,0,0);
lr_request=HttpOpenRequestW(lr_connect,lr_verb,lr_path,lr_vers,lr_nill,lr_nill,FLAG_KEEP_CONNECTION|FLAG_RELOAD|FLAG_PRAGMA_NOCACHE,0);
lr_send=HttpSendRequestW(lr_request,lr_head,StringLen(lr_head),lr_data,ArraySize(lr_data)-1);
if(lr_send<=0)
{
lr_msg=lr_msg1+lr_msg2+"\n"+"Please check your network."+"\n"+
"InternetAttemptConnect="+IntegerToString(InternetAttemptConnect(0))+"\n"+
"session="+IntegerToString(lr_session)+"\n"+
"connect="+IntegerToString(lr_connect)+"\n"+
"request="+IntegerToString(lr_request)+"\n"+
"send="+IntegerToString(lr_send);
Comment(lr_msg);
return;
}
else
{
uchar response[100];
int dwBytes1;
lr_result="";
while(InternetReadFile(lr_request,response,0,dwBytes1))
{
lr_readfile=InternetReadFile(lr_request,response,100,dwBytes1);
if(dwBytes1<=0)
break;
lr_result=lr_result+CharArrayToString(response,0,dwBytes1);
}
string lr_to_split=lr_result;
string lr_sep=",";
string lr_respara[];
ushort lr_u_sep=StringGetCharacter(lr_sep,0);
int k=StringSplit(lr_to_split,lr_u_sep,lr_respara);
if(k>0)
{
for(int j=0; j<k; j++)
{
StringReplace(lr_respara[j],"{","");
StringReplace(lr_respara[j],"}","");
int lr_position=StringFind(lr_respara[j],":",0);
lr_respara[j]=StringSubstr(lr_respara[j],lr_position+1);
}
}
else
{
ArrayResize(lr_respara,1);
lr_respara[0]="false";
}
if(lr_respara[0]!="true")
{
lr_auth_success=false;
lr_auth_failure_times++;
int seconds=lr_auth_failure_times>4?60:5;
lr_auth_time=TimeCurrent()+seconds;
lr_msg=lr_msg1+lr_to_split+lr_msg2;
Comment(lr_msg);
Print(lr_result,lr_msg2);
}
else
{
lr_auth_success=true;
lr_auth_failure_times=0;
lr_auth_time=TimeCurrent()+43200;
Print("Authentication Success "+lr_msg2);
if(StringLen(lr_msg)>0)
Comment("");
}
}
}
if(!lr_auth_success)
return;
#endif // _LETS_REAL
int i;
int ticket;
int CountBuy = 0,CountSell = 0;
double profit;
bool res;
double xpoint = Point;
// Adjust the value of pips according to the number of decimal places
if (Digits() == 3 || Digits() == 5)
{
xpoint = xpoint * 10;
}
// If only the candlestick opening price is active (selection type), start from here
if(Bars != xBars || CandleStartStarting == false)
{
xBars = Bars;
// -------- Close position --------
// Close existing positions
for(i=OrdersTotal()-1; i>=0; i--)
{
// Order selection (if an error occurs, exit the loop)
if (OrderSelect( i, SELECT_BY_POS, MODE_TRADES ) == false)
{
Print("OrderSelect returned the error of ", GetLastError() );
break;
}
// Confirm the order (if the currency pair does not match, return to the beginning of the for statement)
if (OrderSymbol() != Symbol()) continue;
// Check the magic number (if the magic number does not match, return to the beginning of the for statement)
if (OrderMagicNumber() != MagicNumber) continue;
if (OrderType() == OP_BUY)
{
profit = Bid - OrderOpenPrice(); // Calculate the unrealized profit and loss for a lomg position
// Settle the order at market
if (profit >= Internal_TakeProfit_pips * xpoint || profit <= -Internal_StopLoss_pips * xpoint || Exit2() == 2)
{
res = WrapperOrderClose(OrderTicket(),
OrderLots(),
Bid,
NULL,
Green);
}
}
if (OrderType() == OP_SELL)
{
profit = OrderOpenPrice() - Ask; // Calculate the unrealized profit and loss for a short position
// Settle the order at market
if (profit >= Internal_TakeProfit_pips * xpoint || profit <= -Internal_StopLoss_pips * xpoint || Exit1() == 1)
{
res = WrapperOrderClose(OrderTicket(),
OrderLots(),
Ask,
NULL,
Green);
}
}
}
// -------- Close position (until here) --------
// -------- Trailing a position --------
// Trail the SL and TP of the owned position
for (i=OrdersTotal()-1; i>=0; i--)
{
// Order selection (if an error occurs, exit the loop)
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false)
{
Print("OrderSelect returned the error of ", GetLastError());
break;
}
// Confirm the order (if the currency pair does not match, return to the beginning of the for statement)
if (OrderSymbol() != Symbol()) continue;
// Check the magic number (if the magic number does not match, return to the beginning of the for statement)
if (OrderMagicNumber() != MagicNumber) continue;
if (OrderType() == OP_BUY)
{
// Calculate buy position profit and trailing levels
double buyProfit = NormalizeDouble(Bid - OrderOpenPrice(), Digits());
double buyStopLevel = NormalizeDouble(TrailingStopStartPips * xpoint, Digits());
// Check trailing stop conditions
if (buyProfit >= buyStopLevel)
{
double newSL = NormalizeDouble(Bid - TrailingStopPips * xpoint, Digits());
// Update only if new SL is more favorable than current SL
if (OrderStopLoss() < newSL || OrderStopLoss() == 0)
{
res = WrapperOrderModify(OrderTicket(),
OrderOpenPrice(),
newSL,
OrderTakeProfit(),
0,
Green);
if(!res) Print("Error modifying buy trailing stop: ", GetLastError());
}
}
// Check limit order adjustment conditions
double limitLevel = NormalizeDouble(OrderOpenPrice() - ReLimitActionPips * xpoint, Digits());
if (Bid <= limitLevel)
{
double newTP = NormalizeDouble(OrderOpenPrice() + ReLimitPips * xpoint, Digits());
// Update only if take profit has changed
if (OrderTakeProfit() != newTP)
{
res = WrapperOrderModify(OrderTicket(),
OrderOpenPrice(),
OrderStopLoss(),
newTP,
0,
Green);
if(!res) Print("Error modifying buy take profit: ", GetLastError());
}
}
}
else if (OrderType() == OP_SELL)
{
// Calculate sell position profit and trailing levels
double sellProfit = NormalizeDouble(OrderOpenPrice() - Ask, Digits());
double sellStopLevel = NormalizeDouble(TrailingStopStartPips * xpoint, Digits());
// Check trailing stop conditions
if (sellProfit >= sellStopLevel)
{
double newSL = NormalizeDouble(Ask + TrailingStopPips * xpoint, Digits());
// Update only if new SL is more favorable than current SL
if (OrderStopLoss() > newSL || OrderStopLoss() == 0)
{
res = WrapperOrderModify(OrderTicket(),
OrderOpenPrice(),
newSL,
OrderTakeProfit(),
0,
Green);
if(!res) Print("Error modifying sell trailing stop: ", GetLastError());
}
}
// Check limit order adjustment conditions
double limitLevel = NormalizeDouble(OrderOpenPrice() + ReLimitActionPips * xpoint, Digits());
if (Ask >= limitLevel)
{
double newTP = NormalizeDouble(OrderOpenPrice() - ReLimitPips * xpoint, Digits());
// Update only if take profit has changed
if (OrderTakeProfit() != newTP)
{
res = WrapperOrderModify(OrderTicket(),
OrderOpenPrice(),
OrderStopLoss(),
newTP,
0,
Green);
if(!res) Print("Error modifying sell take profit: ", GetLastError());
}
}
}
}
// -------- Trailing a position (until here) --------
// -------- Entry of new position --------
// Count the number of positions
for(i=OrdersTotal()-1; i>=0; i--)
{
// Order selection (if an error occurs, exit the loop)
if (OrderSelect( i, SELECT_BY_POS, MODE_TRADES ) == false)
{
Print("OrderSelect returned the error of ", GetLastError() );
break;
}
// Confirm the order (if the currency pair does not match, return to the beginning of the for statement)
if (OrderSymbol() != Symbol()) continue;
// Check the magic number (if the magic number does not match, return to the beginning of the for statement)
if (OrderMagicNumber() != MagicNumber) continue;
if (OrderType() == OP_BUY)
{
CountBuy = CountBuy + 1;
}
if (OrderType() == OP_SELL)
{
CountSell = CountSell + 1;
}
}
double lots = CalculateCompoundLots();
double correct_volume;
string message;
// Check the order quantity
if (!CheckVolumeValue(lots, correct_volume, message))
{
// If the order quantity is incorrect, set the correct quantity
Print(message);
lots = correct_volume;
}
// Check the entry conditions and enter if they are met.
if (Entry() == 1 && CountBuy < maxposition && xxBars != Bars)
{
// Check trading funds
if (!CheckMoneyForTrade(OrderSymbol(), lots, OrderType()))
{
return;
}
ticket = WrapperOrderSend(Symbol(),
OP_BUY,
lots,
Ask,
Slippage,
0,
0,
STR_EA_NAME + " " + STR_OP_BUY,
MagicNumber,
0,
Blue);
// If the entry is successful, set TP and SL
if (ticket != -1)
{
// Select an order using the ticket (if an error occurs, do nothing)
if( OrderSelect( ticket, SELECT_BY_TICKET ) == true )
{
res = WrapperOrderModify(OrderTicket(),
OrderOpenPrice(),
OrderOpenPrice() - StopLoss_pips * xpoint,
OrderOpenPrice() + TakeProfit_pips * xpoint,
0,
MediumSeaGreen);
}
}
xxBars = Bars;
}
else if (Entry() == 2 && CountSell < maxposition && xxBars != Bars)
{
// Check trading funds
if (!CheckMoneyForTrade(OrderSymbol(), lots, OrderType()))
{
return;
}
ticket = WrapperOrderSend(Symbol(),
OP_SELL,
lots,
Bid,
Slippage,
0,
0,
STR_EA_NAME + " " + STR_OP_SELL,
MagicNumber,
0,
Red);
// If the entry is successful, set TP and SL
if (ticket != -1)
{
// Select an order using the ticket (if an error occurs, do nothing)
if( OrderSelect( ticket, SELECT_BY_TICKET ) == true )
{
res = WrapperOrderModify(OrderTicket(),
OrderOpenPrice(),
OrderOpenPrice() + StopLoss_pips * xpoint,
OrderOpenPrice() - TakeProfit_pips * xpoint,
0,
MediumSeaGreen);
}
}
xxBars = Bars;
}
// -------- Entry of new position (until here) --------
}
// The range for candlestick opening price only (selection type) is here
// -------- Set TP, SL for the position --------
// Set TP, SL in the position
for(i=OrdersTotal()-1; i>=0; i--)
{
// Order selection (if an error occurs, exit the loop)
if (OrderSelect( i, SELECT_BY_POS, MODE_TRADES ) == false)
{
Print("OrderSelect returned the error of ", GetLastError() );
break;
}
// Confirm the order (if the currency pair does not match, return to the beginning of the for statement)
if (OrderSymbol() != Symbol()) continue;
// Check the magic number (if the magic number does not match, return to the beginning of the for statement)
if (OrderMagicNumber() != MagicNumber) continue;
// In case of a buy position
if (OrderType() == OP_BUY)
{
profit = Bid - OrderOpenPrice(); // // Calculate the unrealized profit and loss for a lomg position
// If neither TP nor SL is set, set TP and SL
if (OrderStopLoss() == 0 && OrderTakeProfit() == 0)
{
res = WrapperOrderModify(OrderTicket(),
OrderOpenPrice(),
OrderOpenPrice() - StopLoss_pips * xpoint,
OrderOpenPrice() + TakeProfit_pips * xpoint,
0,
MediumSeaGreen);
}
}
// In case of a sell position
if (OrderType() == OP_SELL)
{
profit = OrderOpenPrice() - Ask; // // Calculate the unrealized profit and loss for a short position
// If neither TP nor SL is set, set TP and SL
if (OrderStopLoss() == 0 && OrderTakeProfit() == 0)
{
res = WrapperOrderModify(OrderTicket(),
OrderOpenPrice(),
OrderOpenPrice() + StopLoss_pips * xpoint,
OrderOpenPrice() - TakeProfit_pips * xpoint,
0,
MediumSeaGreen);
}
}
}
// -------- Set TP, SL for the position (until here) --------
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Entry summary |
//+------------------------------------------------------------------+
int Entry()
{
if (Entry_Rule1() == 1 && (Entry_Filter1() == 1 || Entry_Filter1() == 3)
&& (Entry_Filter2() == 1 || Entry_Filter2() == 3)
&& (Entry_Filter3() == 1 || Entry_Filter3() == 3)
&& (Entry_Filter4() == 1 || Entry_Filter4() == 3)
&& (Entry_Filter5() == 1 || Entry_Filter5() == 3)
)
{
return(1);
}
else if (Entry_Rule1() == 2 && (Entry_Filter1() == 2 || Entry_Filter1() == 3)
&& (Entry_Filter2() == 2 || Entry_Filter2() == 3)
&& (Entry_Filter3() == 2 || Entry_Filter3() == 3)
&& (Entry_Filter4() == 2 || Entry_Filter4() == 3)
&& (Entry_Filter5() == 2 || Entry_Filter5() == 3)
)
{
return(2);
}
else
{
return(0);
}
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Summary of exit for closing buying position |
//+------------------------------------------------------------------+
int Exit1()
{
if (Exit_Rule1() == 3 && Exit_Rule2() == 3 && Exit_Rule3() == 3 && Exit_Rule4() == 3 && Exit_Rule5() == 3)
{
return(0);
}
else if (Exit_Rule1() == 1)
{
return(1);
}
else if( Exit_Rule2() == 1)
{
return(1);
}
else if (Exit_Rule3() == 1)
{
return(1);
}
else if( Exit_Rule4() == 1)
{
return(1);
}
else if (Exit_Rule5() == 1)
{
return(1);
}
else
{
return(0);
}
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Summary of exit for closing selling position |
//+------------------------------------------------------------------+
int Exit2()
{
if (Exit_Rule1() == 3 && Exit_Rule2() == 3 && Exit_Rule3() == 3 && Exit_Rule4() == 3 && Exit_Rule5() == 3)
{
return(0);
}
else if (Exit_Rule1() == 2)
{
return(2);
}
else if (Exit_Rule2() == 2)
{
return(2);
}
else if (Exit_Rule3() == 2)
{
return(2);
}
else if (Exit_Rule4() == 2)
{
return(2);
}
else if (Exit_Rule5() == 2)
{
return(2);
}
else
{
return(0);
}
}
//+------------------------------------------------------------------+
//| Check the accuracy of the order quantity |
//+------------------------------------------------------------------+
bool CheckVolumeValue(double volume,double &correct_volume, string &message)
{
// Minimum volume allowed in a trade operation
double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
if(volume<min_volume)
{
message=StringFormat("Volume is less than the minimal allowed SYMBOL_VOLUME_MIN=%.2f",min_volume);
correct_volume = min_volume;
return(false);
}
// Maximum volume allowed in a trade operation
double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
if(volume>max_volume)
{
message=StringFormat("Volume is greater than the maximal allowed SYMBOL_VOLUME_MAX=%.2f",max_volume);
correct_volume = max_volume;
return(false);
}
// Get the minimum step for quantity change
double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP);
int ratio=(int)MathRound(volume/volume_step);
if(MathAbs(ratio*volume_step-volume)>0.0000001)
{
message=StringFormat("Volume is not a multiple of the minimal step SYMBOL_VOLUME_STEP=%.2f, the closest correct volume is %.2f",
volume_step,ratio*volume_step);
correct_volume = ratio*volume_step;
return(false);
}
message="Correct volume value";
correct_volume = volume;
return(true);
}
//+------------------------------------------------------------------+
//| Check if trading funds are insufficient |
//+------------------------------------------------------------------+
bool CheckMoneyForTrade(string symbol, double lots,int type)
{
if (!IsTesting())
{
double free_margin=AccountFreeMarginCheck(symbol, type, lots);
// If funds are insufficient
if(free_margin<0)
{
string op = (type == OP_BUY ? "Buy" : "Sell");
Print("Not enough money for ", op," ",lots, " ", symbol, " Error code=",GetLastError());
return(false);
}
}
return(true);
}
//+------------------------------------------------------------------+
//| Calculate lot size based on account balance |
//+------------------------------------------------------------------+
double CalculateCompoundLots()
{
// If compound mode is disabled, return the fixed lot size
if (!CompoundMode)
return Lots;
double balance = AccountBalance();
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
// Adjust tick value for JPY pairs
if (Digits == 3 || Digits == 5)
tickValue *= 10;
// Calculate position size based on risk percentage
double riskAmount = balance * (RiskPercent / 100.0);
double lotSize = riskAmount / (StopLoss_pips * tickValue);
// Normalize lot size
lotSize = NormalizeDouble(lotSize, 2);
// Apply minimum and maximum lot constraints
lotSize = MathMax(MinLots, lotSize);
lotSize = MathMin(MaxLots, lotSize);
return lotSize;
}
//+------------------------------------------------------------------+
#ifdef _DEVELOPMENT
//+------------------------------------------------------------------+
//| OnTester - Optimize with the specified optimization type |
//+------------------------------------------------------------------+
double OnTester()
{
// Set the optimization criterion for the number of trades to 100 per years
const double TRADES_STANDARD = 100.0 * (Year() - TimeYear(iTime(NULL, 0, Bars(NULL, 0) - 1)));
// Initializing optimization criterion value
double optimization_value = 0.0;
double recovery_factor = 0.0;
double trades_optimization_factor = 0.0;
double win_rate = 0.0;
// To calculate the recovery factor, obtain the net profit and the maximum margin-based drawdown
double profit = TesterStatistics(STAT_PROFIT);
double max_drawdown = TesterStatistics(STAT_EQUITY_DD);
// If the maximum drawdown is non-zero, calculate the recovery factor
if(max_drawdown != 0)
{
recovery_factor = profit / max_drawdown;
}
//Get the number of trades
double trades = TesterStatistics(STAT_TRADES);
// If the number of trades is not 0, calculate the win rate (total wins/total trades)
if(trades != 0)
{
win_rate = TesterStatistics(STAT_PROFIT_TRADES) / trades;
}
// Finding the optimization coefficient for the number of trades
trades_optimization_factor = trades / TRADES_STANDARD;
// Optimization criterion value = the sum of the element values depending on the optimization type
switch(OP_Type)
{
case OPTYPE_RF_ONLY:
optimization_value = recovery_factor;
break;
case OPTYPE_TR_ONLY:
optimization_value = trades_optimization_factor;
break;
case OPTYPE_WR_ONLY:
optimization_value = win_rate;
break;
case OPTYPE_RF_TR:
optimization_value = recovery_factor * trades_optimization_factor;
break;
case OPTYPE_RF_WR:
optimization_value = recovery_factor * win_rate;
break;
case OPTYPE_TR_WR:
optimization_value = trades_optimization_factor * win_rate;
break;
case OPTYPE_RF_TR_WR:
optimization_value = recovery_factor * trades_optimization_factor * win_rate;
break;
case OPTYPE_NONE:
optimization_value = 0.0;
break;
default:
optimization_value = 0.0;
break;
}
return(optimization_value);
}
//+------------------------------------------------------------------+
#endif // _DEVELOPMENT
//+------------------------------------------------------------------+
//| Check if the market is open |
//+------------------------------------------------------------------+
bool IsMarketOpen(string symbol = NULL, int mode = SYMBOL_TRADE_MODE_FULL)
{
datetime currentTime = TimeCurrent();
// Get the trading hours for the specified symbol
int tradingHours = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE);
// Determine market conditions based on trading hours
if (tradingHours == SYMBOL_TRADE_MODE_DISABLED)
{
// Not tradable
return false;
}
else if (tradingHours == SYMBOL_TRADE_MODE_CLOSEONLY)
{
if (mode == SYMBOL_TRADE_MODE_CLOSEONLY)
{
// Only closing orders are possible
return true;
}
else
{
// Not tradable
return false;
}
}
else if (tradingHours == SYMBOL_TRADE_MODE_FULL)
{
// Normal trading available
return true;
}
// In cases other than the above, the trade will be deemed unacceptable
return false;
}
//+------------------------------------------------------------------+
//| Convert price from currency units to pips |
//+------------------------------------------------------------------+
double PriceToPips(
double price, // Price to convert to pips
int round_digits = 1 // Number of decimal places to round to
)
{
// Declare the pips value of the conversion result
double pips = 0.0;
// Calculate the conversion multiple based on the broker's decimal point holding unit
int digits = Digits();
// For 3-digit and 5-digit brokers
if(digits == 3 || digits == 5){
pips = price * MathPow(10, digits) / 10;
}
// For 2-digit and 4-digit brokers
if(digits == 2 || digits == 4){
pips = price * MathPow(10, digits);
}
// Rounds a number to a specified number of decimal places
pips = NormalizeDouble(pips, round_digits);
return(pips);
}
//+------------------------------------------------------------------+
//| Convert price from pips to currency units |
//+------------------------------------------------------------------+
double PipsToPrice(
double pips // Pips value converted to currency unit
)
{
// Declare the resulting price value
double price = 0;
// Declare the resulting price value and calculate the conversion multiple
// depending on the broker's decimal point holding unit
int digits = Digits();
// For 3-digit and 5-digit brokers
if(digits == 3 || digits == 5)
{
price = pips / MathPow(10, digits) * 10;
}
// For 2-digit and 4-digit brokers
if(digits == 2 || digits == 4)
{
price = pips / MathPow(10, digits);
}
// Round the price to the correct number of significant digits
price = NormalizeDouble(price, digits);
return(price);
}
//+------------------------------------------------------------------+
//| Convert points to pips |
//+------------------------------------------------------------------+
double PointsToPips(double points)
{
// Calculate pip value and digits dynamically
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
int pip_digits;
double pip_value;
if(_Digits == 3 || _Digits == 5)
{
pip_digits = 1;
pip_value = tick_size * 10;
}
else
{
pip_digits = 0;
pip_value = tick_size;
}
return NormalizeDouble(points / pip_value, pip_digits);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Get the angle of the hypotenuse with the two specified prices |
//| as the height and the time as the base |
//+------------------------------------------------------------------+
double GetAngle(
double price_from, // Price on the right
datetime time_from, // Time on the right
double price_to, // Price on the left
datetime time_to) // Time on the left
{
// Declaring an angle
double angle = 0.0;
// Calculate the minimum time-price difference
double time_diff = 60; // 60 seconds
double price_diff = PipsToPrice(0.1); // Price difference equivalent to 0.1 pips
// If the difference between the time value and the price is 0,
// an error occurs during division, so if the difference is 0, an angle of 0 is returned
if (time_diff != 0 && price_diff != 0)
{
// Define the correction coefficient that is the base when the maximum/minimum time value/price value
// of the chart is set as the base/height, respectively
const double bottom_correction = 1.0 / time_diff;
const double height_correction = 1.0 / price_diff;
// Calculate the bottom and height values to find the angle
double bottom =(double)(time_from - time_to) * bottom_correction;
double height = (price_from - price_to) * height_correction;
// If bottom is 0, an error occurs during division, so if bottom is 0, the angle 0 is returned
if (bottom != 0.0)
{
// Calculate the angle (theta) of the hypotenuse using the inverse trigonometric function arctangent
// Note that the result is in radians
double theta = MathArctan(height / bottom);
// Convert the calculated angle of the hypotenuse from radians (radian measure) to degrees (degree measure)
angle = theta * 180.0 / M_PI;
}
}
return(angle);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Spread monitoring and control system |
//+------------------------------------------------------------------+
void MonitorSpreadConditions()
{
// Get raw spread and normalize based on digits
CurrentSpread = MarketInfo(Symbol(), MODE_SPREAD);
if(Digits == 3 || Digits == 5)
{
CurrentSpread *= 0.1;
}
// Update spread statistics
UpdateSpreadStatistics();
// Adjust spread limits based on market conditions
AdjustDynamicSpreadLimits();
}
//+------------------------------------------------------------------+
//| Update spread statistics |
//+------------------------------------------------------------------+
void UpdateSpreadStatistics()
{
// Reset statistics on new month
int currentMonthBar = iBars(NULL, PERIOD_MN1);
if(LastMonthBar != currentMonthBar)
{
ResetSpreadStatistics();
LastMonthBar = currentMonthBar;
return;
}
// Update running statistics
SpreadSamples++;
SpreadTotal += CurrentSpread;
SpreadMin = MathMin(SpreadMin, CurrentSpread);
SpreadMax = MathMax(SpreadMax, CurrentSpread);
// Calculate moving average with weight on recent values
if(SpreadSamples > 0)
{
double weight = WeightFactor / (SpreadSamples + 1);
SpreadAvg = (CurrentSpread * weight) + (SpreadAvg * (1 - weight));
}
}
//+------------------------------------------------------------------+
//| Reset spread statistics |
//+------------------------------------------------------------------+
void ResetSpreadStatistics()
{
SpreadTotal = 0;
SpreadSamples = 0;
SpreadMin = DBL_MAX;
SpreadMax = 0;
SpreadAvg = 0;
}
//+------------------------------------------------------------------+
//| Adjust dynamic spread limits |
//+------------------------------------------------------------------+
void AdjustDynamicSpreadLimits()
{
if(AllowSpread == 0) // Auto mode
{
// Calculate volatility factor based on spread range
double volatilityFactor = 1.0;
if(SpreadMax > 0 && SpreadMin < DBL_MAX)
{
volatilityFactor = (SpreadMax - SpreadMin) / SpreadAvg;
volatilityFactor = MathMin(volatilityFactor * VolatilityAdjustment, 2.0);
}
// Apply dynamic adjustment
DynamicSpreadLimit = MathMin(
SpreadAvg * (1 + volatilityFactor),
MaxAllowableSpread
);
}
}
//+------------------------------------------------------------------+
//| Check if the spread is acceptable |
//+------------------------------------------------------------------+
bool IsSpreadAcceptable()
{
// Update spread monitoring
MonitorSpreadConditions();
// Fixed spread mode
if(AllowSpread > 0)
{
return CurrentSpread <= AllowSpread;
}
// Dynamic spread mode - wait for minimum samples
if(SpreadSamples < MinimumSpreadSamples)
{
return CurrentSpread <= BaseSpreadLimit;
}
// Consider market volatility and recent spread patterns
double adjustedLimit = MaxSpread;
// Check for extreme spread conditions
if(CurrentSpread > SpreadAvg * ExtremeSpikeMultiplier)
{
return false; // Reject extreme spread spikes
}
return CurrentSpread <= adjustedLimit;
}
//+------------------------------------------------------------------+
//| Entry rule 1 |
//+------------------------------------------------------------------+
int Entry_Rule1()
{
// Entry rule 1 is here
int shift = 0;
// Recalculate point value
double point = Point;
if(Digits == 3 || Digits == 5) {
point *= 10;
}
// Spread check
if(!IsSpreadAcceptable())
{
#ifdef _DEVELOPMENT
Print("Spread condition not met: Current=", CurrentSpread,
" Limit=", AllowSpread > 0 ? AllowSpread : MaxSpread,
" Avg=", SpreadAvg);
#endif
return(0);
}
// Price movement validation
double currentClose = iClose(NULL, 0, 0);
double previousClose = iClose(NULL, 0, 1);
if(OrderType() == OP_BUY && currentClose - previousClose >= Gap_Pips * point) return(0);
if(OrderType() == OP_SELL && previousClose - currentClose >= Gap_Pips * point) return(0);
// Seasonal trading restrictions
int currentMonth = Month();
int currentDay = Day();
if((currentMonth == 12 && currentDay >= 20) ||
(currentMonth == 1 && currentDay <= 3)) {
return(0);
}
// Time-based restrictions
if(TimeHour(TimeCurrent()) == 0) return(0);
// Sell when the candlestick crosses the upper Bollinger band (do not buy)
double upper_bb1 = iBands(NULL, 0, BB_Period, BB_Deviation, 0, PRICE_CLOSE, MODE_UPPER, shift);
double upper_bb2 = iBands(NULL, 0, BB_Period, BB_Deviation, 0, PRICE_CLOSE, MODE_UPPER, shift+1);
// Sell when the candlestick crosses the upper Bollinger band
if (iClose(NULL, 0, shift) > upper_bb1 && iClose(NULL, 0, shift+1) < upper_bb2)
{
// Calculate the angle of the upper Bollinger band
double upper_angle = GetAngle(upper_bb1, iTime(NULL, 0, shift), upper_bb2, iTime(NULL, 0, shift+1));
// "Entry" when the angle of the upper Bollinger band is less than the specified angle
if (MathAbs(upper_angle) < BB_Angle)
{
return(2);
} // Sell
else
{
return(0);
// No entry
}
}
else
{
return(0); // No entry
}
// Entry rule 1 ends here
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Entry filter 1 |
//+------------------------------------------------------------------+
int Entry_Filter1()
{
// Entry filter 1 is here
// Get short swap points
double swap = MarketInfo(NULL, MODE_SWAPSHORT);
// When the price rate has 3 or 5 significant digits
if(Digits == 3 || Digits == 5)
swap /= 10.0;
// Check if swap is below minimum allowed swap
if(swap < MinAllowedSwap)
return(0); // Trade not allowed
else
return(3); // Trade allowed
// Entry filter 1 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Entry filter 2 |
//+------------------------------------------------------------------+
int Entry_Filter2()
{
// Entry filter 2 is here
// Check if the spread is acceptable
if(!IsSpreadAcceptable())
return(0); // Trade not allowed
else
return(3); // Trade allowed
// Entry filter 2 ends here
return(3);
}//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Entry filter 3 |
//+------------------------------------------------------------------+
int Entry_Filter3()
{
// Entry filter 3 is here
// Entry filter 3 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Entry filter 4 |
//+------------------------------------------------------------------+
int Entry_Filter4()
{
// Entry filter 4 is here
// Entry filter 4 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Entry filter 5 |
//+------------------------------------------------------------------+
int Entry_Filter5()
{
// Entry filter 5 is here
// Entry filter 5 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Exit rule 1 |
//+------------------------------------------------------------------+
int Exit_Rule1()
{
// Exit rule 1 is here
int shift = 0;
// If the candlestick falls below the moving average, "close buy" will be made ("close sell" will not be made).
double MA1 = iMA(NULL, 0, MA_Break_Period, 0, MODE_SMA, PRICE_CLOSE, shift);
double MA2 = iMA(NULL, 0, MA_Break_Period, 0, MODE_SMA, PRICE_CLOSE, shift+1);
// If the candlestick falls below the moving average, "close buy" will be made
if (MA1 > iClose(NULL, 0, shift) && MA2 < iClose(NULL, 0, shift+1))
{
return(1);
} // Close buy
// Exit rule 1 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Exit rule 2 |
//+------------------------------------------------------------------+
int Exit_Rule2()
{
// Exit rule 2 is here
// If there is an unrealized loss, force close
double profit = 0.0; // Unrealized profit and loss
// In case of a sell position
if(OrderType() == OP_SELL)
{
profit = OrderOpenPrice() - Ask; // Calculate the unrealized profit or loss for a short position
if(PriceToPips(profit) < -SL_Acceptable_Pips)
{
// If the unrealized loss exceeds the allowable limit, close buy will be forced
return(1);
// Close buy
}
}
// Exit rule 2 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Exit Rule 3 |
//+------------------------------------------------------------------+
int Exit_Rule3()
{
// Exit rule 3 is here
// Exit rule 3 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Exit Rule 4 |
//+------------------------------------------------------------------+
int Exit_Rule4()
{
// Exit rule 4 is here
// Exit rule 4 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Exit Rule 5 |
//+------------------------------------------------------------------+
int Exit_Rule5()
{
// Exit rule 5 is here
// Exit rule 5 ends here
return(3);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Trading functions |
//+------------------------------------------------------------------+
// Wrapper function for OrderSend
//
// Places an order after modifying SL and TP to fit market constraints
// Implements retry and error handling in case of OrderSend error
int WrapperOrderSend(string symbol, // Symbol
int cmd, // Order type
double volume, // Lots
double price, // Order price
int slippage, // Slippage
double stoploss, // StopLoss
double takeprofit, // TakeProfit
string comment=NULL, // Comment
int magic=0, // Magic number
datetime expiration=0, // Expiry date
color arrow_color=clrNONE // Arrow color
)
{
int i;
int err = 0; // Error code
int res = 0; // OrderSend return value
// Wait until the market opens
datetime currentTime = TimeCurrent();
datetime waitEndTime = currentTime + TIME_WAIT_FOR_MARKET_OPEN;
while (TimeCurrent() < waitEndTime)
{
// If the market is open, exit the loop
if (IsMarketOpen())
{
break;
}
// Wait 500msec before next retry
Sleep(500);
}
// Retry MAX_ORDER_REPEAT times
for ( i=0; i<MAX_ORDER_REPEAT; i++ )
{
res = OrderSend(symbol,
cmd,
volume,
NormalizeDouble(price, Digits()),
slippage,
NormalizeDouble(stoploss, Digits()),
NormalizeDouble(takeprofit, Digits()),
comment,
magic,
expiration,
arrow_color);
// If the order is successfully placed, the ticket number is returned and the function ends
if ( res != -1 ) return(res);
// If an order fails to be placed, an error message will be displayed and the order will be retried
if ( res == -1 )
{
err = GetLastError();
Print("OrderSend return error: code=",err, ", Error=", ErrorDescription(err));
// Wait 500msec before next retry
Sleep(500);
}
}
return(res);
}
// Wrapper function for OrderModify
//
// Places an order after modifying SL and TP to match market constraints
// Executes retry and error handling in case of an OrderModify error
bool WrapperOrderModify(int ticket, // Ticket number
double price, // Order price
double stoploss, // StopLoss
double takeprofit, // TakeProfit
datetime expiration, // Expiration date
color arrow_color // Arrow color
)
{
int i;
int err = 0; // Error code
bool res = false; // OrderModify return value
// Wait until the market opens
datetime currentTime = TimeCurrent();
datetime waitEndTime = currentTime + TIME_WAIT_FOR_MARKET_OPEN;
while (TimeCurrent() < waitEndTime)
{
// If the market is open, exit the loop
if (IsMarketOpen())
{
break;
}
// Wait 500msec before next retry
Sleep(500);
}
// Retry MAX_ORDER_REPEAT times
for ( i=0; i<MAX_ORDER_REPEAT; i++ )
{
res = OrderModify(ticket,
NormalizeDouble(price, Digits()),
NormalizeDouble(stoploss, Digits()),
NormalizeDouble(takeprofit, Digits()),
expiration,
arrow_color);
// If the order is successfully modified, return the exit flag
if (res) return(res);
// If the order modify fails, display an error and then retry
if ( !res )
{
err = GetLastError();
Print("OrderModify return error: code=",err, ", Error=", ErrorDescription(err));
// Wait 500msec before next retry
Sleep(500);
}
}
return(res);
}
// Wrapper function for OrderClose
//
// Places an order after modifying SL and TP to fit market constraints
// Executes retry and error handling in case of an OrderClose error
bool WrapperOrderClose(int ticket, // Ticket number
double lots, // Lot size
double price, // Order price
int slippage, // Slippage
color arrow_color // Arrow color
)
{
int i;
int err = 0; // Error code
bool res = false; // OrderClose return value
// Wait until the market opens
datetime currentTime = TimeCurrent();
datetime waitEndTime = currentTime + TIME_WAIT_FOR_MARKET_OPEN;
while (TimeCurrent() < waitEndTime)
{
// If the market is open, exit the loop
if (IsMarketOpen(NULL, SYMBOL_TRADE_MODE_CLOSEONLY))
{
break;
}
// Wait 500msec before next retry
Sleep(500);
}
// Retry MAX_ORDER_REPEAT times
for ( i=0; i<MAX_ORDER_REPEAT; i++ )
{
res = OrderClose(ticket,
lots,
NormalizeDouble(price, Digits()),
slippage,
arrow_color);
// If the order is successfully closed, return the exit flag
if ( res ) return(res);
// If the order close fails, an error will be displayed and the order will be retried
if ( !res )
{
err = GetLastError();
Print("OrderClose return error: code=",err, ", Error=", ErrorDescription(err));
// Wait 500msec before next retry
Sleep(500);
}
}
return(res);
}