last ...
This commit is contained in:
@@ -1267,7 +1267,8 @@ class XCSessionDetector : public XCBase
|
||||
bool isActive = IsTimeInRange(
|
||||
session.startTime,
|
||||
session.endTime,
|
||||
currentTime);
|
||||
currentTime //
|
||||
);
|
||||
|
||||
//
|
||||
// Session Started
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# XCAccount
|
||||
|
||||
it is a module which used for Accoess Trading Account Data in SaherElm's XTrade Framework.
|
||||
|
||||
## Dependencies
|
||||
|
||||
there is no Dependencies on this modules.
|
||||
|
||||
## Instancing
|
||||
|
||||
there is not any args in Constructor.
|
||||
|
||||
## Instructions
|
||||
|
||||
use provided Actions for Access Account informations in where you need.
|
||||
|
||||
## Actions
|
||||
|
||||
### long GetUserAccount()
|
||||
|
||||
retrieve Current User Identifier.
|
||||
|
||||
### long GetLeverage()
|
||||
|
||||
retrieve Current Account Leverage.
|
||||
|
||||
### bool CanExpertTrade()
|
||||
|
||||
retrieve Flag which Specified Trading using Experts or Manual.
|
||||
|
||||
### ENUM_ACCOUNT_TRADE_MODE GetTradeMode()
|
||||
|
||||
retrieve Current Account Trading Mode.
|
||||
|
||||
- ACCOUNT_TRADE_MODE_DEMO;
|
||||
- ACCOUNT_TRADE_MODE_CONTEST;
|
||||
- ACCOUNT_TRADE_MODE_REAL;
|
||||
|
||||
### double GetBalance()
|
||||
|
||||
retrieve Current Account Balance.
|
||||
|
||||
### double GetCredit()
|
||||
|
||||
retrieve Current Account Credit.
|
||||
|
||||
### double GetProfit()
|
||||
|
||||
retrieve Current Account's Amount of Profits.
|
||||
|
||||
### double GetEquity()
|
||||
|
||||
retrieve Current Account's Amount of Equity.
|
||||
|
||||
### double GetMargin()
|
||||
|
||||
retrieve Current Account's Amount of Margin.
|
||||
|
||||
### double GetFreeMargin()
|
||||
|
||||
retrieve Current Account's Amount of Free Margin.
|
||||
|
||||
### double GetMarginLevel()
|
||||
|
||||
retrieve Current Account's Margin Level.
|
||||
|
||||
### double GetMarginCall()
|
||||
|
||||
retrieve Current Account's Margin Call Level.
|
||||
|
||||
### double GetMarginStopOut()
|
||||
|
||||
retrieve Current Account's Margin Stopout Level.
|
||||
|
||||
### string GetName()
|
||||
|
||||
retrieve Current Account's User Name.
|
||||
|
||||
### string GetServerName()
|
||||
|
||||
retrieve Current Account's Server Name.
|
||||
|
||||
### string GetCurrency()
|
||||
|
||||
retrieve Current Account's Currency.
|
||||
|
||||
### string GetCompany()
|
||||
|
||||
retrieve Current Account's Market Name.
|
||||
|
||||
### double CalculateTradeProfit(const string symbol, ENUM_ORDER_TYPE type, double volume, double entry, double exit)
|
||||
|
||||
Calculate and Retrieved Trade Profit based on Account Currency.
|
||||
|
||||
### double CalculateMarging(const string symbol, ENUM_ORDER_TYPE type, double volume, double entry)
|
||||
|
||||
Calculate and Retrieved Specified Trades used Marigin.
|
||||
|
||||
### double CalculateFreeMarging(const string symbol, ENUM_ORDER_TYPE type, double volume, double entry)
|
||||
|
||||
Calculate and Retrieved Specified Trades Free Marigin.
|
||||
|
||||
### double CalculateMaxVolume(const string symbol, ENUM_ORDER_TYPE type, double entry, double percent = 100)
|
||||
|
||||
Calculate Max Allowed Specified Trade's Volume for Entry.
|
||||
|
||||
### double GetPointValue(string symbol)
|
||||
|
||||
Retrieve Specified Symbol's Point Value.
|
||||
|
||||
### double CalculateRiskAmount(string symbol, double points, double volume)
|
||||
|
||||
Calculate How many Risk Currency in a Trade when risk Points and Trade Volume is Specified.
|
||||
used when we have to know how many risk in $ Currency, for a Trade when we risk 100 Point, by Providing 0.01 Lot as Volume.
|
||||
|
||||
### double CalculateRiskPoints(string symbol, double volume, double amount)
|
||||
|
||||
Calculate Risk Points in a Trade when Volume and Curreny Amount is Specified.
|
||||
used when we want to know How many points risk in a Trade when we have to risk 5$ in a Trade by 0.01 Lot as Volume.
|
||||
|
||||
### double CalculateVolume(string symbol, double amount, double points)
|
||||
|
||||
Calculate Trade Volume when Risk Amount in Currency and Risk Amount in Points Specified.
|
||||
used when we want to know Open a Trade by how Size of Volume when we have to risk 5$ in a Trade when 100 points risking.
|
||||
|
||||
### string GetBalanceReportMessage()
|
||||
|
||||
Prepare and Retrieve Balance Report Message for Current Account.
|
||||
|
||||
## Maintainer
|
||||
|
||||
Hadi Khazaee asl
|
||||
|
||||
[https://www.saherelm.ir](https://www.saherelm.ir)
|
||||
|
||||
[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com)
|
||||
@@ -0,0 +1,143 @@
|
||||
# XCAlert
|
||||
|
||||
it is a module which used for Providing Logs in SaherElm's XTrade Framework.
|
||||
|
||||
## Dependencies
|
||||
|
||||
this Module used XCDataCollector for Saving Logs in Files.
|
||||
|
||||
## Instancing
|
||||
|
||||
using this syntax for instancing :
|
||||
|
||||
```mql5
|
||||
XCAlert(
|
||||
string prefix = "X-Alert", // Alerts Prefixe
|
||||
bool enableAlerts = true, // Enable Alerts
|
||||
bool logAlerts = true, // Log Alerts
|
||||
bool terminalAlerts = false, // Terminal Alerts
|
||||
bool mailAlerts = false, // Mail Alerts
|
||||
bool pushAlerts = false, // Push Alerts
|
||||
bool saveAlerts = false // Save Alerts
|
||||
) {}
|
||||
```
|
||||
|
||||
all of Constructor Input args is Optional, means have default values.
|
||||
you can set them after instancing using Properties Setter/Getter (s).
|
||||
|
||||
## Instructions
|
||||
|
||||
- Prefix: string, Specified Logger Prefix. this for Separate Different Logger Instances.
|
||||
- EnableAlerts: boolean, Specified Logging is Enabled or not.
|
||||
- SaveAlerts: boolean, Specified Saving Log(s) or not.
|
||||
- LogAlerts: boolean, Specified Can Print Logs on Console or not.
|
||||
- TerminalAlerts: boolean, Specified Send Logs in Terminal or not.
|
||||
- MailAlerts: boolean, Specified Send Logs as Mail or not.
|
||||
- PushAlerts: boolean, Specified Send Logs as Push or not.
|
||||
|
||||
## Actions
|
||||
|
||||
### LogAlert(string message)
|
||||
|
||||
do specified type of Logs.
|
||||
do log by Print in Console.
|
||||
|
||||
### TerminalAlert(string message)
|
||||
|
||||
do specified type of Logs.
|
||||
do log by sending Terminal Alert.
|
||||
|
||||
### MailAlert(string message)
|
||||
|
||||
do specified type of Logs.
|
||||
do log by sending Mail Alert.
|
||||
|
||||
### PushAlert(string message)
|
||||
|
||||
do specified type of Logs.
|
||||
do log by sending Push Notification Alert.
|
||||
|
||||
### SendAlert(string message)
|
||||
|
||||
do sending log by configuration.
|
||||
retrieve log and based on configuration send each specified enabled logs separately.
|
||||
|
||||
### Log(string message)
|
||||
|
||||
Checking Log Flag and Print Message.
|
||||
|
||||
### Log(string tag, string message)
|
||||
|
||||
Checking Log Flag and Print Specified Tag & Message.
|
||||
|
||||
### LogEmpty()
|
||||
|
||||
Checking Log Flag and Print Empty Message.
|
||||
|
||||
### LogArray(T &array[], string label = NULL)
|
||||
|
||||
Checking Log Flag and Print Array.
|
||||
|
||||
### LogError(int error = -1)
|
||||
|
||||
Checking Log Flag and then Generate Error Message based on provided error number,
|
||||
then Print Tag Specified Message.
|
||||
|
||||
### string GetErrorDescription(int err_code)
|
||||
|
||||
Generate Specified Error Messaged based on Provided error code.
|
||||
|
||||
### string GetTradeServerReturnCodeDescription(int return_code)
|
||||
|
||||
Generate Specified Error Message based on Provided server error code.
|
||||
|
||||
### bool CanLog()
|
||||
|
||||
protected
|
||||
return a Flag for Specified Flag which allowed Print Logs or not.
|
||||
|
||||
### bool CanLogAlert()
|
||||
|
||||
protected
|
||||
return a Flag for Specified Flag which allowed Print Logs and also Enabled or not.
|
||||
|
||||
### bool CanTerminalAlert()
|
||||
|
||||
protected
|
||||
return a Flag for Specified Flag which allowed Sending Logs to Terminal and also Enabled or not.
|
||||
|
||||
### bool CanMailAlert()
|
||||
|
||||
protected
|
||||
return a Flag for Specified Flag which allowed Sending Logs as Mail and also Enabled or not.
|
||||
|
||||
### bool CanPushAlert()
|
||||
|
||||
protected
|
||||
return a Flag for Speified Flag which allowed Sending Logs as Push and also Enabled or not.
|
||||
|
||||
### Save(string message)
|
||||
|
||||
protected
|
||||
Saving Specified Message into Logger File. if alert Saving is Enabled.
|
||||
|
||||
#### Descriptions
|
||||
|
||||
- Preparing a File Name by Combining setted Prefix + ".XLogs".
|
||||
- Generating File Path based on XCDataCollector instance Path.
|
||||
- Validate Generated filePath.
|
||||
- Preparing Log Message for Saving in Log File by attaching Current Time.
|
||||
- Append prepared Message to Log File using XCDataCollector instance.
|
||||
|
||||
### string PrepareMessage(string message)
|
||||
|
||||
private
|
||||
Replace Escaped strings and Attach Prefix to Message.
|
||||
|
||||
## Maintainer
|
||||
|
||||
Hadi Khazaee asl
|
||||
|
||||
[https://www.saherelm.ir](https://www.saherelm.ir)
|
||||
|
||||
[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com)
|
||||
@@ -0,0 +1,80 @@
|
||||
# XCDataCollector
|
||||
|
||||
it is a module which used for Save and Retrieve Data in SaherElm's XTrade Framework.
|
||||
this tool helps all required modules to save data for next usage and analytics.
|
||||
we can implement Save To File and Read From File actions to all Structures and Classes and Modules.
|
||||
|
||||
## Dependencies
|
||||
|
||||
there is no Dependencies on this modules.
|
||||
|
||||
## Instancing
|
||||
|
||||
there is not any args in Constructor.
|
||||
|
||||
## Instructions
|
||||
|
||||
- using Path Action of instance for Setting File Path;
|
||||
- using Save, Append, Read for File Manipulations;
|
||||
|
||||
## Actions
|
||||
|
||||
### Path
|
||||
|
||||
is a Property Setter and Getter Action for Setting Collector Path.
|
||||
for setter it's have an input Parameters (string value) which Specified the File's Path.
|
||||
|
||||
### GetFilePath(string fileName)
|
||||
|
||||
retrieve Full path (including provided file name) based on Current Configured Path for instance.
|
||||
|
||||
### GetFileHandlerForWrite(string filePath)
|
||||
|
||||
retrieve File Handler by Write Access for Writing Data to File.
|
||||
you have to provide Fully File Path for access Handler.
|
||||
this for Use Implementing Custom Collecting Senarios out of Instance Box.
|
||||
commonly it's used by built in Actions for Regular Saving Data.
|
||||
|
||||
### GetFileHandlerForRead(string filePath)
|
||||
|
||||
retrieve File Handler by Read Access for Reading Data from File.
|
||||
you have to provide Fully File Path for access Handler.
|
||||
this for Use Implementing Custom Retrieving Senarios out of Instance Box.
|
||||
commonly it's used by built in Actions for Regular Reading Data.
|
||||
|
||||
### Save(string fileName, string content)
|
||||
|
||||
Save Specified Content to Specified File Nmae.
|
||||
here the file name must Contains fully file path including file name.
|
||||
it's only one time writing file. if it's file exists before overwrite it's content.
|
||||
|
||||
### Append(string fileName, string content)
|
||||
|
||||
Append Specified Contonet to end of Specified File.
|
||||
here the file name must Contains fully file path including file name.
|
||||
if file Exists, add to end of it. if not, Create file at Specified Provided path, then write content.
|
||||
|
||||
### Read(string fileName, string &content)
|
||||
|
||||
Read all of File Content at once from Specified File.
|
||||
here the file name must Contains fully file path including file name.
|
||||
content arg is a reference string variable to hold file content.
|
||||
|
||||
### Read(string fileName, string &content[])
|
||||
|
||||
Read all of File Content Lines at once from Specified File.
|
||||
here the file name must Contains fully file path including file name.
|
||||
content arg is a reference string array variable to hold file content lines.
|
||||
|
||||
### IsExists(string fileName)
|
||||
|
||||
Check Specified File Exists or not ...
|
||||
here the file name must Contains fully file path including file name.
|
||||
|
||||
## Maintainer
|
||||
|
||||
Hadi Khazaee asl
|
||||
|
||||
[https://www.saherelm.ir](https://www.saherelm.ir)
|
||||
|
||||
[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com)
|
||||
@@ -0,0 +1,11 @@
|
||||
# XCTrade
|
||||
|
||||
it is a module which used for Providing Trade Related Actions in SaherElm's XTrade Framework.
|
||||
|
||||
## Maintainer
|
||||
|
||||
Hadi Khazaee asl
|
||||
|
||||
[https://www.saherelm.ir](https://www.saherelm.ir)
|
||||
|
||||
[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com)
|
||||
@@ -0,0 +1,25 @@
|
||||
# XCVolume
|
||||
|
||||
it is a module which used for Accoess Trading Account Data in SaherElm's XTrade Framework.
|
||||
|
||||
## Dependencies
|
||||
|
||||
there is no Dependencies on this modules.
|
||||
|
||||
## Instancing
|
||||
|
||||
there is not any args in Constructor.
|
||||
|
||||
## Instructions
|
||||
|
||||
use provided Actions for Access Account informations in where you need.
|
||||
|
||||
## Actions
|
||||
|
||||
## Maintainer
|
||||
|
||||
Hadi Khazaee asl
|
||||
|
||||
[https://www.saherelm.ir](https://www.saherelm.ir)
|
||||
|
||||
[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com)
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* XMQL Helper CLI Tools ...
|
||||
* a module for handle MQL5 Commands running ...
|
||||
*
|
||||
* Maintainer
|
||||
*
|
||||
* Hadi Khazaee Asl <https://saherelm.ir> (hadi_khazaee_asl@yahoo.com)
|
||||
*/
|
||||
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// Read Command Arguments ...
|
||||
let cmdName = process.argv[2];
|
||||
|
||||
//
|
||||
XProjectTools.runCommand(cmdName);
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "X121XKIEA";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"X121XKIEA/*", // X121XKIEA Projects ...
|
||||
"Indicators/x-saherelm.x121.xki", // Required Indicators
|
||||
"Helpers/x-saherelm.x121.xki.helper", // Indicator's Helpers Classes
|
||||
"Experts/x-saherelm.x121.xki.backtester.ea", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(projectName, projectResources);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "XCATBEA";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"XCATBEA/*", // XCATBEA Projects ...
|
||||
"Indicators/x-saherelm.x121.xcatb", // Required Indicators
|
||||
"Helpers/x-saherelm.x121.xcatb.helper", // Indicator's Helpers Classes
|
||||
"Experts/x-saherelm.x121.xcatb.ea", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(
|
||||
projectName,
|
||||
projectResources
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "XFIMAEA";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"XFIMAEA/*", // XOM1EA Projects ...
|
||||
"Indicators/x-saherelm.xfima", // Required Indicators
|
||||
"Indicators/x-saherelm.x121.xct", // Required Indicators
|
||||
"Helpers/x-saherelm.xfima.helper", // Indicator's Helpers Classes
|
||||
"Experts/x-saherelm.xfima.backtester.ea", // Expert Advisers
|
||||
"Experts/x-saherelm.xfima.ea", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(projectName, projectResources);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "XOM1EA";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"XOM1EA/*", // XOM1EA Projects ...
|
||||
"Indicators/x-saherelm.xom1", // Required Indicators
|
||||
"Helpers/x-saherelm.xom1.helper", // Indicator's Helpers Classes
|
||||
"Experts/x-saherelm.xom1.ea", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(projectName, projectResources);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "XRRTools";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"Indicators/x-saherelm.x121.xcc", // Required Indicators
|
||||
"Experts/x-saherelm.rr.tools", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(projectName, projectResources);
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
/**
|
||||
* XProject Tools Module ...
|
||||
* a module for handle MQL5 Projects Compile and Packing ...
|
||||
*
|
||||
* Maintainer
|
||||
*
|
||||
* Hadi Khazaee Asl <https://saherelm.ir> (hadi_khazaee_asl@yahoo.com)
|
||||
*/
|
||||
|
||||
//
|
||||
// Import Requirements ...
|
||||
const execSync = require("child_process").execSync;
|
||||
const XFileTools = require("./Documents/JsModules/x-file.tools");
|
||||
const XShellTools = require("./Documents/JsModules/x-shell.tools");
|
||||
const XColorTools = require("./Documents/JsModules/x-color.tools");
|
||||
const XTypeTools = require("./Documents/JsModules/x-type-detector.tools");
|
||||
|
||||
//
|
||||
// Definitions ...
|
||||
const packsPath = XFileTools.joinPath(".", "Packs");
|
||||
const workspacePaths = [
|
||||
"Classes", // Required Classes
|
||||
"Experts", // Expert Advisers
|
||||
"Helpers", // Indicator's Helpers Classes
|
||||
"Indicators", // Required Indicators
|
||||
"Libraries", // Required Libraries
|
||||
"XOM1EA", // XOM1EA Projects ...
|
||||
"XFIMAEA", // XOM1EA Projects ...
|
||||
"XCATBEA", // XCATBEA Projects ...
|
||||
"X121XKIEA", // X121XKIEA Projects ...
|
||||
];
|
||||
|
||||
//
|
||||
//#region Tools ...
|
||||
|
||||
/**
|
||||
* Handle Compile and Pack Projects ...
|
||||
*
|
||||
* @param {string} projectName
|
||||
* @param {string[]} resources
|
||||
* @param {boolean} forceCleanup
|
||||
* @param {boolean} forceCompileIndicators
|
||||
*/
|
||||
async function prepareProject(
|
||||
projectName = "",
|
||||
resources = [],
|
||||
forceCleanup = true,
|
||||
forceCompileIndicators = true
|
||||
) {
|
||||
//
|
||||
// Check Resources Has Child ...
|
||||
let isValidArgs =
|
||||
resources.length > 0 &&
|
||||
XTypeTools.isArray(resources);
|
||||
if (!isValidArgs) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Invalid Resource List ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Check Project Name is Valid ...
|
||||
isValidArgs =
|
||||
projectName.length > 0 &&
|
||||
XTypeTools.isString(projectName);
|
||||
if (!isValidArgs) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Empty Project Name ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Prepare Pack Folder Exists ...
|
||||
let isExists = XFileTools.isDirectoryExists(packsPath);
|
||||
if (!isExists) {
|
||||
XFileTools.createDirectory(packsPath);
|
||||
}
|
||||
|
||||
//
|
||||
// Check Folder Exists or not ...
|
||||
// if Exists Remove it ...
|
||||
// Create Project Folder on Packs ...
|
||||
const prjFolderOnPacks = XFileTools.joinPath(packsPath, projectName);
|
||||
isExists = XFileTools.isDirectoryExists(prjFolderOnPacks);
|
||||
if (isExists) {
|
||||
await XFileTools.removeDirectory(prjFolderOnPacks, true);
|
||||
}
|
||||
XFileTools.createDirectory(prjFolderOnPacks);
|
||||
|
||||
//
|
||||
// Extract Required to Compile Resources List ...
|
||||
let compileList = await extractResources(resources, true);
|
||||
if (!compileList || compileList.length == 0) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Resource List Extraction to Compile Failed ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Compile Resources Files ...
|
||||
for (const f of compileList) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("try to Compile: ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(f, XColorTools.COLOR_NAMES.Blue)
|
||||
);
|
||||
|
||||
//
|
||||
await recursiveTriesToCompileFile(f, 0);
|
||||
}
|
||||
|
||||
//
|
||||
// Extract Required to Pack Resources List ...
|
||||
let packList = await extractResources(resources, false);
|
||||
if (!packList || packList.length == 0) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Resource List Extraction to Pack Failed ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Loop through Files ...
|
||||
for (const f of packList) {
|
||||
//
|
||||
// Prepare Destination Path ...
|
||||
const fBasePath = XFileTools.basename(f);
|
||||
let fDestPath = f.replace(fBasePath, "");
|
||||
fDestPath = fDestPath.substring(0, fDestPath.length - 1);
|
||||
fDestPath = XFileTools.joinPath(prjFolderOnPacks, fDestPath);
|
||||
|
||||
//
|
||||
const isFDestPathExists = XFileTools.isDirectoryExists(fDestPath);
|
||||
if (!isFDestPathExists) {
|
||||
XFileTools.createDirectory(fDestPath);
|
||||
}
|
||||
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("try to copy: ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(f, XColorTools.COLOR_NAMES.Blue),
|
||||
XColorTools.applyForegroundColor(" to : ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(fDestPath, XColorTools.COLOR_NAMES.Magenta)
|
||||
);
|
||||
|
||||
//
|
||||
let result = await XFileTools.copyFile(f, fDestPath);
|
||||
if (result) {
|
||||
//
|
||||
const msg = "copy successfully ...";
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Green)
|
||||
);
|
||||
} else {
|
||||
//
|
||||
const msg = "copy failed ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Project: ", XColorTools.COLOR_NAMES.Green),
|
||||
XColorTools.applyForegroundColor(projectName, XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(" Compiled and Packed Successfully ...", XColorTools.COLOR_NAMES.Green),
|
||||
);
|
||||
|
||||
//
|
||||
// Cleanup WorkSpace if Provided ...
|
||||
if (forceCleanup) {
|
||||
await cleanupWorkSpace();
|
||||
}
|
||||
|
||||
//
|
||||
// Compile Indicators ...
|
||||
if (forceCompileIndicators) {
|
||||
await runCommand("Indicators");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract required Files List ...
|
||||
*
|
||||
* @param {boolean} forCompile
|
||||
*
|
||||
* @returns Promise<string[]>
|
||||
*/
|
||||
async function extractResources(
|
||||
resources = [],
|
||||
forCompile = true
|
||||
) {
|
||||
//
|
||||
let result = [];
|
||||
|
||||
//
|
||||
// Loop Through Project Resources and Extract Files List to Compile ...
|
||||
for (const resource of resources) {
|
||||
//
|
||||
// Prepare Normalized Resource Path ...
|
||||
let resourcePath = XFileTools.joinPath(".", resource);
|
||||
|
||||
//
|
||||
// Handle Wild Card Resources ...
|
||||
if (resourcePath.endsWith("//*") ||
|
||||
resourcePath.endsWith("\\*")) {
|
||||
//
|
||||
resourcePath = resourcePath.substring(0, resourcePath.length - 2);
|
||||
const files = await XFileTools.extractFiles(
|
||||
resourcePath,
|
||||
forCompile ? [".mq5", ".mq4"] : [".ex5", ".ex4"]
|
||||
);
|
||||
|
||||
//
|
||||
result = result.concat(files);
|
||||
}
|
||||
//
|
||||
// Handle Direct File Resources ...
|
||||
else {
|
||||
//
|
||||
// Check MQL 5 Version ...
|
||||
let fileResourcePath = resourcePath + (forCompile ? ".mq5" : ".ex5");
|
||||
|
||||
//
|
||||
// Check File Exists ...
|
||||
let isExists = XFileTools.isFileExists(fileResourcePath);
|
||||
if (!isExists) {
|
||||
//
|
||||
// Check MQL 4 Version ...
|
||||
fileResourcePath = resourcePath + (forCompile ? ".mq4" : ".ex4");
|
||||
isExists = XFileTools.isFileExists(fileResourcePath);
|
||||
}
|
||||
|
||||
//
|
||||
// Add File Path to Files List if Exists ...
|
||||
if (isExists) {
|
||||
result.push(fileResourcePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to compile a file ...
|
||||
* @param {string} f
|
||||
* @param {number} numberOfTries
|
||||
*/
|
||||
async function recursiveTriesToCompileFile(f = "", numberOfTries = 0) {
|
||||
//
|
||||
let result = await handleFileCompile(f);
|
||||
if (result) {
|
||||
//
|
||||
const msg = "compiled successfully ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Green));
|
||||
return result;
|
||||
} else {
|
||||
//
|
||||
if (numberOfTries < 10) {
|
||||
//
|
||||
numberOfTries++;
|
||||
|
||||
//
|
||||
const msg = "retry (" + (numberOfTries + 1) + ") ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.LightMagenta));
|
||||
await recursiveTriesToCompileFile(f, numberOfTries);
|
||||
} else {
|
||||
//
|
||||
const msg = "failed ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
|
||||
//
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* execute compile task on Specific File ...
|
||||
* @param {string} f file path to Execute Compile task ...
|
||||
* @returns
|
||||
*/
|
||||
async function handleFileCompile(f = "") {
|
||||
//
|
||||
let result = false;
|
||||
|
||||
//
|
||||
let exResult = await compileMQLFile(f);
|
||||
if (exResult.includes("0 errors")) {
|
||||
result = true;
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Specific MQL File ...
|
||||
*
|
||||
* @param {string} path MQL file path ...
|
||||
* @returns
|
||||
*/
|
||||
async function compileMQLFile(path = "") {
|
||||
//
|
||||
// Check File Exists ...
|
||||
const isExists = XFileTools.isFileExists(path);
|
||||
if (!isExists) {
|
||||
//
|
||||
const msg = "file not found ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
}
|
||||
|
||||
//
|
||||
const ext = XFileTools.getFileExtension(path);
|
||||
if (ext !== ".mq5" && ext !== "mq4") {
|
||||
//
|
||||
const msg = "invalid file extension ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
}
|
||||
|
||||
//
|
||||
const cwd = __dirname;
|
||||
const cmd = "compile.cmd " + path;
|
||||
|
||||
//
|
||||
const result = await XShellTools.execute(cmd, cwd);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Specified Command in workspace ...
|
||||
*
|
||||
* @param {string} cmd
|
||||
* @returns
|
||||
*/
|
||||
async function runCommand(cmd = "") {
|
||||
//
|
||||
// Validate Args ...
|
||||
let isValidArgs = cmd.length > 0 &&
|
||||
XTypeTools.isString(cmd);
|
||||
|
||||
//
|
||||
// Validate Commands ...
|
||||
if (isValidArgs) {
|
||||
//
|
||||
// Check Available Commands ...
|
||||
isValidArgs =
|
||||
cmd === "*" ||
|
||||
cmd === "cleanup" ||
|
||||
workspacePaths.includes(cmd) ||
|
||||
workspacePaths.map(x => x.toLowerCase(x)).includes(cmd.toLowerCase());
|
||||
}
|
||||
if (!isValidArgs) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Invalid Command ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Compile ...
|
||||
if (cmd == "*") {
|
||||
await compileWorkSpace();
|
||||
} else if (cmd == "cleanup") {
|
||||
await cleanupWorkSpace();
|
||||
} else {
|
||||
//
|
||||
// Select Workspace to Compile ...
|
||||
const folder = workspacePaths
|
||||
.find(x => x.toLowerCase() === cmd.toLowerCase());
|
||||
isValidArgs =
|
||||
XTypeTools.isString(folder) &&
|
||||
folder.length > 0;
|
||||
if (!isValidArgs) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Invalid Command ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Extract Required to Compile Resources List ...
|
||||
let compileList = await XFileTools.extractFiles(
|
||||
folder,
|
||||
[".mq5", ".mq4"]
|
||||
);
|
||||
if (!compileList || compileList.length == 0) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Extraction to Compile Failed ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Compile Resources Files ...
|
||||
for (const f of compileList) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("try to Compile: ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(f, XColorTools.COLOR_NAMES.Blue)
|
||||
);
|
||||
|
||||
//
|
||||
await recursiveTriesToCompileFile(f, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Workspace compiled files ...
|
||||
*/
|
||||
async function cleanupWorkSpace() {
|
||||
//
|
||||
for (const path of workspacePaths) {
|
||||
//
|
||||
const fSourcePath = XFileTools.joinPath(".", path);
|
||||
const filesToRemove = await XFileTools.extractFiles(fSourcePath, [".ex5", ".ex4"]);
|
||||
for (const f of filesToRemove) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("try to Remove: ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(f, XColorTools.COLOR_NAMES.Blue)
|
||||
);
|
||||
|
||||
//
|
||||
let result = await XFileTools.removeFile(f);
|
||||
if (result) {
|
||||
//
|
||||
const msg = "removed successfully ...";
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Green)
|
||||
);
|
||||
} else {
|
||||
//
|
||||
const msg = "removed failed ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Workspace ...
|
||||
*/
|
||||
async function compileWorkSpace() {
|
||||
//
|
||||
for (const path of workspacePaths) {
|
||||
await runCommand(path);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//
|
||||
//#region Module Exports ...
|
||||
module.exports = {
|
||||
extractResources,
|
||||
recursiveTriesToCompileFile,
|
||||
handleFileCompile,
|
||||
compileMQLFile,
|
||||
runCommand,
|
||||
cleanupWorkSpace,
|
||||
compileWorkSpace,
|
||||
prepareProject
|
||||
};
|
||||
//#endregion
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* XMQL Document Helper CLI Tools ...
|
||||
* a module for handle MQL5 Documents Generating ...
|
||||
*
|
||||
* Maintainer
|
||||
*
|
||||
* Hadi Khazaee Asl <https://saherelm.ir> (hadi_khazaee_asl@yahoo.com)
|
||||
*/
|
||||
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XColorTools = require("./x-color.tools");
|
||||
const XDocumentTools = require("./x-mql-document.tools");
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 1) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(
|
||||
"Invalid Usage Command ...",
|
||||
XColorTools.COLOR_NAMES.Red
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(
|
||||
"Usage: node mql5-doc-extractor.js <path_to_mql5_file> [output_md_file]",
|
||||
XColorTools.COLOR_NAMES.Yellow
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(
|
||||
"Example: node mqlDocumentHelper.js ./MyEA.mq5 ./docs/MyEA.md",
|
||||
XColorTools.COLOR_NAMES.LightBlue
|
||||
)
|
||||
);
|
||||
|
||||
//
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const inputFile = args[0];
|
||||
const outputFile = args[1] || inputFile.replace(/\.(mq5|mqh)$/i, '_DOCS.md');
|
||||
|
||||
XDocumentTools.extractMQL5Documentation(inputFile, outputFile);
|
||||
@@ -0,0 +1,183 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
//
|
||||
//#region Tools ...
|
||||
/**
|
||||
* Extracts Doxygen-style documentation from MQL5 files and generates Markdown.
|
||||
* @param {string} filePath - Path to the .mq5 or .mqh file
|
||||
* @param {string} outputPath - Path to save the generated .md file
|
||||
*/
|
||||
function extractMQL5Documentation(filePath, outputPath) {
|
||||
//
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`Error: File not found at ${filePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
const extractedDocs = [];
|
||||
|
||||
//
|
||||
let currentCommentBlock = [];
|
||||
let isInsideBlockComment = false;
|
||||
|
||||
//
|
||||
// State machine to parse comments and map them to the next line of code
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
//
|
||||
const line = lines[i];
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
//
|
||||
// 1. Handle Block Comments (/* ... */)
|
||||
if (trimmedLine.startsWith('/*') || trimmedLine.startsWith('/**')) {
|
||||
//
|
||||
isInsideBlockComment = true;
|
||||
currentCommentBlock.push(trimmedLine);
|
||||
if (trimmedLine.endsWith('*/')) {
|
||||
isInsideBlockComment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
if (isInsideBlockComment) {
|
||||
//
|
||||
currentCommentBlock.push(trimmedLine);
|
||||
if (trimmedLine.endsWith('*/')) {
|
||||
isInsideBlockComment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// 2. Handle Line Comments (/// or //)
|
||||
if (trimmedLine.startsWith('///') || trimmedLine.startsWith('//')) {
|
||||
//
|
||||
currentCommentBlock.push(trimmedLine);
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// 3. Handle Empty Lines (Keep them to preserve spacing before code)
|
||||
if (trimmedLine === '') {
|
||||
//
|
||||
if (currentCommentBlock.length > 0) {
|
||||
currentCommentBlock.push('');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// 4. We hit actual code. If we have a comment block, map it.
|
||||
if (currentCommentBlock.length > 0) {
|
||||
//
|
||||
// Clean up trailing empty lines in the comment block
|
||||
while (currentCommentBlock.length > 0 &&
|
||||
currentCommentBlock[currentCommentBlock.length - 1].trim() === ''
|
||||
) {
|
||||
currentCommentBlock.pop();
|
||||
}
|
||||
|
||||
//
|
||||
if (currentCommentBlock.length > 0) {
|
||||
//
|
||||
extractedDocs.push({
|
||||
comment: currentCommentBlock.join('\n'),
|
||||
code: trimmedLine
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
currentCommentBlock = [];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Generate Markdown Output
|
||||
generateMarkdown(filePath, extractedDocs, outputPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the extracted data into a clean Markdown document.
|
||||
*/
|
||||
function generateMarkdown(filePath, docs, outputPath) {
|
||||
//
|
||||
const fileName = path.basename(filePath);
|
||||
let md = `# Documentation for \`${fileName}\`\n\n`;
|
||||
md += `*Extracted on: ${new Date().toLocaleDateString()}*\n\n---\n\n`;
|
||||
|
||||
//
|
||||
let fileHeaderFound = false;
|
||||
|
||||
//
|
||||
docs.forEach((doc, index) => {
|
||||
//
|
||||
// Check if this is the file-level header (usually the very first block)
|
||||
if (index === 0 &&
|
||||
(doc.code.startsWith('#property') || doc.code.startsWith('//+--'))) {
|
||||
//
|
||||
md += `## File Overview\n`;
|
||||
md += formatComment(doc.comment) + '\n\n';
|
||||
fileHeaderFound = true;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Only document actual functions, classes, structs, or important variables
|
||||
const isDocumentableCode = /^(void|int|double|bool|string|long|ulong|uint|class|struct|enum|input)\s+/.test(doc.code)
|
||||
|| doc.code.includes('class ')
|
||||
|| doc.code.includes('struct ');
|
||||
|
||||
if (isDocumentableCode) {
|
||||
//
|
||||
md += `### \`${extractSignature(doc.code)}\`\n\n`;
|
||||
md += `**Declaration:**\n\`\`\`cpp\n${doc.code}\n\`\`\`\n\n`;
|
||||
md += `**Documentation:**\n${formatComment(doc.comment)}\n\n---\n\n`;
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
fs.writeFileSync(outputPath, md, 'utf8');
|
||||
console.log(`✅ Successfully extracted documentation to: ${outputPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up Doxygen tags for better Markdown readability.
|
||||
*/
|
||||
function formatComment(comment) {
|
||||
return comment
|
||||
.replace(/\/\*\*?|\*\//g, '') // Remove block comment markers
|
||||
.replace(/^\s*\*\s?/gm, '') // Remove leading asterisks
|
||||
.replace(/^\/\/\/?\s?/gm, '') // Remove line comment markers
|
||||
.replace(/@param\s+(\w+)\s*/g, '**Param `$1`:** ')
|
||||
.replace(/@return\s*/g, '**Returns:** ')
|
||||
.replace(/@brief\s*/g, '**Summary:** ')
|
||||
.replace(/@note\s*/g, '*Note:* ')
|
||||
.replace(/@warning\s*/g, '> **Warning:** ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a clean function/class signature from the code line.
|
||||
*/
|
||||
function extractSignature(codeLine) {
|
||||
//
|
||||
// Basic extraction, stops at the first opening parenthesis or brace
|
||||
const match = codeLine.match(/^(.*?)[\({]/);
|
||||
return match ? match[1].trim() : codeLine;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//
|
||||
//#region Module Exports ...
|
||||
module.exports = {
|
||||
formatComment,
|
||||
generateMarkdown,
|
||||
extractSignature,
|
||||
extractMQL5Documentation,
|
||||
}
|
||||
//#endregion
|
||||
@@ -24,7 +24,10 @@ const workspacePaths = [
|
||||
"Helpers", // Indicator's Helpers Classes
|
||||
"Indicators", // Required Indicators
|
||||
"Libraries", // Required Libraries
|
||||
"XOM1EA", // XOM1EA Projects ...
|
||||
"XFIMAEA", // XOM1EA Projects ...
|
||||
"XCATBEA", // XCATBEA Projects ...
|
||||
"X121XKIEA", // X121XKIEA Projects ...
|
||||
];
|
||||
|
||||
//
|
||||
@@ -70,6 +73,24 @@ async function prepareProject(
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Prepare Pack Folder Exists ...
|
||||
let isExists = XFileTools.isDirectoryExists(packsPath);
|
||||
if (!isExists) {
|
||||
XFileTools.createDirectory(packsPath);
|
||||
}
|
||||
|
||||
//
|
||||
// Check Folder Exists or not ...
|
||||
// if Exists Remove it ...
|
||||
// Create Project Folder on Packs ...
|
||||
const prjFolderOnPacks = XFileTools.joinPath(packsPath, projectName);
|
||||
isExists = XFileTools.isDirectoryExists(prjFolderOnPacks);
|
||||
if (isExists) {
|
||||
await XFileTools.removeDirectory(prjFolderOnPacks, true);
|
||||
}
|
||||
XFileTools.createDirectory(prjFolderOnPacks);
|
||||
|
||||
//
|
||||
// Extract Required to Compile Resources List ...
|
||||
let compileList = await extractResources(resources, true);
|
||||
@@ -112,24 +133,6 @@ async function prepareProject(
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Prepare Pack Folder Exists ...
|
||||
let isExists = XFileTools.isDirectoryExists(packsPath);
|
||||
if (!isExists) {
|
||||
XFileTools.createDirectory(packsPath);
|
||||
}
|
||||
|
||||
//
|
||||
// Check Folder Exists or not ...
|
||||
// if Exists Remove it ...
|
||||
// Create Project Folder on Packs ...
|
||||
const prjFolderOnPacks = XFileTools.joinPath(packsPath, projectName);
|
||||
isExists = XFileTools.isDirectoryExists(prjFolderOnPacks);
|
||||
if (isExists) {
|
||||
await XFileTools.removeDirectory(prjFolderOnPacks, true);
|
||||
}
|
||||
XFileTools.createDirectory(prjFolderOnPacks);
|
||||
|
||||
//
|
||||
// Loop through Files ...
|
||||
for (const f of packList) {
|
||||
@@ -188,7 +191,7 @@ async function prepareProject(
|
||||
//
|
||||
// Compile Indicators ...
|
||||
if (forceCompileIndicators) {
|
||||
await compile("Indicators");
|
||||
await runCommand("Indicators");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,5 +493,5 @@ module.exports = {
|
||||
cleanupWorkSpace,
|
||||
compileWorkSpace,
|
||||
prepareProject
|
||||
}
|
||||
};
|
||||
//#endregion
|
||||
@@ -0,0 +1,360 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SmartShadowScanner.mq5 |
|
||||
//| Advanced Shadow Scanner with 3-Step Checklist Filter |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Professional Trader Setup 2026"
|
||||
#property version "3.00"
|
||||
#property description "Scans for large rejection shadows filtered by Sweep, POI, and Session"
|
||||
|
||||
//--- User Inputs
|
||||
input group "=== Base Shadow Settings ==="
|
||||
input double ShadowMultiplier = 2.0; // Shadow to Body Multiplier (e.g., 2.0 means shadow is 2x body)
|
||||
input double MinRangeATR_Multi = 0.5; // Min Candle Range as ATR Multiplier (0.5 = 50% of ATR)
|
||||
input int MinCandleSizePoints = 30; // Min Absolute Candle Size in Points (Filters micro-candles)
|
||||
input ENUM_TIMEFRAMES Timeframe = PERIOD_H4; // Timeframe to Analyze
|
||||
|
||||
input group "=== Filter 1: Liquidity Sweep ==="
|
||||
input bool EnableSweepFilter = true; // Enable Liquidity Sweep Filter
|
||||
input int SwingLookback = 10; // Swing Lookback Period (Left & Right candles)
|
||||
|
||||
input group "=== Filter 2: Point of Interest (POI) ==="
|
||||
input bool EnablePOIFilter = true; // Enable POI Filter
|
||||
input int POI_ProximityPoints = 50; // Allowed Distance to POI in Points
|
||||
input bool CheckPDH_PDL = true; // Check Previous Day High/Low
|
||||
input bool CheckOrderBlock = true; // Check Recent Order Blocks
|
||||
|
||||
input group "=== Filter 3: Trading Session ==="
|
||||
input bool EnableSessionFilter = true; // Enable Session Time Filter
|
||||
input int LondonStartHour = 8; // London Start Hour (Server Time)
|
||||
input int LondonEndHour = 12; // London End Hour (Server Time)
|
||||
input int NYStartHour = 13; // New York Start Hour (Server Time)
|
||||
input int NYEndHour = 18; // New York End Hour (Server Time)
|
||||
|
||||
input group "=== Display & Alert Settings ==="
|
||||
input bool DrawArrows = true; // Draw Arrows on Chart
|
||||
input bool ShowAlerts = true; // Show Popup Alerts
|
||||
input bool SendPush = false; // Send Push Notifications
|
||||
input color BullishArrowColor = clrLime; // Bullish Arrow Color (Lower Shadow)
|
||||
input color BearishArrowColor = clrRed; // Bearish Arrow Color (Upper Shadow)
|
||||
input int ArrowDistancePoints = 10; // Arrow Distance from Shadow Tip (Points)
|
||||
|
||||
//--- Global Variables
|
||||
datetime lastBarTime = 0;
|
||||
int atrHandle = INVALID_HANDLE;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize ATR indicator handle for volatility filtering
|
||||
atrHandle = iATR(_Symbol, Timeframe, 14);
|
||||
if(atrHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Error creating ATR indicator handle!");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
Print("=== Smart Shadow Scanner v3.0 Initialized ===");
|
||||
Print("Sweep Filter: ", EnableSweepFilter ? "Enabled" : "Disabled");
|
||||
Print("POI Filter: ", EnablePOIFilter ? "Enabled" : "Disabled");
|
||||
Print("Session Filter: ", EnableSessionFilter ? "Enabled" : "Disabled");
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Clean up chart objects and release indicator memory
|
||||
ObjectsDeleteAll(0, "ShadowArrow_");
|
||||
IndicatorRelease(atrHandle);
|
||||
ChartRedraw();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Process only on new bar close to avoid redundant calculations
|
||||
datetime currentBarTime = iTime(_Symbol, Timeframe, 0);
|
||||
if(currentBarTime == lastBarTime) return;
|
||||
lastBarTime = currentBarTime;
|
||||
|
||||
// Copy required bars (Need enough history for Swing Lookback)
|
||||
int barsNeeded = SwingLookback * 2 + 10;
|
||||
MqlRates rates[];
|
||||
ArraySetAsSeries(rates, true);
|
||||
if(CopyRates(_Symbol, Timeframe, 0, barsNeeded, rates) < barsNeeded) return;
|
||||
|
||||
// Copy ATR value for the closed candle (index 1)
|
||||
double atrBuffer[];
|
||||
ArraySetAsSeries(atrBuffer, true);
|
||||
if(CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) < 1) return;
|
||||
double currentATR = atrBuffer[0];
|
||||
|
||||
// Extract OHLC data for the target candle (index 1)
|
||||
double openPrice = rates[1].open;
|
||||
double highPrice = rates[1].high;
|
||||
double lowPrice = rates[1].low;
|
||||
double closePrice = rates[1].close;
|
||||
datetime barTime = rates[1].time;
|
||||
|
||||
// Calculate candle metrics
|
||||
double body = MathAbs(openPrice - closePrice);
|
||||
double upperShadow = highPrice - MathMax(openPrice, closePrice);
|
||||
double lowerShadow = MathMin(openPrice, closePrice) - lowPrice;
|
||||
double totalRange = highPrice - lowPrice;
|
||||
|
||||
// ==========================================
|
||||
// Base Filters (Volatility & Size)
|
||||
// ==========================================
|
||||
// Filter 1: Absolute minimum size (ignores dead-market micro candles)
|
||||
if(totalRange < MinCandleSizePoints * _Point) return;
|
||||
|
||||
// Filter 2: Dynamic ATR filter (candle must be at least X% of average range)
|
||||
if(totalRange < (currentATR * MinRangeATR_Multi)) return;
|
||||
|
||||
// Identify shadow type
|
||||
bool isBullishShadow = (lowerShadow >= (ShadowMultiplier * body) && lowerShadow > upperShadow);
|
||||
bool isBearishShadow = (upperShadow >= (ShadowMultiplier * body) && upperShadow > lowerShadow);
|
||||
|
||||
if(!isBullishShadow && !isBearishShadow) return;
|
||||
|
||||
// ==========================================
|
||||
// Advanced 3-Step Checklist
|
||||
// ==========================================
|
||||
bool passedSweep = !EnableSweepFilter || CheckLiquiditySweep(rates, isBullishShadow);
|
||||
bool passedPOI = !EnablePOIFilter || CheckNearPOI(rates, isBullishShadow, barTime);
|
||||
bool passedSession = !EnableSessionFilter || CheckSession(barTime);
|
||||
|
||||
// Trigger final signal only if ALL enabled filters are passed
|
||||
if(passedSweep && passedPOI && passedSession)
|
||||
{
|
||||
ProcessSignal(isBullishShadow, barTime, isBullishShadow ? lowPrice : highPrice);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Filter 1: Check for Liquidity Sweep |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckLiquiditySweep(const MqlRates &rates[], bool isBullish)
|
||||
{
|
||||
int targetIndex = 1; // Target candle is at index 1
|
||||
|
||||
if(isBullish) // For lower shadow, it must have swept a previous Swing Low
|
||||
{
|
||||
for(int i = targetIndex + 2; i < ArraySize(rates) - SwingLookback; i++)
|
||||
{
|
||||
if(IsSwingLow(rates, i, SwingLookback))
|
||||
{
|
||||
double swingLow = rates[i].low;
|
||||
// Sweep condition: Wick pierces the swing low, but body closes above it
|
||||
if(rates[targetIndex].low < swingLow && rates[targetIndex].close > swingLow)
|
||||
{
|
||||
return true; // Valid liquidity sweep detected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // For upper shadow, it must have swept a previous Swing High
|
||||
{
|
||||
for(int i = targetIndex + 2; i < ArraySize(rates) - SwingLookback; i++)
|
||||
{
|
||||
if(IsSwingHigh(rates, i, SwingLookback))
|
||||
{
|
||||
double swingHigh = rates[i].high;
|
||||
// Sweep condition: Wick pierces the swing high, but body closes below it
|
||||
if(rates[targetIndex].high > swingHigh && rates[targetIndex].close < swingHigh)
|
||||
{
|
||||
return true; // Valid liquidity sweep detected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // No valid sweep found
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Identify Swing High |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsSwingHigh(const MqlRates &rates[], int index, int lookback)
|
||||
{
|
||||
if(index - lookback < 0 || index + lookback >= ArraySize(rates)) return false;
|
||||
|
||||
double high = rates[index].high;
|
||||
for(int i = 1; i <= lookback; i++)
|
||||
{
|
||||
if(rates[index - i].high >= high) return false;
|
||||
if(rates[index + i].high >= high) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Identify Swing Low |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsSwingLow(const MqlRates &rates[], int index, int lookback)
|
||||
{
|
||||
if(index - lookback < 0 || index + lookback >= ArraySize(rates)) return false;
|
||||
|
||||
double low = rates[index].low;
|
||||
for(int i = 1; i <= lookback; i++)
|
||||
{
|
||||
if(rates[index - i].low <= low) return false;
|
||||
if(rates[index + i].low <= low) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Filter 2: Check Proximity to Point of Interest (POI) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckNearPOI(const MqlRates &rates[], bool isBullish, datetime barTime)
|
||||
{
|
||||
double currentPrice = isBullish ? rates[1].low : rates[1].high;
|
||||
double proximity = POI_ProximityPoints * _Point;
|
||||
|
||||
// POI Type 1: Previous Day High / Previous Day Low (PDH/PDL)
|
||||
if(CheckPDH_PDL)
|
||||
{
|
||||
MqlRates dayRates[];
|
||||
ArraySetAsSeries(dayRates, true);
|
||||
|
||||
// Fetch previous daily candle data
|
||||
if(CopyRates(_Symbol, PERIOD_D1, 1, 1, dayRates) >= 1)
|
||||
{
|
||||
double pdh = dayRates[0].high;
|
||||
double pdl = dayRates[0].low;
|
||||
|
||||
if(isBullish && MathAbs(currentPrice - pdl) <= proximity) return true; // Near PDL
|
||||
if(!isBullish && MathAbs(currentPrice - pdh) <= proximity) return true; // Near PDH
|
||||
}
|
||||
}
|
||||
|
||||
// POI Type 2: Recent Order Blocks (OB)
|
||||
if(CheckOrderBlock)
|
||||
{
|
||||
// Scan the last 20 candles for Order Blocks
|
||||
for(int i = 2; i < MathMin(20, ArraySize(rates) - 1); i++)
|
||||
{
|
||||
// Bullish OB: Bearish candle followed by a strong bullish displacement
|
||||
if(isBullish && IsBullishOrderBlock(rates, i))
|
||||
{
|
||||
double obLow = rates[i].low;
|
||||
double obHigh = rates[i].high;
|
||||
if(currentPrice >= (obLow - proximity) && currentPrice <= (obHigh + proximity))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bearish OB: Bullish candle followed by a strong bearish displacement
|
||||
if(!isBullish && IsBearishOrderBlock(rates, i))
|
||||
{
|
||||
double obLow = rates[i].low;
|
||||
double obHigh = rates[i].high;
|
||||
if(currentPrice >= (obLow - proximity) && currentPrice <= (obHigh + proximity))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Not near any valid POI
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Identify Bullish Order Block |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsBullishOrderBlock(const MqlRates &rates[], int index)
|
||||
{
|
||||
// Current candle must be bearish
|
||||
if(rates[index].close >= rates[index].open) return false;
|
||||
|
||||
if(index - 1 < 0) return false;
|
||||
|
||||
// Next candle (index-1) must be a strong bullish displacement
|
||||
double nextBody = MathAbs(rates[index-1].close - rates[index-1].open);
|
||||
double nextRange = rates[index-1].high - rates[index-1].low;
|
||||
|
||||
if(rates[index-1].close <= rates[index-1].open) return false; // Must be bullish
|
||||
if(nextBody < (nextRange * 0.6)) return false; // Body must be at least 60% of total range
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Identify Bearish Order Block |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsBearishOrderBlock(const MqlRates &rates[], int index)
|
||||
{
|
||||
// Current candle must be bullish
|
||||
if(rates[index].close <= rates[index].open) return false;
|
||||
|
||||
if(index - 1 < 0) return false;
|
||||
|
||||
// Next candle (index-1) must be a strong bearish displacement
|
||||
double nextBody = MathAbs(rates[index-1].close - rates[index-1].open);
|
||||
double nextRange = rates[index-1].high - rates[index-1].low;
|
||||
|
||||
if(rates[index-1].close >= rates[index-1].open) return false; // Must be bearish
|
||||
if(nextBody < (nextRange * 0.6)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Filter 3: Check Trading Session Time |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckSession(datetime barTime)
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(barTime, dt);
|
||||
int hour = dt.hour;
|
||||
|
||||
// London Session
|
||||
if(hour >= LondonStartHour && hour < LondonEndHour) return true;
|
||||
|
||||
// New York Session
|
||||
if(hour >= NYStartHour && hour < NYEndHour) return true;
|
||||
|
||||
return false; // Outside active sessions (e.g., Asian session)
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Process Final Signal (Draw, Alert, Push) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ProcessSignal(bool isBullish, datetime barTime, double extremePrice)
|
||||
{
|
||||
string direction = isBullish ? "BULLISH" : "BEARISH";
|
||||
string message = StringFormat("✅ Golden %s Signal | %s | Price: %s",
|
||||
direction,
|
||||
TimeToString(barTime, TIME_DATE|TIME_MINUTES),
|
||||
DoubleToString(extremePrice, _Digits));
|
||||
|
||||
// 1. Draw Arrow on Chart
|
||||
if(DrawArrows)
|
||||
{
|
||||
string objName = "ShadowArrow_" + IntegerToString(barTime);
|
||||
int arrowCode = isBullish ? 233 : 234; // 233: Up arrow, 234: Down arrow
|
||||
color arrowColor = isBullish ? BullishArrowColor : BearishArrowColor;
|
||||
double arrowPrice = isBullish ?
|
||||
(extremePrice - ArrowDistancePoints * _Point) :
|
||||
(extremePrice + ArrowDistancePoints * _Point);
|
||||
|
||||
ObjectCreate(0, objName, OBJ_ARROW, 0, barTime, arrowPrice);
|
||||
ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrowCode);
|
||||
ObjectSetInteger(0, objName, OBJPROP_COLOR, arrowColor);
|
||||
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3);
|
||||
ObjectSetInteger(0, objName, OBJPROP_ANCHOR, isBullish ? ANCHOR_TOP : ANCHOR_BOTTOM);
|
||||
}
|
||||
|
||||
// 2. Trigger Alerts
|
||||
if(ShowAlerts) Alert(message);
|
||||
if(SendPush) SendNotification(message);
|
||||
|
||||
// 3. Print to Experts Log
|
||||
Print(message);
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -103,7 +103,7 @@ ENUM_X_SESSION_OVERLAP_TYPE eaAllowedOverlaps[]; // Allowed Overlaps for Analysi
|
||||
|
||||
//
|
||||
string eaTag; // EA Scoped Tag ...
|
||||
bool isNewBar = false; // EA Is New Bar ...
|
||||
bool isEaInNewBar = false; // EA Is New Bar ...
|
||||
|
||||
//
|
||||
XCAlert *eaAlert; // EA Scope Alert Handler ...
|
||||
@@ -172,7 +172,7 @@ void OnTick()
|
||||
|
||||
//
|
||||
// Check if Is New Bar ...
|
||||
isNewBar = eaBarTracker.IsNewBar();
|
||||
isEaInNewBar = eaBarTracker.IsNewBar();
|
||||
|
||||
//
|
||||
// Check Analysis Enabled ...
|
||||
@@ -184,7 +184,7 @@ void OnTick()
|
||||
{
|
||||
//
|
||||
// Check New Bar or not ...
|
||||
if (!isNewBar)
|
||||
if (!isEaInNewBar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"aab": "| Commons |",
|
||||
"aac": "|----------|",
|
||||
"mqlHelper": "node mqlHelper.js",
|
||||
"mqlDocumentHelper": "node ./Documents/JsModules/mqlDocumentHelper.js",
|
||||
"cleanupEx": "npm run mqlHelper cleanup",
|
||||
"compileWorkspace": "npm run mqlHelper *",
|
||||
"compileExperts": "npm run mqlHelper experts",
|
||||
|
||||
Reference in New Issue
Block a user