FFCal

FFCal

官方版 无广告 76

更新日期:2022-11-05 分类标签: 语言:中文 平台:没限制

13 人已下载 手机查看

FFCal

//===================================================================================
// Last revision 21, September 2014 - Unicode strings on grabWeb and FileSeek
// FFCal.mq4
// Copyright © 2006, Derk Wehler
// (derkwehler@gmail.com)
// Written in cooperation with: http://www.forexfactory.com
//
// $Workfile:: FFCal.mq4 $
// $Revision:: 20 $
// $Author:: Derk $
// $Date:: 7/07/09 5:40a $
//
// This "indicator" calls DLLs to fetch a special XML file from the
// ForexFactory web site. It then parses it and writes it out as a .CSV
// file, which it places in the folder: experts/files so that IsNewsTime()
// can use that file to tell if it is near announcement time.
//
// It does this once when it starts up and once per 6 hours in case there
// have been any updates to the annoucement calendar. In order to lessen
// sudden traffic on the FF site, it refreshes every 6 hours on a random
// minute.
//
// SAMPLE CALLS TO THE INDICATOR:
//
// int minutesSincePrevEvent =
// iCustom(NULL, 0, "FFCal", true, true, false, true, true, 1, 0);
//
// int minutesUntilNextEvent =
// iCustom(NULL, 0, "FFCal", true, true, false, true, true, 1, 1);
//
// // Use this call to get ONLY impact of previous event
// int impactOfPrevEvent =
// iCustom(NULL, 0, "FFCal", true, true, false, true, true, 2, 0);
//
// // Use this call to get ONLY impact of nexy event
// int impactOfNextEvent =
// iCustom(NULL, 0, "FFCal", true, true, false, true, true, 2, 1);
//
//
// EXAMPLE CODE FOR USE IN AN EA:
// (NOTE I HAVE PUT IN CODE TO CALL THE INDICATOR ONLY ONCE PER MINUTE)
//
// // EA Setting variables
// extern int MinsBeforeNews = 60; // mins before an event to stay out of trading
// extern int MinsafterNews = 60; // mins after an event to stay out of trading
//
// // Global variable at top of file
// bool NewsTime;
//
// // Function to check if it is news time
// void NewsHandling()
// {
// static int PrevMinute = -1;
//
// if (Minute() != PrevMinute)
// {
// PrevMinute = Minute();
//
// int minutesSincePrevEvent =
// iCustom(NULL, 0, "FFCal", true, true, false, true, true, 1, 0);
//
// int minutesUntilNextEve nt =
// iCustom(NULL, 0, "FFCal", true, true, false, true, true, 1, 1);
//
// NewsTime = false;
// if ((minutesUntilNextEvent <= MinsBeforeNews) ||
// (minutesSincePrevEvent <= MinsAfterNews))
// {
// NewsTime = true;
// }
// }
// }//newshandling
//
//=============================================================================
//
// OTHER CREDIT DUE
// (For GrebWeb and LogUtils functionality (see end of this flie)
//
// 2/14/2007: Robert Hill added code for using text objects instead
// of Comment() for easier reading
//
// 2/25/2007: Paul Hampton-Smith for his TimeZone DLL code
// 3/31/2007: Code replaces by simpler method from BurgerKing
//
// 2/26/2007: Mike Nguyen added the following things:
// (search text for "Added by MN")
//
// - Connection test so that MT4 doesnt hang when
// there is no server connection
// - Fixed some minor syntax because was getting
// "too many files open error..."
// - Added vertical lines and vertical news text so that
// we have a visual reference of when the news happened.
// - Now supports and correctly draws two simultaneous news
// announcements
// - Added text to say "xx min SINCE event..." after the
// event as past
// - Clean up old Objects left behind on old news annoumcents
// - Now "Back Draws" Old news headlines onto the chart
// - Now can choose which corner of the chart to place the
// headlines
//
// 4/2/2007: Mike Nguyen added the following things:
// (search text for "Added by MN")
// - Added ability to disable Web/URL updates. This is so that the
// multiple instances of the indicator used by other charts or EAs
// dont fight with each other (Error code 4103)
// - Fixed deleting OBJ_TREND, only deletes its own Trend Lines
// objects instead of all OBJ_TREND objects
// - Fixed deleting OBJ_TEXT, only deletes its own Headlines
// instead of all text
// - Made file name global and indicator now deletes the xml file
// each time the indicator is put on or removed from chart. Fixed
// one case of divide by zero where multiple charts indicator is
// on was trying to overwrite the same file (forces a new
// download of the xml)
//
// 4/29/2007: Derk Wehler
// - Fixed problem where indicator returns zero for "Minutes Until
// Next Event" when there are no more events for the week for
// that currency pair. That caused EAs calling it to think that
// it was always new time. Instead, we now set it to a flag
// value, which EAs can test for.
//
// 5/16/2007: Derk Wehler
// - Added sample code to header
// - Changed variable name from DispOldNews to DispVertNews
//
// 6/02/2007: Derk Wehler (thanks to "Flourishing")
// - Changed how often it updates the file from the FF site web
// page. Now it uses a global variable, so that when you have
// it running on multiple charts, it should only update once
// every 4 hours for all of them.
//
// 6/05/2007: Derk Wehler
// - Fixed logHandle error by resetting to -1 when closed
// - Added "NeedToGetFile" variable for getting the XML if it
// does not already exist
// - Added extern "SaveXmlFiles", so that when FFCal de-inits,
// the user can choose whether or not to delete old XML files
//
//=============================================================================
//+---------------------------------------------------------------------------+
//| WinInet.mqh |
//| Paul Hampton-Smith |
//| paul1000@pobox.com |
//| |
//| This include has been adapted from the groundbreaking GrabWeb script |
//| developed by Adbi that showed me how to import functions from wininet.dll |
//|---------------------------------------------------------------------------|
//| grabweb.mq4 |
//| Copyright © 2006, Abhi |
//| http://www.megadelfi.com/experts/ |
//| E-mail: grabwebexpert{Q)megadelfi.com |
//| fix my e-mail address before mailing me 😉 |
//+---------------------------------------------------------------------------+
/*
IDEAS FOR ADDITIONS:

* A text lable of what the news was and when it was, e.g
"USD CPI m/m @ [broker time here]" at the top or bottom within the shaded area.

* Label the number of pips it went in its initial direction and how much it
retraced...of course you'd have to come up with a convention for measuring
movement. Or you could label what price was just prior to news then labels
the next two price exteremes. I think it would be nice to have the numbers
right under/over the candle that produced the extremes for whatever timeframe
you are in.

* Label the volume count for the candle that included the news in it...whatever
timeframe your in. So I could switch from 1 min. to 5 min. to 15 min.
timeframes and get different vol. counts for the bar that included the news.

*/

