//+------------------------------------------------------------------+
//|                                   Pip Digger's Night Scalper.mq4 |
//|                                      Copyright © 2013, PipDigger |
//|                                      http://worldwide-invest.org |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2013, PipDigger"
#property link      "http://worldwide-invest.org/"

#include <WinUser32.mqh>

#import "kernel32.dll"
int GetTimeZoneInformation(int& TIME_ZONE_INFORMATION[]);
#import

// EA name and version
string EA_Name									= "Pip Digger\'s Night Scalper";
string EA_version								= "0.6.1";

// externals
extern string EA_Configuration         = "==== EA Configuration ====";
extern int    Magic                    = -1;
extern string BotComment               = "pdns0.6.1";
extern bool   AutoConfig					= true;
extern string TradeSettings            = "==== Trade settings ====";
extern int	  MaxTrades						= 2;
extern double MaxSpread                = 4.0;
extern int    Slippage                 = 1;
extern string MoneyManagement          = "==== Money Management ====";
extern double LotSize                  = 0.1;
extern double Risk                     = 4.0;
extern bool   RiskBooster					= true;
extern double BoosterBase					= 0.1;
extern double BoosterPower					= 8;
extern string TimeSettings             = "==== Time settings ====";
extern bool   AutoGMT						= true;
extern bool   AdjustDST						= true;
extern int    GMT_Offset               = 0;
extern string GMT_StartHrs             = "21:30";
extern string GMT_StopHrs              = "23:16";
//extern int	  iGMT_ForceCloseHrs			= 0215;
extern string GMT_ForceCloseHrs			= "08:30";
extern bool   TradeMondayMorning			= true;
extern bool   TradeFridayNight			= false;
extern string OpenSettings             = "==== Open / close settings ====";
extern int    WPR_Period					= 12;
extern double WPR_Open_Level				= 92;
 bool   WPR_DblCycle					= false;
//extern int    WPR_Close_Period			= 10;
extern double WPR_Close_Level				= 16;
extern int    SL                       = 35;
extern double TP                       = 5.5;
extern double PartTP							= 2.5;
extern double PartCloseFraction			= 0.25;
extern bool   OpenBetterPriceOnly		= true;
extern double OpenExtraPips				= 0.5;

// globals
double MinLot, MaxLot, LotDigits, StpLvl;
datetime bt = 0;

int init()
{
   LotDigits = MathLog(MarketInfo(Symbol(), MODE_LOTSTEP)) / MathLog(0.1);
   MinLot = MarketInfo(Symbol(), MODE_MINLOT);
   MaxLot = MarketInfo(Symbol(), MODE_MAXLOT);
   StpLvl = MarketInfo(Symbol(), MODE_STOPLEVEL);
   
   // Auto magic number generator
   if(Magic <= 0)
   	Magic = AutoMagic(-Magic);
   
   if(AutoConfig) {
   	if(!AutoSetParameters()) {
   	   Comment("Unsupported instrument! Aborting...");
   		MessageBox("Unsupported instrument! Aborting...", "Unsupported instrument", MB_OK|MB_ICONERROR);
   		while(true)
   			Sleep(10000);
   	}
   	if(Period() != PERIOD_M15) {
   	   Comment("Unsupported time frame! Put on M15 chart. Aborting...");
   		MessageBox("Unsupported time frame! Put on M15 chart. Aborting...", "Unsupported time frame", MB_OK|MB_ICONERROR);
   		while(true)
   			Sleep(10000);
   	}
   }
   
   // comment out the following to perform force close hrs optimization
   /*GMT_ForceCloseHrs = DoubleToStr(iGMT_ForceCloseHrs, 0);
   if(StringLen(GMT_ForceCloseHrs) == 3)
   	GMT_ForceCloseHrs = StringConcatenate("0", GMT_ForceCloseHrs);
   GMT_ForceCloseHrs = StringConcatenate(StringSubstr(GMT_ForceCloseHrs, 0, 2), ":", StringSubstr(GMT_ForceCloseHrs, 2, 2));*/
   
   MaxSpread *= Point;   
   
	if(Digits%2==1) {
		MaxSpread *= 10.0;
		Slippage	*= 10;
		SL *= 10;
		TP *= 10.0;
		PartTP *= 10.0;
		OpenExtraPips *= 10.0;
	}
	
	if(MaxTrades < 0)
		MaxTrades = 0;
   
   if(!RiskBooster) {
   	BoosterBase = 1.0;
   	BoosterPower = 0.0;
   }
   
   if(AutoGMT)
   	if(IsTesting()||IsOptimization()) {
   		AutoGMT = false;
   		Print("WARNING: AutoGMT does not work in backtester! Using manual GMT_Offset = ", GMT_Offset);
   	} else if(!IsDllsAllowed()) {
   		Comment("Please enable DLLs for AutoGMT to work! Aborting...");
   		MessageBox("Please enable DLLs for AutoGMT to work! Aborting...", "Please enable DLLs", MB_OK|MB_ICONERROR);
   		while(true)
   			Sleep(10000);
   	} else
   		GMT_Offset = GMT_OffsetServer();
	
	if(StringLen(BotComment) == 0)
		BotComment = EA_Name;
   
   return(0);
}