#property copyright "Copyright © 2006, Derk Wehler"
#property link "http://www.forexfactory.com"

#property indicator_chart_window
#property indicator_buffers 3

#define TITLE 0
#define COUNTRY 1
#define DATE 2
#define TIME 3
#define IMPACT 4
#define FORECAST 5
#define PREVIOUS 6

//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================

extern bool IncludeHigh = true;
extern bool IncludeMedium = true;
extern bool IncludeLow = false; //true;
extern bool IncludeSpeaks = true; // news items with "Speaks" in them have different characteristics
extern bool IsEA_Call = false;
extern int OffsetHours = 0; //-1;
extern bool AllowWebUpdates = true; // Set this to false when using in another EA or Chart, so that the multiple instances of the indicator dont fight with each other
extern int Alert1MinsBefore = 0; // Set to -1 for no Alert
extern int Alert2MinsBefore = -1; // Set to -1 for no Alert
extern bool ReportAllForUSD = true;
extern bool ReportAllForEUR = true;
extern bool ReportAllForGBP = true;
// added and tested by a1ra, seems working OK.
extern bool ReportAllForNZD = true;
extern bool ReportAllForJPY = true;
extern bool ReportAllForAUD = true;
extern bool ReportAllForCHF = true;
extern bool ReportAllForCAD = true;
extern bool ReportAllForCNY = false;

extern bool EnableLogging = false; // Perhaps remove this from externs once its working well
extern bool ShowNextTwoEvents = true;
extern bool ShowVertNews = false;
extern int TxtSize = 10;
extern color TxtColorTitle = Gold; //LightGray;
extern color TxtColorNews = DeepSkyBlue;
extern color TxtColorImpact = Red;
extern color TxtColorPrevious = Peru;
extern color TxtColorForecast = Lime;
extern int VertTxtShift = 21; // How far away below the ask line we want to place our vertical news text
extern int VertLeftLineShift = 900; // How far away to the left of the line we want to place our vertical news text
extern int VertRightLineShift = 200; // How far away to the left of the line we want to place our vertical news text
extern color VertLineColor = SlateBlue; // Color of our vertical news line
extern color VertTxtColor = Black; // Color of our vertical text color
extern int VertTxtSize = 8; // Color of our vertical text
extern int NewsCorner = 1; // Choose which corner to place headlines 0=Upper Left, 1=Upper Right, 2=lower left , 3=lower right
extern bool SaveXmlFiles = false; // If true, this will keep the daily XML files

int DebugLevel = 5;

double ExtMapBuffer0[]; // Contains (minutes until) each news event
double ExtMapBuffer1[]; // Contains only most recent and next news event ([0] & [1])
double ExtMapBuffer2[]; // Contains impact value for most recent and next news event

//string sUrl = "http://www.forexfactory.com/calendar.php";

string sUrl = "https://nfs.faireconomy.media/ff_calendar_thisweek.xml";
int xmlHandle;
int logHandle = -1;
int BoEvent, finalend, end, i;
int begin;
string mainData[100][7];
int minsTillNews;
string sData, csvoutput;
string commentStr;
int tmpMins;
int idxOfNext;
int dispMinutes[2];
string dispTitle[2],
dispCountry[2],
dispImpact[2],
dispForecast[2],
dispPrevious[2];

string sTags[7] = { "<title>", "<country>", "<date><![CDATA[", "<time><![CDATA[", "<impact><![CDATA[", "<forecast><![CDATA[", "<previous><![CDATA[" };
string eTags[7] = { "</title>", "</country>", "]]></date>", "]]></time>", "]]></impact>", "]]></forecast>", "]]></previous>" };
//old tag
//string sTags[7] = { "<title>", "<country>", "<date>", "<time>", "<impact>", "<forecast>", "<previous>" };
//string eTags[7] = { "</title>", "</country>", "</date>", "</time>", "</impact>", "</forecast>", "</previous>" };

static bool NeedToGetFile = false;
static int PrevMinute = -1;
static int RefreshMin = 0;
static int RefreshHour = 0;

datetime LastTimeAlert1 = 0; // Used to make sure we only draw something once per annoucement. Added by MN
string xmlFileName; // Made global. added by MN

int init()
{
if (DebugLevel > 0)
Print("In Init()...\n");

SetIndexStyle(0, DRAW_NONE);
SetIndexBuffer(0, ExtMapBuffer0);

SetIndexStyle(1, DRAW_NONE);
SetIndexBuffer(1, ExtMapBuffer1);

SetIndexStyle(1, DRAW_NONE);
SetIndexBuffer(2, ExtMapBuffer2);

IndicatorShortName("FFCal");
// SetIndexLabel(0, "MinsBeforeNews");
// SetIndexLabel(1, "MinsAfterNews");
//-------------------------------------------------------------
MathSrand(TimeLocal()); //random number
RefreshMin = (MathRand() % 60);
RefreshHour = (MathRand() % 6);
if (DebugLevel > 0)
{ Print("RefreshMin == ", RefreshMin);
Print("RefreshHour == ", RefreshHour);
}
//------------------------------------------------------------

return(0);
}

int deinit()
{
int i2;

ObjectDelete("Sponsor");

ObjectDelete("Minutes");
ObjectDelete("Impact");
ObjectDelete("Previous");
ObjectDelete("Forecast");

ObjectDelete("Minutes2");
ObjectDelete("Impact2");
ObjectDelete("Previous2");
ObjectDelete("Forecast2");

// Cycle through all the Objects looking for the Vertical Line. //added by MN
for(i2=ObjectsTotal()-1; i2 >= 0; i2--)
{
string VerticalLineName = ObjectName(i2);
if (StringSubstr(VerticalLineName, 0, 5) != "vLine")
continue;

ObjectDelete(VerticalLineName);
}

// Cycle through all the Objects looking for the HeadLine text. //added by MN
for (i2=ObjectsTotal()-1; i2 >= 0; i2--)
{
string HeadlineName = ObjectName(i2);
if (StringSubstr(HeadlineName, 0, 8) != "Headline")
continue;

ObjectDelete(HeadlineName);
}

// Fixed one case of divide by zero where multiple charts
// indicator is on was trying to overwrite the same file
xmlFileName = GetXmlFileName();
xmlHandle = FileOpen(xmlFileName, FILE_BIN|FILE_READ|FILE_WRITE);

// File does not exist if FileOpen return -1 or if GetLastError = ERR_CANNOT_OPEN_FILE (4103)
if (xmlHandle >= 0)
{
// Since file exists, Close what we just opened
FileClose(xmlHandle);

// Delete our news file and redownload a new one to prevent a remainder from zero divide error
if (!SaveXmlFiles)
FileDelete(xmlFileName);
}

return(0);
}

string GetXmlFileName()
{
return (Month() + "-" + Day() + "-" + Year() + "-" + Symbol() + Period() + "-" + "FFCal.xml");
}
//C:\Program Files\IBFX Australia\experts\files\2-16-2011-EURUSD240-FFCal.xml

int start()
{
int newsIdx = 0;
int nextNewsIdx = -1;
int next;
string myEvent;
bool skip;
datetime newsTime;
ulong file_size = 0;
ulong data_size = 0;

// check to make sure we are connected, otherwise exit. Added by MN
if (!IsConnected())
{
Print("News Indicator is disabled because NO CONNECTION to Broker!");
return(0);
}

// If we are not logging, then do not output debug statements either
if (!EnableLogging)
DebugLevel = 0;

commentStr = "FOREX FACTORY CALENDAR";

// Added this section to check if the XML file already exists.
// If it does NOT, then we need to set a flag to go get it
xmlFileName = GetXmlFileName();
xmlHandle = FileOpen(xmlFileName, FILE_BIN|FILE_READ);
file_size = FileSize(xmlHandle);

// File does not exist if FileOpen return -1 or if GetLastError = ERR_CANNOT_OPEN_FILE (4103)
if (xmlHandle >= 0)
{
// Since file exists, close what we just opened
FileClose(xmlHandle);
NeedToGetFile = false;
}
else
NeedToGetFile = true;

//added by MN. Set this to false when using in another EA or Chart, so that the multiple
//instances of the indicator dont fight with each other
if (AllowWebUpdates)
{
// New method: Use global variables so that when put on multiple charts, it
// will not update overly often; only first time and every 4 hours
if (DebugLevel > 1)
Print(GlobalVariableGet("LastUpdateTime") + " " + (TimeCurrent() - GlobalVariableGet("LastUpdateTime")));

if (file_size == 0 || NeedToGetFile || GlobalVariableCheck("LastUpdateTime") == false || (TimeCurrent() - GlobalVariableGet("LastUpdateTime")) > 14400)
{
if (DebugLevel > 1)
Print("sUrl == ", sUrl);

if (DebugLevel > 0)
Print("Grabbing Web, url = ", sUrl);

// THIS CALL WAS DONATED BY PAUL TO HELP FIX THE RESOURCE ERROR
GrabWeb(sUrl, sData);

if (DebugLevel > 0)
{
Print("Opening XML file...\n");
Print(sData);
}

// Delete existing file
FileDelete(xmlFileName);

// Write the contents of the ForexFactory page to an .htm file
// If it is still open from the above FileOpen call, close it.
xmlHandle = FileOpen(xmlFileName, FILE_BIN|FILE_WRITE);
if (xmlHandle < 0)
{
if (DebugLevel > 0)
Print("Can\'t open new xml file, the last error is ", GetLastError());
return(false);
}
FileWriteString(xmlHandle, sData, StringLen(sData));
FileClose(xmlHandle);

if (DebugLevel > 0)
Print("Wrote XML file...\n");

// THIS BLOCK OF CODE DONATED BY WALLY TO FIX THE RESOURCE ERROR
//--- Look for the end XML tag to ensure that a complete page was downloaded ---//
end = StringFind(sData, "</weeklyevents>", 0);

if (end <= 0)
{
Alert("FFCal Error - Web page download was not complete!");
return(false);
}
else
{
// set global to time of last update
GlobalVariableSet("LastUpdateTime", TimeCurrent());
}

//-------------------------------------------------------------------------------//
}
} //end of allow web updates

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Perform remaining checks once per minute
if (!IsEA_Call && Minute() == PrevMinute)
return (true);
PrevMinute = Minute();
// Print("FFCal NEW MINUTE...Refreshing News from XML file...");

// Open the log file (will not open if logging is turned off)
OpenLog("FFCal");

// Init the buffer array to zero just in case
ArrayInitialize(ExtMapBuffer0, 0);
ArrayInitialize(ExtMapBuffer1, 0);

// Open the XML file
xmlHandle = FileOpen(xmlFileName, FILE_BIN|FILE_READ);
if (xmlHandle < 0)
{
Print("Can\'t open xml file: ", xmlFileName, ". The last error is ", GetLastError());
return(false);
}

if (DebugLevel > 0)
Print("XML file open must be okay size = ", file_size, " handle = ", xmlHandle);

// Read in the whole XML file
// Ave file length == ~7K, so 65536 should always read whole file

sData = FileReadString(xmlHandle, 65536);
bool pnt = FileSeek(xmlHandle, 0, 0); //senza non funziona

file_size = FileSize(xmlHandle);
data_size = StringLen(sData);

//if (((ulong)StringLen(sData)) < FileSize(xmlHandle))
// sData = sData + FileReadString(xmlHandle, FileSize(xmlHandle));
if (data_size < file_size)
sData = sData + FileReadString(xmlHandle, file_size);

// Because MT4 build 202 complained about too many files open and MT4 hung. Added by MN
if (xmlHandle > 0)
FileClose(xmlHandle);

// Get the currency pair, and split it into the two countries
string pair = Symbol();
string cntry1 = StringSubstr(pair, 0, 3);
string cntry2 = StringSubstr(pair, 3, 3);
if (DebugLevel > 0)
Print("cntry1 = ", cntry1, " cntry2 = ", cntry2);

if (DebugLevel > 0)
Log("Weekly calendar for " + pair + "\n\n");

// -------------------------------------------------
// Parse the XML file looking for an event to report
// -------------------------------------------------

tmpMins = 10080; // (a week)
BoEvent = 0;

while (true)
{
BoEvent = StringFind(sData, "<event>", BoEvent);
if (BoEvent == -1)
break;

BoEvent += 7;
next = StringFind(sData, "</event>", BoEvent);
if (next == -1)
break;

myEvent = StringSubstr(sData, BoEvent, next - BoEvent);
BoEvent = next;

begin = 0;
skip = false;
for (i=0; i < 7; i++)
{
mainData[newsIdx][i] = "";
next = StringFind(myEvent, sTags[i], begin);

// Within this event, if tag not found, then it must be missing; skip it
if (next == -1)
continue;
else
{
// We must have found the sTag okay...
begin = next + StringLen(sTags[i]); // Advance past the start tag
end = StringFind(myEvent, eTags[i], begin); // Find start of end tag
if (end > begin && end != -1)
{
// Get data between start and end tag
mainData[newsIdx][i] = StringSubstr(myEvent, begin, end - begin);
}
}
}

// for (i=6; i >= 0; i--)
// Print(sTags[i], " = ", mainData[newsIdx][i]);

// = - = = - = = - = = - = = - = = - = = - = = - = = - = = - =
// Test against filters that define whether we want to
// skip this particular annoucement
if (cntry1 != mainData[newsIdx][COUNTRY] && cntry2 != mainData[newsIdx][COUNTRY] &&
((!ReportAllForUSD || mainData[newsIdx][COUNTRY] != "USD") &&
(!ReportAllForEUR || mainData[newsIdx][COUNTRY] != "EUR") &&
(!ReportAllForGBP || mainData[newsIdx][COUNTRY] != "GBP")&&
(!ReportAllForNZD || mainData[newsIdx][COUNTRY] != "NZD")&&
(!ReportAllForJPY || mainData[newsIdx][COUNTRY] != "JPY")&&
(!ReportAllForAUD || mainData[newsIdx][COUNTRY] != "AUD")&&
(!ReportAllForCAD || mainData[newsIdx][COUNTRY] != "CAD")&&
(!ReportAllForCHF || mainData[newsIdx][COUNTRY] != "CHF")&&
(!ReportAllForCNY || mainData[newsIdx][COUNTRY] != "CNY")))
skip = true;

if (!IncludeHigh && mainData[newsIdx][IMPACT] == "High")
skip = true;
if (!IncludeMedium && mainData[newsIdx][IMPACT] == "Medium")
skip = true;
if (!IncludeLow && mainData[newsIdx][IMPACT] == "Low")
skip = true;
if (!IncludeSpeaks && (StringFind(mainData[newsIdx][TITLE], "speaks") != -1 ||
StringFind(mainData[newsIdx][TITLE], "Speaks") != -1) )
skip = true;
if (mainData[newsIdx][TIME] == "All Day" ||
mainData[newsIdx][TIME] == "Tentative" ||
mainData[newsIdx][TIME] == "")
skip = true;
// = - = = - = = - = = - = = - = = - = = - = = - = = - = = - =

// If not skipping this event, then log it into the draw buffers
if (!skip)
{
// If we got this far then we need to calc the minutes until this event
//
// First, convert the announcement time to seconds (in GMT)
newsTime = StrToTime(MakeDateTime(mainData[newsIdx][DATE], mainData[newsIdx][TIME]));

// Now calculate the minutes until this announcement (may be negative)
minsTillNews = (newsTime - myTimeGMT()) / 60;
if (DebugLevel > 0)
{
Log("FOREX FACTORY\nTitle: " + mainData[newsIdx][TITLE] + "\n" + minsTillNews + "\n\n");
}

// This "if" section added by MN
// If minsTillNews is zero, then it's the time our news is hit
if ((minsTillNews == 0) && (LastTimeAlert1 != newsTime))
{
// draw the 1st news event onto the chart vertically so
// we have a visual record of when it occured
DisplayVerticalNews(dispTitle[0], dispCountry[0], 0);

// If there is a 2nd simultaneous news event,
// display the 2nd news event
if (dispMinutes[0] == dispMinutes[1])
DisplayVerticalNews(dispTitle[1], dispCountry[1], 1);

// only draw once per announcement
LastTimeAlert1 = newsTime;
}

// This "if" section added by MN
// Back Draw old news onto the chart. Added by MN
if (ShowVertNews && !IsEA_Call)
{
// Back draw 1st news headline
DisplayOldNews(mainData[newsIdx][TITLE], mainData[newsIdx][COUNTRY], 0, StrToTime(MakeDateTime(mainData[newsIdx][DATE], mainData[newsIdx][TIME])));

//if there is a 2nd simultaneously occuring news headline, draw that onto the chart as well
if (mainData[newsIdx][TIME] == mainData[newsIdx+1][TIME])
DisplayOldNews(mainData[newsIdx+1][TITLE], mainData[newsIdx+1][COUNTRY], 1,
StrToTime(MakeDateTime(mainData[newsIdx+1][DATE], mainData[newsIdx+1][TIME])));
}

// Keep track of the most recent news announcement.
// Do that by saving each one until we get to the
// first annoucement that isn't in the past; i.e.
// minsTillNews > 0. Then, keep this one instead for
// display, but only once the minutes until the next
// news is SMALLER than the minutes since the last.
// Print("Mins till event: ", minsTillNews);
if (minsTillNews < 0 || MathAbs(tmpMins) > minsTillNews)
{
idxOfNext = newsIdx;
tmpMins = minsTillNews;
}

Log("Weekly calendar for " + pair + "\n\n");
if (DebugLevel > 0)
{
Log("FOREX FACTORY\nTitle: " + mainData[newsIdx][TITLE] +
"\nCountry: " + mainData[newsIdx][COUNTRY] +
"\nDate: " + mainData[newsIdx][DATE] +
"\nTime: " + mainData[newsIdx][TIME] +
"\nImpact: " + mainData[newsIdx][IMPACT] +
"\nForecast: " + mainData[newsIdx][FORECAST] +
"\nPrevious: " + mainData[newsIdx][PREVIOUS] + "\n\n");
}

// Do alert if user has enabled
if (Alert1MinsBefore != -1 && minsTillNews == Alert1MinsBefore)
Alert(Alert1MinsBefore, " minutes until news for ", pair, ": ", mainData[newsIdx][TITLE]);
if (Alert2MinsBefore != -1 && minsTillNews == Alert2MinsBefore)
Alert(Alert2MinsBefore, " minutes until news for ", pair, ": ", mainData[newsIdx][TITLE]);

// Buffers are set up as so:
// ExtMapBuffer0 contains the time UNTIL each announcement (can be negative)
// e.g. [0] = -372; [1] = 25; [2] = 450; [3] = 1768 (etc.)
// ExtMapBuffer1[0] has the mintutes since the last annoucement.
// ExtMapBuffer1[1] has the mintutes until the next annoucement.
ExtMapBuffer0[newsIdx] = minsTillNews;
newsIdx++;
}
}

// Cycle through the events array and pick out the most recent
// past and the next coming event to put into ExtMapBuffer1.
// Put the corresponding impact for these two into ExtMapBuffer2.
bool first = true;
ExtMapBuffer1[0] = 99999;
ExtMapBuffer1[1] = 99999;
ExtMapBuffer2[0] = 0;
ExtMapBuffer2[1] = 0;
string outNews = "Minutes until news events for " + pair + " : ";
for (i=0; i < newsIdx; i++)
{
outNews = outNews + ExtMapBuffer0[i] + ", ";
if (ExtMapBuffer0[i] >= 0 && first)
{
first = false;

// Put the relevant info into the indicator buffers...

// Minutes SINCE - - - - - - - - - - - - - - - - - - - - - - - - -
// (does not apply if the first event of the week has not passed)
if (i > 0)
{
ExtMapBuffer1[0] = MathAbs(ExtMapBuffer0[i-1]);
ExtMapBuffer2[0] = ImpactToNumber(mainData[i-1][IMPACT]);
}

// Minutes UNTIL - - - - - - - - - - - - - - - - - - - - - - - - -
// Check if past the last event.
if (ExtMapBuffer0[i] > 0 || (ExtMapBuffer0[i] == 0 && ExtMapBuffer0[i+1] > 0))
{
ExtMapBuffer1[1] = ExtMapBuffer0[i];
}
ExtMapBuffer2[1] = ImpactToNumber(mainData[i][IMPACT]);
}

// Also use this loop to set which information to display
if (i == idxOfNext)
{
dispTitle[0] = mainData[i][TITLE];
dispCountry[0] = mainData[i][COUNTRY];
dispImpact[0] = mainData[i][IMPACT];
dispForecast[0] = mainData[i][FORECAST];
dispPrevious[0] = mainData[i][PREVIOUS];
dispMinutes[0] = ExtMapBuffer0[i];
}

if (i == idxOfNext + 1)
{
dispTitle[1] = mainData[i][TITLE];
dispCountry[1] = mainData[i][COUNTRY];
dispImpact[1] = mainData[i][IMPACT];
dispForecast[1] = mainData[i][FORECAST];
dispPrevious[1] = mainData[i][PREVIOUS];
dispMinutes[1] = ExtMapBuffer0[i];
}

}
// If we are past all news events, then neither one will have been
// set, so set the past event to the last (negative) minutes
if (ExtMapBuffer1[0] == 0 && ExtMapBuffer1[1] == 0)
{
ExtMapBuffer1[0] = ExtMapBuffer0[i-1];
ExtMapBuffer1[1] = 999999;

// Ensures that we clean up so that we get old text
// left behind on old news annoumcents. Added by MN
ObjectDelete("Impact");
ObjectDelete("Previous");
ObjectDelete("Forecast");

ObjectDelete("Impact2");
ObjectDelete("Previous2");
ObjectDelete("Forecast2");
}

// For debugging...Print the tines until news events, as a "Comment"
if (DebugLevel > 0)
{
Print(outNews);
Print("LastMins (ExtMapBuffer1[0]) = ", ExtMapBuffer1[0]);
Print("NextMins (ExtMapBuffer1[1]) = ", ExtMapBuffer1[1]);
}

if (!IsEA_Call)
OutputToChart();

if (logHandle > 0)
{
FileClose(logHandle);
logHandle = -1;
}

return (0);
}