int deinit()
{
	return(0);
}

int start()
{
	static bool OpenBuy = false, OpenSell = false, CloseBuy = false, CloseSell = false;
	static double PrevAsk, PrevBid, BuyOpenRefPrc, SellOpenRefPrc, WPR;
	static double LastBuyPrc, LastSellPrc;
	double x;
	int i, nbuy, nsell;
	//int i, nbuy = -1, nsell = -1;
	static int ticket = -1;
	
	// run every new bar openening
	if(bt != Time[0]) {
		// check if DST status has changed
		if(AutoGMT && TimeDayOfWeek(bt) == 5 && DayOfWeek() != 5)
			GMT_Offset = GMT_OffsetServer();
		
		bt = Time[0];
		
		OpenBuy = false;
		OpenSell = false;
		CloseBuy = false;
		CloseSell = false;
		
		WPR = iWPR(NULL, 0, WPR_Period, 1);
		
		// check for close conditions
		if(WPR > -WPR_Close_Level)
			CloseBuy = true;
		else if(WPR < -100 + WPR_Close_Level)
			CloseSell = true;
		if(IsForceCloseTime()) {
			CloseBuy = true;
			CloseSell = true;
		}
		
		// check for open conditions
		if(MaxTrades > 0) { // get number of open trades
			nbuy = 0;
			nsell = 0;
			for(i = OrdersTotal() - 1; i >= 0; i--)
				if(OrderSelect(i, SELECT_BY_POS))
					if(OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
						switch(OrderType()) {
							case OP_BUY:
								nbuy++;
								break;
							case OP_SELL:
								nsell++;
								break;
						}
		} // end get number of open trades
		if(WPR_DblCycle)
			WPR = WPR_DblCycle();
		if(WPR < -WPR_Open_Level && nbuy < MaxTrades && !CloseBuy) {
			OpenBuy = true;
			//BuyOpenRefPrc = Ask;
			BuyOpenRefPrc = MathMin(PrevAsk, Ask);
		} else if(WPR > -100 + WPR_Open_Level && nsell < MaxTrades && !CloseSell) {
			OpenSell = true;
			//SellOpenRefPrc = Bid;
			SellOpenRefPrc = MathMax(PrevBid, Bid);
		}
	} // end run every new bar openening
	
	// save Ask and Bid ticks for next run of start()
	PrevAsk = Ask;
	PrevBid = Bid;
	
	// close open positions
	if(CloseBuy || CloseSell) {
		nbuy = 0;
		nsell = 0;
		for(i = OrdersTotal() - 1; i >= 0; i--)
			if(OrderSelect(i, SELECT_BY_POS))
				if(OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
					if(OrderType() == OP_BUY && CloseBuy) {
						nbuy++;
						if(OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, Blue))
							nbuy--;
					} else if(OrderType() == OP_SELL && CloseSell) {
						nsell++;
						if(OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, Red))
							nsell--;
					}
		if(nbuy == 0)
			CloseBuy = false;
		if(nsell == 0)
			CloseSell = false;
	}
	
	// open new positions
	if(IsTradeTime() && Ask - Bid <= MaxSpread) {
		if(OpenBuy) {
			nbuy = 0;
			LastBuyPrc = 10.0 * Ask;
			if(OpenBetterPriceOnly || RiskBooster)
				for(i = OrdersTotal() - 1; i >= 0; i--)
					if(OrderSelect(i, SELECT_BY_POS))
						if(OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY) {
							nbuy++;
							if(OpenBetterPriceOnly)
								LastBuyPrc = MathMin(LastBuyPrc, OrderOpenPrice());
						}
			if(Ask < MathMin(LastBuyPrc, BuyOpenRefPrc) - OpenExtraPips * Point) {
				ticket = OrderSend(Symbol(), OP_BUY, GetLot(MathPow(BoosterBase, nbuy) * MathPow(-WPR / WPR_Open_Level, BoosterPower)), Ask, Slippage, 0.0, 0.0, BotComment, Magic, 0, Blue);
				if(ticket != -1) {
					OrderSelect(ticket, SELECT_BY_TICKET);
					LastBuyPrc = OrderOpenPrice();
					OpenBuy = false;
				}
			}
		}
		if(OpenSell) {
			nsell = 0;
			LastSellPrc = 0.0;
			if(OpenBetterPriceOnly || RiskBooster)
				for(i = OrdersTotal() - 1; i >= 0; i--)
					if(OrderSelect(i, SELECT_BY_POS))
						if(OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_SELL) {
							nsell++;
							if(OpenBetterPriceOnly)
								LastSellPrc = MathMax(LastSellPrc, OrderOpenPrice());
						}
			if(Bid > MathMax(LastSellPrc, SellOpenRefPrc) + OpenExtraPips * Point) {
				ticket = OrderSend(Symbol(), OP_SELL, GetLot(MathPow(BoosterBase, nsell) * MathPow((100 + WPR) / WPR_Open_Level, BoosterPower)), Bid, Slippage, 0.0, 0.0, BotComment, Magic, 0, Red);
				if(ticket != -1) {
					OrderSelect(ticket, SELECT_BY_TICKET);
					LastSellPrc = OrderOpenPrice();
					OpenSell = false;
				}
			}
		}
	}
	
   // set hard TP & SL for last opened trade
	if(OrderSelect(ticket, SELECT_BY_TICKET))
		if(OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
			switch(OrderType()) {
				case OP_BUY:
					if(OrderModify(ticket, OrderOpenPrice(), Bid - SL * Point, Ask + TP * Point, 0, Blue))
						ticket = -1;
					break;
				case OP_SELL:
					if(OrderModify(ticket, OrderOpenPrice(), Ask + SL * Point, Bid - TP * Point, 0, Red))
						ticket = -1;
					break;
			}
	
	// check for partial close
	if(PartTP > 0.0)
		for(i = OrdersTotal() - 1; i >= 0; i--)
			if(OrderSelect(i, SELECT_BY_POS))
				if(OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
					switch(OrderType()) {
						case OP_BUY:
							if(Bid - OrderOpenPrice() >= PartTP * Point) // PA beyond partial Close
								if(StringFind(OrderComment(), BotComment) != -1) // Order has original comment (not partially closed yet!)
									OrderClose(OrderTicket(), NormalizeDouble(MathMin(MathMax(MinLot, OrderLots() * PartCloseFraction), MaxLot), LotDigits), Bid, Slippage, Blue);
							break;
						case OP_SELL:
							if(OrderOpenPrice() - Ask >= PartTP * Point) // PA beyond partial Close
								if(StringFind(OrderComment(), BotComment) != -1) // Order has original comment (not partially closed yet!)
									OrderClose(OrderTicket(), NormalizeDouble(MathMin(MathMax(MinLot, OrderLots() * PartCloseFraction), MaxLot), LotDigits), Ask, Slippage, Red);
							break;
					}
	
	// update HUD
	if(!(IsTesting()||IsOptimization()) || IsVisualMode())
		HUD();
	
	// update PrevAsk and PrevBid if tickvalues have changed
	if(RefreshRates()) {
		PrevAsk = MathMin(PrevAsk, Ask);
		PrevBid = MathMax(PrevBid, Bid);
	}
	
	return(0);
}

// return lotsize for next trade
double GetLot(double x = 1.0)
{
	if(Risk > 0.0)
		x *= AccountFreeMargin() * (Risk/100.0) / (SL * MarketInfo(Symbol(), MODE_TICKVALUE));
	else
		x = LotSize;
	
	return(NormalizeDouble(MathMin(MathMax(MinLot, x), MaxLot), LotDigits));
}

// return true if server time is between StartHrs and StopHrs (with GMT adjustment)
bool IsTradeTime()
{
	datetime GMT_Server, GMT_Start, GMT_Stop;
	int DST_Adjust = 0;
	
	if(AdjustDST && IsEDT())
		DST_Adjust = 3600;
	
	GMT_Server = TimeCurrent() - GMT_Offset * 3600;
	GMT_Start = StrToTime(GMT_StartHrs); // usually 22:00 = NY close (no DST)
	
	if(GMT_Server >= GMT_Start - DST_Adjust)
		if((!TradeFridayNight && TimeDayOfWeek(GMT_Server) == 5) || (!TradeMondayMorning && TimeDayOfWeek(GMT_Server) <= 1))
			return(false);
	
	GMT_Stop = StrToTime(GMT_StopHrs); // usually 23:XX = 1h XXm after NY close (no DST)
	if(GMT_Stop < GMT_Start)
		GMT_Stop += 86400;
	
	if(GMT_Server >= GMT_Start - DST_Adjust && GMT_Server <= GMT_Stop - DST_Adjust)
		return(true);
	else
		return(false);
}

// return true if server time is between ForceCloseHrs and StartHrs (with GMT adjustment)
bool IsForceCloseTime()
{
	datetime GMT_Server, GMT_ForceClose, GMT_Start;
	int DST_Adjust = 0;
	
	if(AdjustDST && IsEDT())
		DST_Adjust = 3600;
	
	GMT_Server = TimeCurrent() - GMT_Offset * 3600;
	GMT_ForceClose = StrToTime(GMT_ForceCloseHrs);
	GMT_Start = StrToTime(GMT_StartHrs);
	
	if(GMT_Start < GMT_ForceClose)
		GMT_Start += 86400;
	
	if(GMT_Server >= GMT_ForceClose - DST_Adjust && GMT_Server < GMT_Start - DST_Adjust)
		return(true);
	else
		return(false);
}

// return average of n previous WPR values
double WPR_DblCycle()
{
	double WPR_2, WPR_1;
	
	WPR_2 = iWPR(NULL, 0, WPR_Period, 2);
	WPR_1 = iWPR(NULL, 0, WPR_Period, 1);
	
	if(WPR_2 < -50.0 && WPR_1 <= -WPR_Open_Level)
		WPR_1 = -MathSqrt(WPR_1 * WPR_2);
	else if(WPR_2 > -50.0 && WPR_1 >= -100 + WPR_Open_Level)
		WPR_1 = -MathSqrt(WPR_1 * WPR_2);
	else
		WPR_1 = -50.0;

	return(WPR_1);
}

// automatic number generator
int AutoMagic(int salt = 0)
{
   int m;
   string s;
   
	s = StringConcatenate(EA_Name, Symbol(), Period(), DoubleToStr(AccountNumber(), 0), AccountCompany(), DoubleToStr(MathMod(100.0 + MathAbs(salt), 100.0), 0));
      
   m = djb2(s);
   if(m < 0) // invert m if negative (original djb2 is for unsigned int!)
   	m = -m;
  	m = MathMod(m, 1000000) + (m - MathMod(m, 1000000)) / 1000000;
  	if(m > 1000000)
  		m = MathMod(m, 1000000);
  	
  	return(m);
}

// djb2 hash
int djb2(string key){
	int i, h = 5381, c;
	for (i = 0; i < StringLen(key); i++){
		c = StringGetChar(key, i);
		h = (h << 5) + h + c;
	}
	return(h);
}

// get local GMT offset from system settings
double GMT_OffsetLocal()
{
	int a[43];
	
	switch (GetTimeZoneInformation(a))
	{
		case 0: // no daylight saving in local time zone, or unkown
			return (a[0] / (-60.0));
		case 1: // local system is operating in standard time (no daylight saving time)
			return (a[0] / (-60.0));
		case 2: // local system is operating in daylight saving time
			return ((a[0] + a[42]) / (-60.0));
	}
	
	return (0.0);
}

// get server GMT offset
double GMT_OffsetServer()
{
	int n;
	
	n = (TimeCurrent() - TimeLocal()) / 60; // server time - local time, in minutes
	n = 30 * MathRound(n / 30.0); // round to nearest 30 minutes
	
	return (GMT_OffsetLocal() + n / 60.0);
}

// return true if current server time is in DST days for EST/EDT
bool IsEDT()
{
	datetime servertime;
	datetime EDTstart[9], EDTend[9];
	int i;
	
	EDTstart[0]	= D'2007.03.11';
	EDTend[0]	= D'2007.11.04';
	EDTstart[1]	= D'2008.03.09';
	EDTend[1]	= D'2008.11.02';
	EDTstart[2]	= D'2009.03.08';
	EDTend[2]	= D'2009.11.01';
	EDTstart[3]	= D'2010.03.14';
	EDTend[3]	= D'2010.11.07';
	EDTstart[4]	= D'2011.03.13';
	EDTend[4]	= D'2011.11.06';
	EDTstart[5]	= D'2012.03.11';
	EDTend[5]	= D'2012.11.04';
	EDTstart[6]	= D'2013.03.10';
	EDTend[6]	= D'2013.11.03';
	EDTstart[7]	= D'2014.03.09';
	EDTend[7]	= D'2014.11.02';
	EDTstart[8]	= D'2015.03.08';
	EDTend[8]	= D'2015.11.01';
	
	servertime = TimeCurrent();
	
	for(i = 0; i < 9; i++)
		if(servertime > EDTstart[i] && servertime < EDTend[i])
			return(true);
	
	return(false);
}

// HUD
void HUD()
{
	string cmt = "\n", autoparams = "";
	
	if(AutoConfig)
		autoparams = " (auto configured)";
	
	cmt = StringConcatenate(cmt, EA_Name, " v", EA_version, "\n");
	cmt = StringConcatenate(cmt, "=====\n");
	cmt = StringConcatenate(cmt, "Instrument : ");
	if(StringFind(Symbol(),"USDCAD") != -1)
		cmt = StringConcatenate(cmt, "Loonie", autoparams,"\n");
	else if(StringFind(Symbol(),"GBPUSD") != -1)
		cmt = StringConcatenate(cmt, "Cable", autoparams,"\n");
	else if(StringFind(Symbol(),"EURCAD") != -1)
		cmt = StringConcatenate(cmt, "EURCAD", autoparams,"\n");
	else
		cmt = StringConcatenate(cmt, Symbol(), " (unrecognized)\n");
	cmt = StringConcatenate(cmt, "=====\n");
	cmt = StringConcatenate(cmt, "Risk : ", Risk, "\n");
	cmt = StringConcatenate(cmt, "=====\n");
	cmt = StringConcatenate(cmt, "Magic : ", Magic, "\n");
	cmt = StringConcatenate(cmt, "=====\n");
	cmt = StringConcatenate(cmt, "Local Time : ", TimeToStr(TimeLocal(), TIME_MINUTES|TIME_SECONDS), "\n");
	cmt = StringConcatenate(cmt, "Server Time : ", TimeToStr(TimeCurrent(), TIME_MINUTES|TIME_SECONDS), "\n");
	cmt = StringConcatenate(cmt, "GMT Time : ", TimeToStr(TimeCurrent() - GMT_Offset * 3600, TIME_MINUTES|TIME_SECONDS), "\n");
	if(AutoGMT)
		cmt = StringConcatenate(cmt,"GMT Offset (auto) : ", GMT_Offset, "\n");
	else
		cmt = StringConcatenate(cmt,"GMT Offset (manual) : ", GMT_Offset, "\n");
	if(IsEDT()) {
		cmt = StringConcatenate(cmt,"NY market is observing EDT / DST\n");
		if(AdjustDST)
			cmt = StringConcatenate(cmt,"Trading hours adjusted for DST.\n");
		else
			cmt = StringConcatenate(cmt,"WARNING: Trading hours NOT adjusted for DST. ONLY FOR BACKTESTING!\n");
	} else
		cmt = StringConcatenate(cmt,"NY market is observing EST (no DST)\n");
	cmt = StringConcatenate(cmt, "=====\n");
	cmt = StringConcatenate(cmt,"Current / Max spread : ", DoubleToStr(Ask - Bid, Digits), " / ", DoubleToStr(MaxSpread, Digits) ,"\n");
	cmt = StringConcatenate(cmt, "=====\n");
	if(IsTradeTime())
		cmt = StringConcatenate(cmt,"Trade session OPEN.\n");
	else if(IsForceCloseTime())
		cmt = StringConcatenate(cmt,"Trade session CLOSED.\n");
	else
		cmt = StringConcatenate(cmt,"Trade session CLOSING.\n");

	Comment(cmt);
}

bool AutoSetParameters()
{
	if(StringFind(Symbol(), "USDCAD") != -1) { // Loonie
		MaxTrades				= 2;
		if(Risk < 0.0)
			Risk *= -4.0;
		else
			Risk					= 4.0;
		RiskBooster				= true;
		BoosterBase				= 0.1;
		BoosterPower			= 8.0;
		GMT_StartHrs			= "21:30";
		GMT_StopHrs				= "23:16";
		GMT_ForceCloseHrs		= "08:30";
		TradeMondayMorning	= true;
		TradeFridayNight		= false;
		WPR_Period				= 12;
		WPR_Open_Level			= 92.0;
		WPR_Close_Level		= 16.0;
		SL							= 35;
		TP							= 5.5;
		PartTP					= 2.5;
		PartCloseFraction		= 0.25;
		OpenBetterPriceOnly	= true;
		OpenExtraPips			= 0.5;
	} else if(StringFind(Symbol(), "GBPUSD") != -1) { // Cable
		MaxTrades				= 2;
		if(Risk < 0.0)
			Risk *= -3.0;
		else
		Risk						= 3.0;
		RiskBooster				= true;
		BoosterBase				= 0.2;
		BoosterPower			= 2.0;
		GMT_StartHrs			= "22:00";
		GMT_StopHrs				= "23:20";
		GMT_ForceCloseHrs		= "09:45";
		TradeMondayMorning	= true;
		TradeFridayNight		= false;
		WPR_Period				= 10;
		WPR_Open_Level			= 81.0;
		WPR_Close_Level		= 14.0;
		SL							= 50;
		TP							= 30.0;
		PartTP					= 7.0;
		PartCloseFraction		= 0.4;
		OpenBetterPriceOnly	= true;
		OpenExtraPips			= 1.0;
	} else if(StringFind(Symbol(), "EURCAD") != -1) { // EURCAD
		MaxTrades				= 2;
		if(Risk < 0.0)
			Risk *= -3.5;
		else
		Risk						= 3.5;
		RiskBooster				= true;
		BoosterBase				= 1.2;
		BoosterPower			= 6.0;
		GMT_StartHrs			= "22:00";
		GMT_StopHrs				= "23:20";
		GMT_ForceCloseHrs		= "08:30";
		TradeMondayMorning	= true;
		TradeFridayNight		= false;
		WPR_Period				= 7;
		WPR_Open_Level			= 88.0;
		WPR_Close_Level		= 28.0;
		SL							= 50;
		TP							= 7.5;
		PartTP					= 5.0;
		PartCloseFraction		= 0.5;
		OpenBetterPriceOnly	= true;
		OpenExtraPips			= 0.8;
	} else {
		Print("WARNING: Unsupported instrument, using manual defaults.");
		return(false);
	}
	
	return(true);
}