// Draws the news veritically on the Chart so that we
// have a visual reference of when it occurs. Added by MN
void DisplayVerticalNews(string dispVTitle, string dispVCountry, int shift)
{
double Pivot, Height = 0.0;

// Calculate our pivot point to determine where to place the news text
Pivot = (iHigh(NULL, 1440, 1) +
iLow(NULL, 1440, 1) +
iClose(NULL, 1440, 1)) / 3;

// If Open price is above the Pivot determine our height
if (Open[0] > Pivot)
Height = Low[iLowest(NULL,0,MODE_LOW,5,0)] - VertTxtShift*Point;
else
// Otherwise Open is below Pivot
Height = High[iHighest(NULL,0,MODE_HIGH,5,0)] + VertTxtShift*Point;

if (shift == 0)
{
// Draw a vertical line at the time of the news if it hasnt already been drawn
if (ObjectFind("vLine" + Time[0]) == -1)
{
ObjectCreate("vLine" + Time[0], OBJ_TREND, 0, Time[0], 0, Time[0], High[0]); // experimental
ObjectSet("vLine" + Time[0], OBJPROP_COLOR, VertLineColor);
ObjectSet("vLine" + Time[0], OBJPROP_STYLE, STYLE_DOT);

// Put object in the background behind any other object on the chart
ObjectSet("vLine" + Time[0], OBJPROP_BACK, true);
}

// Place our news if it hasnt already been placed on our chart
if (ObjectFind("Headline" + Time[0]) == -1)
{
// For x value use Time[0], for y value find the lowest
// bar within the last 10 bars, subtract by VertTxtShift
// and used that as our y coordinate
ObjectCreate("Headline" + Time[0], OBJ_TEXT, 0,
Time[0] - VertLeftLineShift, Height);

// Rotate the text 90 degrees
ObjectSet("Headline" + Time[0], OBJPROP_ANGLE, 90);

ObjectSetText("Headline" + Time[0],
"News: " + dispVCountry + " " + dispVTitle,
VertTxtSize, "Arial", VertTxtColor);
}
}
// Draw second news headline
else
{
//place our news if it hasnt already been placed on our chart
if (ObjectFind("Headline" + Time[0]+"s") == -1)
{
// For x value use Time[0], for y value find the lowest
// bar within the last 10 bars, subtract by VertTxtShift
// and used that as our y coordinate
ObjectCreate("Headline" + Time[0] + "s", OBJ_TEXT, 0,
Time[0] + VertRightLineShift, Height);

// Rotate the text 90 degrees
ObjectSet("Headline" + Time[0] + "s", OBJPROP_ANGLE, 90);

ObjectSetText("Headline" + Time[0] + "s",
"News: " + dispVCountry + " " + dispVTitle,
VertTxtSize, "Arial", VertTxtColor);
}
}

//force a redraw of our chart
WindowRedraw();

return;
}

// Back Draw Old news veritically on the Chart so that we
// have a visual reference of when it did occur. Added by MN
void DisplayOldNews(string dispOTitle, string dispOCountry, int shift, datetime TheTime)
{
int BarShift;
double Pivot;
double Height = 0.0;

// We have TheTime, now we need the shift so we can
// access this Bar's High/Low/Close information
BarShift = iBarShift(NULL, 0, TheTime);

// Calculate our pivot point to determine where to place the news text
Pivot = (iHigh(NULL, 1440, BarShift) +
iLow(NULL, 1440, BarShift) +
iClose(NULL, 1440, BarShift)) / 3;

// If open price is above the Pivot, determine our height
if (Open[BarShift] > Pivot)
Height = Low[iLowest(NULL, 0, MODE_LOW, 5, BarShift)] - VertTxtShift*Point;

// Otherwise Open is below Pivot
else
Height = High[iHighest(NULL, 0, MODE_HIGH, 5, BarShift)] + VertTxtShift*Point;

//Print("Title recieved : " + dispTitle + " Country Recieved: " + dispCountry + " Date and time Recieved: " + TheTime);

// We only want to vertically draw the news if it hasnt occured yet
if (TheTime < Time[0])
{
// Draw the first news headline
if (shift == 0)
{
// Draw a vertical line at the time of the
// news if it hasnt already been drawn
if (ObjectFind("vLine" + TheTime) == -1)
{
ObjectCreate("vLine" + TheTime, OBJ_TREND, 0, TheTime, 0, TheTime, High[0]); //experimental
ObjectSet("vLine" + TheTime, OBJPROP_COLOR, VertLineColor);
ObjectSet("vLine" + TheTime, OBJPROP_STYLE, STYLE_DOT);

// put object in the background behind any other object on the chart
ObjectSet("vLine" + TheTime, OBJPROP_BACK, true);
}

// Place our news if it hasnt already been placed on our chart
if (ObjectFind("Headline" + TheTime) == -1)
{
// For x value use Time[0], for y value find the lowest bar within the last 10 bars, subtract by VertTxtShift and used that as our y coordinate
ObjectCreate("Headline" + TheTime, OBJ_TEXT, 0, TheTime - VertLeftLineShift, Height);

// rotate the text 90 degrees
ObjectSet("Headline" + TheTime, OBJPROP_ANGLE, 90);
ObjectSetText("Headline" + TheTime, "News: " + dispOCountry + " " + dispOTitle,
VertTxtSize, "Arial", VertTxtColor);
}
}

// Draw second news headline
else
{
//place our news if it hasnt already been placed on our chart
if (ObjectFind("Headline" + TheTime + "s") == -1)
{
// For x value use Time[0], for y value find the lowest bar within the
// last 10 bars, subtract by VertTxtShift and used that as our y coordinate
ObjectCreate("Headline" + TheTime + "s", OBJ_TEXT, 0,
TheTime + VertRightLineShift, Height);

// Rotate the text 90 degrees
ObjectSet("Headline" + TheTime + "s", OBJPROP_ANGLE, 90);
ObjectSetText("Headline" + TheTime + "s", "News: " + dispOCountry + " " + dispOTitle,
VertTxtSize, "Arial", VertTxtColor);
}
} // end of "if shift)
} // end of (TheTime < ((Time[0]))+180+180)

// Force a redraw of our chart
WindowRedraw();

return;
}

void OutputToChart()
{
// Added by Robert for using TxtSize and TxtColor for easier reading
int curY = 12;
int Days, Hours, Mins; // to display time in days, hours, minutes
string TimeStr;

if (ObjectFind("Sponsor") == -1)
ObjectCreate("Sponsor", OBJ_LABEL, 0, 0, 0);
ObjectSetText("Sponsor", commentStr, TxtSize, "Arial Bold", TxtColorTitle);
ObjectSet("Sponsor", OBJPROP_CORNER, NewsCorner);
ObjectSet("Sponsor", OBJPROP_XDISTANCE, 10);
ObjectSet("Sponsor", OBJPROP_YDISTANCE, curY);

// If the time is 0 or negative, we want to say
// "xxx mins SINCE ... news event", else say "UNTIL ... news event"
string sinceUntil = "until ";
int dispMins = dispMinutes[0];
if (dispMinutes[0] <= 0)
{
sinceUntil = "since ";
dispMins *= -1;
}
curY = curY + TxtSize + 4;

// added the following to show hours and days for longer durations
// this could be enhanced to suppress 0 hours and 0 minutes
if (dispMins == 999999)
{
TimeStr = " (No more events this week)";
}
else if (dispMins < 60)
{
TimeStr = dispMins + " mins ";
}
else // time is 60 minutes or more
{
Hours = MathRound(dispMins / 60);
Mins = dispMins % 60;
if (Hours < 24) // less than a day: show hours and minutes
{
TimeStr = Hours + " hrs " + Mins + " mins ";
}
else // days, hours, and minutes
{
Days = MathRound(Hours / 24);
Hours = Hours % 24;
TimeStr = Days + " days " + Hours + " hrs " + Mins + " mins ";
}
}

if (ObjectFind("Minutes") == -1)
ObjectCreate("Minutes", OBJ_LABEL, 0, 0, 0);

if (dispMins == 999999)
ObjectSetText("Minutes", TimeStr, TxtSize, "Arial Bold", TxtColorNews);
else
ObjectSetText("Minutes", TimeStr + sinceUntil + dispCountry[0] + ": " + dispTitle[0], TxtSize, "Arial Bold", TxtColorNews);

ObjectSet("Minutes", OBJPROP_CORNER, NewsCorner);
ObjectSet("Minutes", OBJPROP_XDISTANCE, 10);
ObjectSet("Minutes", OBJPROP_YDISTANCE, curY);

curY = curY + TxtSize + 4;
if (ObjectFind("Impact") == -1)
ObjectCreate("Impact", OBJ_LABEL, 0, 0, 0);
ObjectSetText("Impact", "Impact: " + dispImpact[0], TxtSize, "Arial Bold", TxtColorImpact);
ObjectSet("Impact", OBJPROP_CORNER, NewsCorner);
ObjectSet("Impact", OBJPROP_XDISTANCE, 10);
ObjectSet("Impact", OBJPROP_YDISTANCE, curY);

if (dispPrevious[0] != "")
{
curY = curY + TxtSize + 4;
if (ObjectFind("Previous") == -1)
ObjectCreate("Previous", OBJ_LABEL, 0, 0, 0);
ObjectSetText("Previous", "Previous: " + dispPrevious[0], TxtSize, "Arial Bold", TxtColorPrevious);
ObjectSet("Previous", OBJPROP_CORNER, NewsCorner);
ObjectSet("Previous", OBJPROP_XDISTANCE, 10);
ObjectSet("Previous", OBJPROP_YDISTANCE, curY);
}

if (dispForecast[0] != "")
{
curY = curY + TxtSize + 4;
if (ObjectFind("Forecast") == -1)
ObjectCreate("Forecast", OBJ_LABEL, 0, 0, 0);
ObjectSetText("Forecast", "Forecast: " + dispForecast[0], TxtSize, "Arial Bold", TxtColorForecast);
ObjectSet("Forecast", OBJPROP_CORNER, NewsCorner);
ObjectSet("Forecast", OBJPROP_XDISTANCE, 10);
ObjectSet("Forecast", OBJPROP_YDISTANCE, curY);
}

///////////////////////////////////////////////////////////////////////////
// Do second news event
if (ShowNextTwoEvents && dispTitle[1] != "")
{
sinceUntil = "until ";
dispMins = dispMinutes[1];
if (dispMinutes[1] <= 0)
{
sinceUntil = "since ";
dispMins *= -1;
}

curY = curY + TxtSize + 20;

// added the following to show hours and days for longer durations
// this could be enhanced to suppress 0 hours and 0 minutes
if (dispMins == 999999)
{
TimeStr = " (No more events this week)";
}
else if (dispMins < 60)
{
TimeStr = dispMins + " mins ";
}
else // time is 60 minutes or more
{
Hours = MathRound(dispMins / 60);
Mins = dispMins % 60;
if (Hours < 24) // less than a day: show hours and minutes
{
TimeStr = Hours + " hrs " + Mins + " mins ";
}
else // days, hours, and minutes
{
Days = MathRound(Hours / 24);
Hours = Hours % 24;
TimeStr = Days + " days " + Hours + " hrs " + Mins + " mins ";
}
}
if (ObjectFind("Minutes2") == -1)
ObjectCreate("Minutes2", OBJ_LABEL, 0, 0, 0);

if (dispMins == 999999)
ObjectSetText("Minutes", TimeStr, TxtSize, "Arial Bold", TxtColorNews);
else
ObjectSetText("Minutes2", TimeStr + "until " + dispCountry[1] + ": " + dispTitle[1], TxtSize, "Arial Bold", TxtColorNews);

ObjectSet("Minutes2", OBJPROP_CORNER, NewsCorner);
ObjectSet("Minutes2", OBJPROP_XDISTANCE, 10);
ObjectSet("Minutes2", OBJPROP_YDISTANCE, curY);

curY = curY + TxtSize + 4;
if (ObjectFind("Impact2") == -1)
ObjectCreate("Impact2", OBJ_LABEL, 0, 0, 0);
ObjectSetText("Impact2", "Impact: " + dispImpact[1], TxtSize, "Arial Bold", TxtColorImpact);
ObjectSet("Impact2", OBJPROP_CORNER, NewsCorner);
ObjectSet("Impact2", OBJPROP_XDISTANCE, 10);
ObjectSet("Impact2", OBJPROP_YDISTANCE, curY);

if (dispPrevious[1] != "")
{
curY = curY + TxtSize + 4;
if (ObjectFind("Previous2") == -1)
ObjectCreate("Previous2", OBJ_LABEL, 0, 0, 0);
ObjectSetText("Previous2", "Previous: " + dispPrevious[1], TxtSize, "Arial Bold", TxtColorPrevious);
ObjectSet("Previous2", OBJPROP_CORNER, NewsCorner);
ObjectSet("Previous2", OBJPROP_XDISTANCE, 10);
ObjectSet("Previous2", OBJPROP_YDISTANCE, curY);
}

if (dispForecast[1] != "")
{
curY = curY + TxtSize + 4;
if (ObjectFind("Forecast2") == -1)
ObjectCreate("Forecast2", OBJ_LABEL, 0, 0, 0);
ObjectSetText("Forecast2", "Forecast: " + dispForecast[1], TxtSize, "Arial Bold", TxtColorForecast);
ObjectSet("Forecast2", OBJPROP_CORNER, NewsCorner);
ObjectSet("Forecast2", OBJPROP_XDISTANCE, 10);
ObjectSet("Forecast2", OBJPROP_YDISTANCE, curY);
}
}

return;
}

double ImpactToNumber(string impact)
{
if (impact == "High")
return (3);
if (impact == "Medium")
return (2);
if (impact == "Low")
return (1);
else
return (0);
}

string MakeDateTime(string strDate, string strTime)
{
// Print("Converting Forex Factory Time into Metatrader time..."); //added by MN
// Converts forexfactory time & date into yyyy.mm.dd hh:mm
int n1stDash = StringFind(strDate, "-");
int n2ndDash = StringFind(strDate, "-", n1stDash+1);

string strMonth = StringSubstr(strDate, 0, 2);
string strDay = StringSubstr(strDate, 3, 2);
string strYear = StringSubstr(strDate, 6, 4);
// strYear = "20" + strYear;

int nTimeColonPos = StringFind(strTime, ":");
string strHour = StringSubstr(strTime, 0, nTimeColonPos);
string strMinute = StringSubstr(strTime, nTimeColonPos+1, 2);
string strAM_PM = StringSubstr(strTime, StringLen(strTime)-2);

int nHour24 = StrToInteger(strHour);
if ((strAM_PM == "pm" || strAM_PM == "PM") && nHour24 != 12)
{
nHour24 += 12;
}
if ((strAM_PM == "am" || strAM_PM == "AM") && nHour24 == 12)
{
nHour24 = 0;
}
string strHourPad = "";
if (nHour24 < 10)
strHourPad = "0";

return(StringConcatenate(strYear, ".", strMonth, ".", strDay, " ", strHourPad, nHour24, ":", strMinute));
}

//=================================================================================================
//=================================================================================================
//==================================== GrabWeb Functions ======================================
//=================================================================================================
//=================================================================================================
// Main Webscraping function
// ~~~~~~~~~~~~~~~~~~~~~~~~~
// bool GrabWeb(string strUrl, string& strWebPage)
// returns the text of any webpage. Returns false on timeout or other error
//
// Parsing functions
// ~~~~~~~~~~~~~~~~~
// string GetData(string strWebPage, int nStart, string strLeftTag, string strRightTag, int& nPos)
// obtains the text between two tags found after nStart, and sets nPos to the end of the second tag
//
// void Goto(string strWebPage, int nStart, string strTag, int& nPos)
// Sets nPos to the end of the first tag found after nStart

bool bWinInetDebug = false;

int hSession_IEType;
int hSession_Direct;
int Internet_Open_Type_Preconfig = 0;
int Internet_Open_Type_Direct = 1;
int Internet_Open_Type_Proxy = 3;
int Buffer_LEN = 80;

#import "wininet.dll"

#define INTERNET_FLAG_PRAGMA_NOCACHE 0x00000100 // Forces the request to be resolved by the origin server, even if a cached copy exists on the proxy.
#define INTERNET_FLAG_NO_CACHE_WRITE 0x04000000 // Does not add the returned entity to the cache.
#define INTERNET_FLAG_RELOAD 0x80000000 // Forces a download of the requested file, object, or directory listing from the origin server, not from the cache.

int InternetOpenW( string sAgent, int lAccessType, string sProxyName, string sProxyBypass, int lFlags );
int InternetOpenUrlW(int hInternetSession,string sUrl,string sHeaders="",int lHeadersLength=0,int lFlags=0,int lContext=0);
int InternetReadFile(int hFile, uchar &sBuffer[],int lNumBytesToRead,int &lNumberOfBytesRead[]);
int InternetCloseHandle(int hInet);

#import

int hSession(bool Direct)
{
string InternetAgent;
if (hSession_IEType == 0)
{
InternetAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461)";
hSession_IEType = InternetOpenW(InternetAgent, Internet_Open_Type_Preconfig, "0", "0", 0);
hSession_Direct = InternetOpenW(InternetAgent, Internet_Open_Type_Direct, "0", "0", 0);
}
if (Direct)
{
return(hSession_Direct);
}
else
{
return(hSession_IEType);
}
}

bool GrabWeb(string strUrl, string& strWebPage)
{
int hInternet;
int iResult;
int lReturn[] = {1};
uchar sBuffer[1024];
int bytes;
uint flags=INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD;

hInternet = InternetOpenUrlW(hSession(FALSE), strUrl, NULL, 0, flags);
//hInternet = InternetOpenUrlW(hSession(FALSE), strUrl, "0", 0,
// INTERNET_FLAG_NO_CACHE_WRITE |
// INTERNET_FLAG_PRAGMA_NOCACHE |
// INTERNET_FLAG_RELOAD, 0);

if (bWinInetDebug)
Log("hInternet: " + hInternet);
if (hInternet == 0)
return(false);

Print("Reading URL: " + strUrl); //added by MN
iResult = InternetReadFile(hInternet, sBuffer, Buffer_LEN, lReturn);

if (bWinInetDebug) Log("iResult: " + iResult);
if (bWinInetDebug) Log("lReturn: " + lReturn[0]);
if (bWinInetDebug) Log("iResult: " + iResult);
if (bWinInetDebug) Log("sBuffer: " + CharArrayToString(sBuffer, 0, lReturn[0]));
if (iResult == 0) return(false);
bytes = lReturn[0];
strWebPage = CharArrayToString(sBuffer, 0, lReturn[0]);

//If there's more data then keep reading it into the buffer
while (lReturn[0] != 0)
{
iResult = InternetReadFile(hInternet, sBuffer, Buffer_LEN, lReturn);
if (lReturn[0]==0) break;
bytes = bytes + lReturn[0];
strWebPage = strWebPage + CharArrayToString(sBuffer, 0, lReturn[0]);
}
Print("Closing URL web connection"); //added by MN
iResult = InternetCloseHandle(hInternet);
if (iResult == 0) return(false);

return(true);
}

//=================================================================================================
//=================================================================================================
//=================================== LogUtils Functions ======================================
//=================================================================================================
//=================================================================================================

void OpenLog(string strName)
{
if (!EnableLogging)
return;

if (logHandle <= 0)
{
string strMonthPad = "";
string strDayPad = "";
if (Month() < 10)
strMonthPad = "0";
if (Day() < 10)
strDayPad = "0";

string strFilename = StringConcatenate(strName, "_", Year(), strMonthPad, Month(), strDayPad, Day(), "_log.txt");

logHandle = FileOpen(strFilename,FILE_CSV|FILE_READ|FILE_WRITE);
Print("logHandle =================================== ", logHandle);
}
if (logHandle > 0)
{
FileFlush(logHandle);
FileSeek(logHandle, 0, SEEK_END);
}
}

void Log(string msg)
{
if (!EnableLogging)
return;

if (logHandle <= 0)
return;

msg = TimeToStr(TimeCurrent(),TIME_DATE|TIME_MINUTES|TIME_SECONDS) + " " + msg;
FileWrite(logHandle,msg);
}

//=================================================================================================
//=================================================================================================
//=================================== Timezone Functions ======================================
//=================================================================================================
//=================================================================================================

#import "kernel32.dll"
int GetTimeZoneInformation(int& TZInfoArray[]);
#import

#define TIME_ZONE_ID_UNKNOWN 0
#define TIME_ZONE_ID_STANDARD 1
#define TIME_ZONE_ID_DAYLIGHT 2

int TZInfoArray[43];

datetime myTimeGMT()
{
int DST = GetTimeZoneInformation(TZInfoArray);
if (DST == 1)
DST = 3600;
else
DST = 2;

return( TimeLocal() + DST + (OffsetHours * 3600) + (TZInfoArray[0] + TZInfoArray[42]) * 60 );
}

//=================================================================================================
//=================================================================================================
//================================= END IMPORTED FUNCTIONS =====================================
//=================================================================================================
//=================================================================================================

相关资源

暂无评论

暂无评论...