Compare commits
6
Commits
422e019acc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
536f5c59e2 | ||
|
|
cc51c0e906 | ||
|
|
0edd3c1b71 | ||
|
|
4707856302 | ||
|
|
9d23e2cba1 | ||
|
|
d50eef35bc |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,557 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Context Generator Module ...
|
||||
* a module for Generating Context ...
|
||||
*
|
||||
* Maintainer
|
||||
*
|
||||
* Hadi Khazaee Asl <https://saherelm.ir> (hadi_khazaee_asl@yahoo.com)
|
||||
*/
|
||||
|
||||
//
|
||||
//#region Imports ...
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
//#endregion
|
||||
|
||||
//
|
||||
//#region Constants ...
|
||||
const outputFilePrefix = "xDashboard";
|
||||
const allowedPatterns = [
|
||||
// {
|
||||
// kind: "file",
|
||||
// mode: "allow",
|
||||
// match: "name",
|
||||
// pattern: /^*.mq5$/i
|
||||
// },
|
||||
// {
|
||||
// kind: "directory",
|
||||
// mode: "deny",
|
||||
// match: "name",
|
||||
// pattern: /^node_modules$/i
|
||||
// },
|
||||
// {
|
||||
// kind: "file",
|
||||
// mode: "allow",
|
||||
// match: "path",
|
||||
// pattern: /^x-saherelm.*.mq5$/i
|
||||
// },
|
||||
|
||||
//
|
||||
// Base Pilot Files ...
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.base.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.session-detector.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-account.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-alert.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-app-dialog.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-backtester.expert.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-bar.analyser.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-chart.helper.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-cobject.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-data.collector.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-expert.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-guard.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-helper.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-http.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-market.pattern.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-md5.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-poi.drawer.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-position.drawer.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-restrictions.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-rm.panel.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-signaller.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-target.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-trade-manager.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-trade.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-volume.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.backtester.ea.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-expert.ea.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x121.xcc.helper.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x121.xct.helper.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x121.xcc.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x121.xct.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.common.extensions.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.common.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.common.models.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.draw.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-enums.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-guard.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-ohcl.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-pivot.analysis.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-poi.extensions.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-poi.lib.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-trade.lib.mq5$/i
|
||||
},
|
||||
]
|
||||
//#endregion
|
||||
|
||||
//
|
||||
// Reading Required Info
|
||||
const inputPath = process.argv[2];
|
||||
const searchPath = path.resolve(inputPath || process.cwd());
|
||||
|
||||
// Check Given Path is Valid or not ...
|
||||
if (!fs.existsSync(searchPath)) {
|
||||
console.error(`Error: path not found: ${searchPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieve Stats of Given Path ...
|
||||
const stat = fs.statSync(searchPath);
|
||||
if (!stat.isDirectory()) {
|
||||
console.error(`Error: path is not a directory: ${searchPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
//
|
||||
// Generate Required Data ...
|
||||
const timestamp = getTimestamp(new Date());
|
||||
const folderName = path.basename(searchPath) || "root";
|
||||
const safeFolderName = sanitizeFileName(folderName);
|
||||
|
||||
// Generate Output file Name ...
|
||||
const outputFileName = inputPath
|
||||
? `${outputFilePrefix}-${safeFolderName}-${timestamp}.txt`
|
||||
: `${outputFilePrefix}-${timestamp}.txt`;
|
||||
|
||||
//
|
||||
const outputPath = path.join(
|
||||
process.cwd(),
|
||||
"Documents",
|
||||
"GeneratedContext",
|
||||
outputFileName
|
||||
);
|
||||
|
||||
// Create Output File ...
|
||||
fs.writeFileSync(outputPath, "", "utf8");
|
||||
|
||||
// Navigate to Context Generation ...
|
||||
walk(searchPath);
|
||||
|
||||
//
|
||||
console.log(`Context generated: ${outputPath}`);
|
||||
|
||||
//
|
||||
//#region Required Tools ...
|
||||
/**
|
||||
* Validate Timestamp ...
|
||||
*
|
||||
* @param {*} value
|
||||
* @returns
|
||||
*/
|
||||
function pad(value) {
|
||||
return String(value).padStart(2, "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Validate ...
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns
|
||||
*/
|
||||
function getTimestamp(date) {
|
||||
//
|
||||
const year = date.getFullYear();
|
||||
const month = pad(date.getMonth() + 1);
|
||||
const day = pad(date.getDate());
|
||||
const hours = pad(date.getHours());
|
||||
const minutes = pad(date.getMinutes());
|
||||
const seconds = pad(date.getSeconds());
|
||||
|
||||
//
|
||||
return `${year}${month}${day}_${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Leaf Folder ...
|
||||
*
|
||||
* @param {*} value
|
||||
* @returns
|
||||
*/
|
||||
function sanitizeFileName(value) {
|
||||
return value.replace(/[\\/:*?"<>|]/g, "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Match Value ...
|
||||
*
|
||||
* @param {*} rule
|
||||
* @param {*} targetPath
|
||||
* @returns
|
||||
*/
|
||||
function getMatchValue(rule, targetPath) {
|
||||
//
|
||||
const normalizedPath = targetPath.replace(/\\/g, "/");
|
||||
if (rule.match === "path") {
|
||||
return normalizedPath;
|
||||
}
|
||||
|
||||
//
|
||||
return path.basename(targetPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match Patterns ...
|
||||
*
|
||||
* @param {*} targetPath
|
||||
* @param {*} rules
|
||||
* @returns
|
||||
*/
|
||||
function matchesPatterns(targetPath, rules) {
|
||||
//
|
||||
return rules.some(rule => {
|
||||
const value = getMatchValue(rule, targetPath);
|
||||
return rule.pattern.test(value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a Directory Path is Valid for Parsing or not ...
|
||||
*
|
||||
* @param {*} fileName
|
||||
* @returns
|
||||
*/
|
||||
function isAllowedFile(filePath) {
|
||||
//
|
||||
// Extract Deny Rules ...
|
||||
const denyRules = allowedPatterns.filter(
|
||||
p => p.kind === "file" && p.mode === "deny"
|
||||
);
|
||||
|
||||
//
|
||||
// Extract Allowed Rules ...
|
||||
const allowRules = allowedPatterns.filter(
|
||||
p => p.kind === "file" && p.mode === "allow"
|
||||
);
|
||||
|
||||
// Check Deny Rules First ...
|
||||
if (matchesPatterns(filePath, denyRules)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check Allowed Rules ...
|
||||
return matchesPatterns(filePath, allowRules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a Directory Path is Valid for Parsing or not ...
|
||||
*
|
||||
* @param {*} directoryPath
|
||||
* @returns
|
||||
*/
|
||||
function isAllowedDirectory(directoryPath) {
|
||||
//
|
||||
// Extract Deny Rules ...
|
||||
const denyRules = allowedPatterns.filter(
|
||||
p => p.kind === "directory" && p.mode === "deny"
|
||||
);
|
||||
|
||||
//
|
||||
// Extract Allowed Rules ...
|
||||
const allowRules = allowedPatterns.filter(
|
||||
p => p.kind === "directory" && p.mode === "allow"
|
||||
);
|
||||
|
||||
//
|
||||
// Check Deny Patterns as First ...
|
||||
if (matchesPatterns(directoryPath, denyRules)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Check Allow Patterns Exists ...
|
||||
if (allowRules.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Chck Allowed Patterns Matched ...
|
||||
return matchesPatterns(directoryPath, allowRules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop through a Directory and Navigate files for including in Context ...
|
||||
*
|
||||
* @param {*} dir
|
||||
* @returns
|
||||
*/
|
||||
function walk(dir) {
|
||||
//
|
||||
// Reading Directory Content ...
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch (error) {
|
||||
fs.appendFileSync(
|
||||
outputPath,
|
||||
`\n### ERROR reading directory: ${dir}\n${error.message}\n`,
|
||||
"utf8"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Loop through Entries ...
|
||||
for (const entry of entries) {
|
||||
//
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
//
|
||||
// Ignore if Symlink ...
|
||||
if (entry.isSymbolicLink()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Handle if requested Path is Directory ...
|
||||
if (entry.isDirectory()) {
|
||||
//
|
||||
// Allowed Folders ...
|
||||
if (!isAllowedDirectory(fullPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
walk(fullPath);
|
||||
}
|
||||
else
|
||||
//
|
||||
// Handle if requested Path is File ...
|
||||
if (entry.isFile()) {
|
||||
//
|
||||
// Allowed File ...
|
||||
if (!isAllowedFile(fullPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Holds File Content ...
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(fullPath, "utf8");
|
||||
} catch (error) {
|
||||
content = `// Error reading file: ${error.message}`;
|
||||
}
|
||||
|
||||
//
|
||||
// Append File Content for Context ...
|
||||
fs.appendFileSync(
|
||||
outputPath,
|
||||
`\n### FILE: ${fullPath}\n\n${content}\n`,
|
||||
"utf8"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
@@ -0,0 +1,318 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>X121 XReplay - سند پروژه</title>
|
||||
<style>
|
||||
@page { size: A4; margin: 2cm; }
|
||||
@media print {
|
||||
.page-break { page-break-before: always; }
|
||||
body { font-size: 11px; }
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Arial, sans-serif;
|
||||
line-height: 1.9; color: #1a1a2e; background: #fff;
|
||||
padding: 40px; max-width: 850px; margin: 0 auto;
|
||||
}
|
||||
h1 { font-size: 1.7em; color: #0f3460; border-bottom: 3px solid #e94560; padding-bottom: 12px; margin-bottom: 20px; }
|
||||
h2 { font-size: 1.35em; color: #16213e; margin-top: 28px; margin-bottom: 12px; padding-right: 12px; border-right: 4px solid #e94560; }
|
||||
h3 { font-size: 1.1em; color: #0f3460; margin-top: 18px; margin-bottom: 8px; }
|
||||
p { margin-bottom: 10px; text-align: justify; }
|
||||
.cover { text-align: center; padding: 70px 0; }
|
||||
.cover h1 { font-size: 2.4em; border: none; color: #e94560; margin-bottom: 10px; }
|
||||
.cover .subtitle { font-size: 1.2em; color: #555; margin-top: 12px; }
|
||||
.cover .meta { margin-top: 50px; color: #777; line-height: 2.2; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 12px 0 20px; font-size: 0.9em; }
|
||||
th, td { border: 1px solid #ccc; padding: 9px 11px; text-align: right; }
|
||||
th { background: #0f3460; color: #fff; font-weight: 600; }
|
||||
tr:nth-child(even) { background: #f4f6f9; }
|
||||
code { background: #f0f0f0; padding: 2px 5px; border-radius: 3px; font-family: 'Courier New', monospace; font-size: 0.87em; color: #c7254e; }
|
||||
pre {
|
||||
background: #1e1e2e; color: #cdd6f4; padding: 16px; border-radius: 6px;
|
||||
overflow-x: auto; margin: 12px 0 20px; font-size: 0.82em;
|
||||
line-height: 1.6; direction: ltr; text-align: left;
|
||||
}
|
||||
.note { background: #fff3cd; border-right: 4px solid #ffc107; padding: 12px 16px; margin: 12px 0; border-radius: 0 5px 5px 0; }
|
||||
.tip { background: #d1ecf1; border-right: 4px solid #17a2b8; padding: 12px 16px; margin: 12px 0; border-radius: 0 5px 5px 0; }
|
||||
.success { background: #d4edda; border-right: 4px solid #28a745; padding: 12px 16px; margin: 12px 0; border-radius: 0 5px 5px 0; }
|
||||
.flow-box { background: #e8f4fd; border: 1px solid #b3d9f2; border-radius: 6px; padding: 14px; margin: 10px 0; }
|
||||
ul, ol { padding-right: 22px; margin-bottom: 10px; }
|
||||
li { margin-bottom: 5px; }
|
||||
.footer { margin-top: 50px; padding-top: 15px; border-top: 2px solid #eee; text-align: center; color: #999; font-size: 0.85em; }
|
||||
.kbd { background: #eee; border: 1px solid #ccc; border-radius: 3px; padding: 2px 6px; font-family: monospace; }
|
||||
.section-num { color: #e94560; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ===== صفحه عنوان ===== -->
|
||||
<div class="cover page-break">
|
||||
<h1>X121 XReplay</h1>
|
||||
<div class="subtitle">ابزار پخش مجدد کندلها در متاتریدر ۵</div>
|
||||
<div class="subtitle" style="color:#e94560;">TradingView-Style Candle Replay Tool for MetaTrader 5</div>
|
||||
<div class="meta">
|
||||
<p><strong>نسخه:</strong> 1.00</p>
|
||||
<p><strong>توسعهدهنده:</strong> هادی خزاعی اصل</p>
|
||||
<p><strong>شرکت:</strong> فن آوران ساحر علم</p>
|
||||
<p><strong>SaherElm IT Center</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== بخش ۱: نیازمندی ===== -->
|
||||
<div class="page-break">
|
||||
<h1><span class="section-num">۱.</span> شرح نیازمندی</h1>
|
||||
|
||||
<h2>۱.۱ مسئله</h2>
|
||||
<p>
|
||||
معاملهگران برای تمرین و بکتست دستی استراتژیهای خود به ابزاری نیاز دارند که بتواند
|
||||
کندلهای بازار را از یک نقطه زمانی مشخص پنهان کند و سپس بهصورت تدریجی
|
||||
(کندل به کندل) آنها را نمایش دهد. این قابلیت در پلتفرم TradingView با نام
|
||||
<strong>Bar Replay</strong> وجود دارد، اما در MetaTrader 5 بهصورت پیشفرض
|
||||
در دسترس نیست.
|
||||
</p>
|
||||
|
||||
<h2>۱.۲ نیازمندیهای عملکردی</h2>
|
||||
<table>
|
||||
<tr><th>#</th><th>نیازمندی</th><th>توضیح</th></tr>
|
||||
<tr><td>۱</td><td>پنهانسازی کندلها</td><td>کندلهای بعد از نقطه cutoff باید از دید کاربر پنهان شوند</td></tr>
|
||||
<tr><td>۲</td><td>نمایش تدریجی</td><td>امکان نمایش کندلها یکییکی با دکمه یا کیبورد</td></tr>
|
||||
<tr><td>۳</td><td>پخش خودکار</td><td>امکان پخش خودکار کندلها با سرعت قابل تنظیم</td></tr>
|
||||
<tr><td>۴</td><td>بازنشانی</td><td>امکان بازگشت به نقطه شروع با یک کلیک</td></tr>
|
||||
<tr><td>۵</td><td>تغییر تایمفریم</td><td>حفظ وضعیت Replay هنگام تغییر تایمفریم چارت</td></tr>
|
||||
<tr><td>۶</td><td>بازیابی خودکار</td><td>بازیابی خودکار حالت چارت پس از حذف اندیکاتور</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۱.۳ محدودیتهای فنی MQL5</h2>
|
||||
<ul>
|
||||
<li>امکان حذف یا پنهانسازی مستقیم کندلهای واقعی وجود ندارد</li>
|
||||
<li>اندیکاتور نمیتواند دادههای تاریخی را تغییر دهد</li>
|
||||
<li>مدیریت رویدادهای چارت محدود به <code>OnChartEvent</code> است</li>
|
||||
<li>ذخیرهسازی وضعیت بین جلسات نیازمند Global Variables یا فایل است</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- ===== بخش ۲: معرفی ابزار ===== -->
|
||||
<div class="page-break">
|
||||
<h1><span class="section-num">۲.</span> معرفی ابزار</h1>
|
||||
|
||||
<h2>۲.۱ مشخصات کلی</h2>
|
||||
<table>
|
||||
<tr><th>مشخصه</th><th>مقدار</th></tr>
|
||||
<tr><td>نام</td><td><code>X121 XReplay</code></td></tr>
|
||||
<tr><td>نوع</td><td>اندیکاتور (Indicator)</td></tr>
|
||||
<tr><td>پلتفرم</td><td>MetaTrader 5 (MQL5)</td></tr>
|
||||
<tr><td>نسخه</td><td>1.00</td></tr>
|
||||
<tr><td>فایل</td><td><code>x-saherelm.x121.x-reply.mq5</code></td></tr>
|
||||
<tr><td>مسیر نصب</td><td><code>MQL5/Indicators/</code></td></tr>
|
||||
<tr><td>وابستگی</td><td><code>x-saherelm.common.lib.mq5</code></td></tr>
|
||||
<tr><td>توسعهدهنده</td><td>هادی خزاعی اصل</td></tr>
|
||||
<tr><td>شرکت</td><td>فن آوران ساحر علم (SaherElm IT Center)</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۲.۲ روش کار</h2>
|
||||
<p>
|
||||
ابزار از روش <strong>DRAW_COLOR_CANDLES</strong> با ۵ بافر برای رسم کندلها استفاده میکند.
|
||||
کندلهای واقعی چارت با تغییر استایل چارت (تبدیل رنگها به <code>CLR_NONE</code>)
|
||||
پنهان میشوند و کندلهای Replay توسط بافرهای اندیکاتور رسم میشوند.
|
||||
</p>
|
||||
|
||||
<h2>۲.۳ بافرها</h2>
|
||||
<table>
|
||||
<tr><th>بافر</th><th>نوع</th><th>کاربرد</th></tr>
|
||||
<tr><td><code>pBarOpenBuffer</code></td><td>INDICATOR_DATA</td><td>قیمت Open</td></tr>
|
||||
<tr><td><code>pBarHighBuffer</code></td><td>INDICATOR_DATA</td><td>قیمت High</td></tr>
|
||||
<tr><td><code>pBarLowBuffer</code></td><td>INDICATOR_DATA</td><td>قیمت Low</td></tr>
|
||||
<tr><td><code>pBarCloseBuffer</code></td><td>INDICATOR_DATA</td><td>قیمت Close</td></tr>
|
||||
<tr><td><code>pBarColorBuffer</code></td><td>INDICATOR_COLOR_INDEX</td><td>ایندکس رنگ کندل</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۲.۴ ایندکسهای رنگ</h2>
|
||||
<table>
|
||||
<tr><th>ایندکس</th><th>نام</th><th>کاربرد</th></tr>
|
||||
<tr><td>0</td><td><code>hideColorIDX</code></td><td>کندل پنهان (CLR_NONE)</td></tr>
|
||||
<tr><td>1</td><td><code>bullishColorIDX</code></td><td>کندل صعودی (clrDodgerBlue)</td></tr>
|
||||
<tr><td>2</td><td><code>bearishColorIDX</code></td><td>کندل نزولی (clrDarkOrange)</td></tr>
|
||||
<tr><td>3</td><td><code>neuturalColorIDX</code></td><td>کندل خنثی</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۲.۵ پارامترهای ورودی</h2>
|
||||
<table>
|
||||
<tr><th>گروه</th><th>پارامتر</th><th>پیشفرض</th><th>توضیح</th></tr>
|
||||
<tr><td rowspan="2">Replay Settings</td><td><code>startBar</code></td><td>20</td><td>تعداد کندلهای cutoff</td></tr>
|
||||
<tr><td><code>autoPlaySpeed</code></td><td>1000</td><td>سرعت پخش خودکار (ms)</td></tr>
|
||||
<tr><td rowspan="4">Presentation</td><td><code>panelButtonTextColor</code></td><td>clrYellow</td><td>رنگ متن دکمهها</td></tr>
|
||||
<tr><td><code>panelBackgroundColor</code></td><td>clrBlack</td><td>رنگ پسزمینه پنل</td></tr>
|
||||
<tr><td><code>infoColor</code></td><td>clrWhite</td><td>رنگ متن اطلاعات</td></tr>
|
||||
<tr><td><code>infoPanelBorderColor</code></td><td>clrBlack</td><td>رنگ حاشیه پنل</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۲.۶ کنترلها</h2>
|
||||
<table>
|
||||
<tr><th>دکمه</th><th>متن</th><th>عملکرد</th><th>کیبورد</th></tr>
|
||||
<tr><td>Prev</td><td><code>|<</code></td><td>یک کندل به عقب</td><td><span class="kbd">←</span> / <span class="kbd">↓</span></td></tr>
|
||||
<tr><td>Play</td><td><code>></code></td><td>شروع پخش خودکار</td><td><span class="kbd">Space</span></td></tr>
|
||||
<tr><td>Pause</td><td><code>||</code></td><td>توقف پخش</td><td><span class="kbd">Space</span></td></tr>
|
||||
<tr><td>Next</td><td><code>>|</code></td><td>یک کندل به جلو</td><td><span class="kbd">→</span> / <span class="kbd">↑</span></td></tr>
|
||||
<tr><td>Reset</td><td><code>R</code></td><td>بازنشانی به نقطه شروع</td><td><span class="kbd">R</span></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ===== بخش ۳: معماری ===== -->
|
||||
<div class="page-break">
|
||||
<h1><span class="section-num">۳.</span> معماری فنی</h1>
|
||||
|
||||
<h2>۳.۱ ساختار فایل</h2>
|
||||
<table>
|
||||
<tr><th>بخش</th><th>محتوا</th></tr>
|
||||
<tr><td><code>#region Imports</code></td><td>کتابخانههای مورد نیاز</td></tr>
|
||||
<tr><td><code>#region Definitions</code></td><td>ثابتها، ساختار XChartStyle، ایندکسهای رنگ</td></tr>
|
||||
<tr><td><code>#region Inputs</code></td><td>پارامترهای ورودی کاربر</td></tr>
|
||||
<tr><td><code>#region Buffers</code></td><td>تعریف ۵ بافر و پلات</td></tr>
|
||||
<tr><td><code>#region Variables</code></td><td>متغیرهای سراسری</td></tr>
|
||||
<tr><td><code>#region Event Handlers</code></td><td>OnInit، OnDeinit، OnCalculate، OnTimer، OnChartEvent</td></tr>
|
||||
<tr><td><code>#region Custm Functions</code></td><td>توابع کمکی و اعتبارسنجی</td></tr>
|
||||
<tr><td><code>#region Value Calculators</code></td><td>محاسبه مقادیر بافرها</td></tr>
|
||||
<tr><td><code>#region Chart Style</code></td><td>مدیریت استایل چارت</td></tr>
|
||||
<tr><td><code>#region UI</code></td><td>پنل کنترل و دکمهها</td></tr>
|
||||
<tr><td><code>#region Control Actions</code></td><td>اکشنهای پخش</td></tr>
|
||||
<tr><td><code>#region TimeFrame Change</code></td><td>مدیریت تغییر تایمفریم</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۳.۲ ساختار XChartStyle</h2>
|
||||
<p>این ساختار ۲۱ فیلد دارد و برای ذخیره و بازیابی کامل حالت چارت استفاده میشود:</p>
|
||||
<table>
|
||||
<tr><th>دسته</th><th>فیلدها</th></tr>
|
||||
<tr><td>شناسایی</td><td>chartId, mode</td></tr>
|
||||
<tr><td>نمایش</td><td>showBidLine, showAskLine, showGrid, showVolumes, showTradeLevels, autoScroll, quickNavigation</td></tr>
|
||||
<tr><td>رنگها</td><td>foreGroundColor, backGroundColor, upColor, downColor, bullishColor, bearishColor, gridColor, bidLineColor, askLineColor, lineColor, stopColor, volumesColor</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۳.۳ مکانیزم پنهانسازی</h2>
|
||||
<div class="flow-box">
|
||||
<ol>
|
||||
<li><strong>ReadChartStyle:</strong> خواندن حالت فعلی چارت و ذخیره در <code>xReplyChartStyle</code></li>
|
||||
<li><strong>SaveChartStyleToFile:</strong> ذخیره در فایل باینری با <code>FileWriteStruct</code></li>
|
||||
<li><strong>ساخت clearStyle:</strong> کپی از حالت اصلی با رنگهای <code>CLR_NONE</code></li>
|
||||
<li><strong>HideBars:</strong> اعمال <code>clearStyle</code> بر چارت → کندلهای واقعی نامرئی میشوند</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h2>۳.۴ مکانیزم رسم کندلهای Replay</h2>
|
||||
<div class="flow-box">
|
||||
<ol>
|
||||
<li><strong>OnCalculate:</strong> پر کردن بافرهای OHLC از دادههای واقعی</li>
|
||||
<li><strong>UpdateVisibleColors:</strong> تنظیم رنگ بافر بر اساس وضعیت Replay</li>
|
||||
<li>کندلهای قبل از cutoff → رنگ عادی (صعودی/نزولی)</li>
|
||||
<li>کندلهای بعد از cutoff → <code>hideColorIDX</code> (پنهان)</li>
|
||||
<li>کندلهای قابل نمایش Replay → رنگ عادی (صعودی/نزولی)</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h2>۳.۵ مدیریت تغییر تایمفریم</h2>
|
||||
<div class="flow-box">
|
||||
<p><strong>هنگام تغییر تایمفریم (OnDeinit با REASON_CHARTCHANGE):</strong></p>
|
||||
<ol>
|
||||
<li>محاسبه ایندکس آخرین کندل نمایش داده شده</li>
|
||||
<li>ذخیره زمان آن کندل در Global Variable</li>
|
||||
<li>ذخیره وضعیت فعال بودن در Global Variable</li>
|
||||
<li>ذخیره تعداد کندلهای قابل نمایش در Global Variable</li>
|
||||
</ol>
|
||||
<p><strong>در تایمفریم جدید (OnInit):</strong></p>
|
||||
<ol>
|
||||
<li>بررسی وجود Global Variables</li>
|
||||
<li>یافتن اولین کندل در تایمفریم جدید که شامل زمان ذخیرهشده است</li>
|
||||
<li>تنظیم <code>xReplyStartBarIndex</code> بر اساس کندل یافتشده</li>
|
||||
<li>بازیابی تعداد کندلهای قابل نمایش</li>
|
||||
<li>حذف Global Variables</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== بخش ۴: فرآیند انجام کار ===== -->
|
||||
<div class="page-break">
|
||||
<h1><span class="section-num">۴.</span> فرآیند و مراحل انجام کار</h1>
|
||||
|
||||
<h2>۴.۱ خلاصه مراحل توسعه</h2>
|
||||
<table>
|
||||
<tr><th>مرحله</th><th>شرح</th><th>نتیجه</th></tr>
|
||||
<tr><td>۱</td><td>تحلیل نیازمندی و بررسی محدودیتهای MQL5</td><td>شناسایی روش پنهانسازی</td></tr>
|
||||
<tr><td>۲</td><td>طراحی معماری با DRAW_COLOR_CANDLES</td><td>تعریف ۵ بافر و ۴ رنگ</td></tr>
|
||||
<tr><td>۳</td><td>پیادهسازی مدیریت Chart Style</td><td>ساختار XChartStyle با ۲۱ فیلد</td></tr>
|
||||
<tr><td>۴</td><td>پیادهسازی ذخیره/بازیابی فایل باینری</td><td>FileWriteStruct / FileReadStruct</td></tr>
|
||||
<tr><td>۵</td><td>پیادهسازی پنل UI و کنترلها</td><td>۵ دکمه + برچسب اطلاعات</td></tr>
|
||||
<tr><td>۶</td><td>پیادهسازی میانبرهای کیبورد</td><td>Arrow Keys + Space + R</td></tr>
|
||||
<tr><td>۷</td><td>پیادهسازی تایمر و پخش خودکار</td><td>StartTimer / StopTimer</td></tr>
|
||||
<tr><td>۸</td><td>پیادهسازی مدیریت تغییر تایمفریم</td><td>Global Variables</td></tr>
|
||||
<tr><td>۹</td><td>بهینهسازی و تست نهایی</td><td>نسخه 1.00</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۴.۲ تصمیمات فنی کلیدی</h2>
|
||||
<table>
|
||||
<tr><th>تصمیم</th><th>دلیل</th></tr>
|
||||
<tr><td>استفاده از DRAW_COLOR_CANDLES</td><td>کارآمدترین روش رسم کندل در MQL5 بدون ایجاد اشیاء گرافیکی</td></tr>
|
||||
<tr><td>پنهانسازی با CLR_NONE</td><td>تنها روش ممکن برای نامرئی کردن کندلهای واقعی بدون حذف آنها</td></tr>
|
||||
<tr><td>ذخیره Chart Style در فایل باینری</td><td>بازیابی خودکار پس از حذف اندیکاتور یا کرش ترمینال</td></tr>
|
||||
<tr><td>Global Variables برای تایمفریم</td><td>حفظ وضعیت بین OnDeinit و OnInit هنگام تغییر تایمفریم</td></tr>
|
||||
<tr><td>تایمر فقط در حالت Play</td><td>بهینهسازی مصرف CPU در حالت Pause</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>۴.۳ ساختار کدنویسی</h2>
|
||||
<p>کد بر اساس دستخط کدنویسی شرکت فن آوران ساحر علم نوشته شده است:</p>
|
||||
<ul>
|
||||
<li>بلوکبندی با <code>#region</code> / <code>#endregion</code></li>
|
||||
<li>کامنتهای ساختاریافته با <code>//</code></li>
|
||||
<li>پیشوند <code>xReply</code> برای متغیرهای مربوط به Replay</li>
|
||||
<li>توابع استاندارد: <code>ValidateInputs</code>، <code>InitRequirements</code>، <code>DefineBuffers</code>، <code>SetIndicatorName</code></li>
|
||||
<li>مدیریت خطا با الگوی <code>has</code> / <code>result</code></li>
|
||||
<li>پاکسازی منابع در <code>OnDeinit</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- ===== بخش ۵: راهنمای استفاده ===== -->
|
||||
<div class="page-break">
|
||||
<h1><span class="section-num">۵.</span> راهنمای استفاده</h1>
|
||||
|
||||
<h2>۵.۱ نصب</h2>
|
||||
<ol>
|
||||
<li>فایل <code>x-saherelm.x121.x-reply.mq5</code> را در مسیر <code>MQL5/Indicators/</code> قرار دهید</li>
|
||||
<li>در MetaEditor با <span class="kbd">F7</span> کامپایل کنید</li>
|
||||
<li>اندیکاتور را از Navigator روی چارت بکشید</li>
|
||||
<li>پارامتر <code>startBar</code> را تنظیم کنید (تعداد کندلهای cutoff)</li>
|
||||
</ol>
|
||||
|
||||
<h2>۵.۲ استفاده</h2>
|
||||
<ul>
|
||||
<li>با <span class="kbd">→</span> یا <span class="kbd">↑</span> یک کندل به جلو بروید</li>
|
||||
<li>با <span class="kbd">←</span> یا <span class="kbd">↓</span> یک کندل به عقب برگردید</li>
|
||||
<li>با <span class="kbd">Space</span> پخش خودکار را شروع/متوقف کنید</li>
|
||||
<li>با <span class="kbd">R</span> به نقطه شروع برگردید</li>
|
||||
<li>تایمفریم را آزادانه تغییر دهید — وضعیت حفظ میشود</li>
|
||||
</ul>
|
||||
|
||||
<h2>۵.۳ حذف</h2>
|
||||
<p>
|
||||
با حذف اندیکاتور از چارت، حالت چارت بهطور خودکار بازیابی میشود،
|
||||
فایل Chart Style حذف میشود و اشیاء UI پاک میشوند.
|
||||
</p>
|
||||
|
||||
<div class="success">
|
||||
<strong>✅ نکته:</strong> در صورت کرش ترمینال، فایل Chart Style باقی میماند و در اجرای بعدی
|
||||
بهجای خواندن از چارت، از فایل خوانده میشود. این تضمین میکند که حالت چارت همیشه درست بازیابی شود.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== فوتر ===== -->
|
||||
<div class="footer page-break">
|
||||
<h2 style="border:none; text-align:center; padding:0;">پایان سند</h2>
|
||||
<br>
|
||||
<p><strong>X121 XReplay</strong> — نسخه 1.00</p>
|
||||
<p>توسعهدهنده: هادی خزاعی اصل</p>
|
||||
<p>فن آوران ساحر علم | SaherElm IT Center</p>
|
||||
<br>
|
||||
<p style="color:#aaa; font-size:0.9em;">© تمامی حقوق محفوظ است</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,672 @@
|
||||
///////////////////////////////////////////////////////
|
||||
//
|
||||
// SaherElm IT Center MQL5 Expert Advisor
|
||||
// -------------------------------------------------
|
||||
// Name: XBaseEA
|
||||
// Description: Session Detector Test Expert Advisor ...
|
||||
//
|
||||
// Maintainer:
|
||||
// ------------
|
||||
// Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com)
|
||||
//
|
||||
//////////////////////////////////////////////////////
|
||||
//
|
||||
// Global Properties ...
|
||||
#property copyright "Copyright 2023, SaherElm IT Center"
|
||||
#property link "https://saherelm.ir"
|
||||
#property version "1.00"
|
||||
#property icon "../Images/SaherElm_Logo_Color.ico"
|
||||
#property description "SaherElm Session Detector Test Expert Advisor"
|
||||
#property strict
|
||||
|
||||
// #region Imports ...
|
||||
#include "../Classes/x-saherelm.pivot-detector.class.mq5"
|
||||
#include "../Classes/x-saherelm.session-detector.class.mq5"
|
||||
#include "../Classes/x-saherelm.x-poi.drawer.class.mq5"
|
||||
// #endregion
|
||||
|
||||
// #region Global Variables ...
|
||||
#define ShortName "XPivotDetectorTestEA"
|
||||
|
||||
//
|
||||
XCAlert *eaAlert;
|
||||
XCPOIDrawer *eaDrawer;
|
||||
XCSessionDetector *eaSessionDetector;
|
||||
|
||||
//
|
||||
XCPivotDetector *eaMajorPivotDetector;
|
||||
XCPivotDetector *eaMinorPivotDetector;
|
||||
|
||||
//
|
||||
// Last Processing Pivots Time ...
|
||||
bool eaDrawMajorPivots = true;
|
||||
bool eaDrawMinorPivots = true;
|
||||
XPOIDrawConfig eaMajorPivotsDrawConfig;
|
||||
XPOIDrawConfig eaMinorPivotsDrawConfig;
|
||||
datetime eaLastMajorPivotProcessing = NULL;
|
||||
datetime eaLastMinorPivotProcessing = NULL;
|
||||
XPOIDrawConfig eaPointedMajorPivotsDrawConfig;
|
||||
XPOIDrawConfig eaPointedMinorPivotsDrawConfig;
|
||||
ENUM_X_DIRECTION eaMajorBias = X_DIRECTION_NONE;
|
||||
ENUM_X_DIRECTION eaMinorBias = X_DIRECTION_NONE;
|
||||
// #endregion
|
||||
|
||||
// #region Global Event Handlers ...
|
||||
/**
|
||||
* Initialize EA Process ...
|
||||
*
|
||||
* @return ( int )
|
||||
*/
|
||||
int OnInit()
|
||||
{
|
||||
//
|
||||
bool result = true;
|
||||
|
||||
//
|
||||
result = InitializeRequirements();
|
||||
if (!result)
|
||||
{
|
||||
return INIT_FAILED;
|
||||
}
|
||||
|
||||
//
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* De Initialize Expert ...
|
||||
*
|
||||
* @param reason: int, DeInitialization Reason ...
|
||||
*
|
||||
* REASON_PROGRAM 0 The EA has stopped working calling the ExpertRemove() function
|
||||
* REASON_REMOVE 1 Program removed from a chart
|
||||
* REASON_RECOMPILE 2 Program recompiled
|
||||
* REASON_CHARTCHANGE 3 A symbol or a chart period is changed
|
||||
* REASON_CHARTCLOSE 4 Chart closed
|
||||
* REASON_PARAMETERS 5 Inputs changed by a user
|
||||
* REASON_ACCOUNT 6 Another account has been activated or reconnection to the trade server has occurred due to changes in the account settings
|
||||
* REASON_TEMPLATE 7 Another chart template applied
|
||||
* REASON_INITFAILED 8 The OnInit() handler returned a non-zero value
|
||||
* REASON_CLOSE 9 Terminal closed
|
||||
*
|
||||
*/
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
//
|
||||
// Destroy Timer ...
|
||||
// If Initialized ...
|
||||
|
||||
//
|
||||
eaMajorPivotsDrawConfig.Clean();
|
||||
eaMinorPivotsDrawConfig.Clean();
|
||||
eaPointedMajorPivotsDrawConfig.Clean();
|
||||
eaPointedMinorPivotsDrawConfig.Clean();
|
||||
|
||||
//
|
||||
// Cleanup Resources ...
|
||||
ZeroMemory(eaAlert);
|
||||
ZeroMemory(eaDrawer);
|
||||
ZeroMemory(eaSessionDetector);
|
||||
ZeroMemory(eaMajorPivotDetector);
|
||||
ZeroMemory(eaMinorPivotDetector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Tick for Processing ...
|
||||
*/
|
||||
void OnTick()
|
||||
{
|
||||
//
|
||||
// Perform Session Detector Process ...
|
||||
eaSessionDetector.Process();
|
||||
|
||||
//
|
||||
// Process Pivot Detector ...
|
||||
HandleProcessMajorPviots();
|
||||
HandleProcessMinorPviots();
|
||||
|
||||
//
|
||||
// Detecting Bias ...
|
||||
HanldeBiasDetection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle On Chart Event Processing ...
|
||||
|
||||
* @param id: int, Specified Event ID ...
|
||||
* @param lparam: long, type event parameter
|
||||
* @param dparam: double, type event parameter
|
||||
* @param sparam: string, type event parameter
|
||||
*/
|
||||
void OnChartEvent(
|
||||
const int id,
|
||||
const long &lparam,
|
||||
const double &dparam,
|
||||
const string &sparam //
|
||||
)
|
||||
{
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Custom Actions ...
|
||||
/**
|
||||
* Initial Requirements of EA ...
|
||||
*
|
||||
* @return ( bool )
|
||||
*/
|
||||
bool InitializeRequirements()
|
||||
{
|
||||
//
|
||||
bool result = false;
|
||||
|
||||
//
|
||||
eaDrawer = new XCPOIDrawer();
|
||||
|
||||
//
|
||||
// Initialize Alert ...
|
||||
eaAlert = new XCAlert(
|
||||
ShortName,
|
||||
false, // Enable Alert ...
|
||||
false, // Log Alert ...
|
||||
false, // Terminal Alert ...
|
||||
false, // Mail Alert ...
|
||||
false, // Push Alerts ...
|
||||
false // Save Alerts ...
|
||||
);
|
||||
result = eaAlert != NULL;
|
||||
if (!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize Session Detector ...
|
||||
eaSessionDetector = new XCSessionDetector(
|
||||
eaAlert, // Alert ...
|
||||
true, // Use Server Time ...
|
||||
0 // Timezone Offset ...
|
||||
);
|
||||
|
||||
//
|
||||
// Register Event Handlers ...
|
||||
eaSessionDetector.AddOnSessionEndHandler(OnSessionEndHandler);
|
||||
eaSessionDetector.AddOnOverlapEndHandler(OnOverlapEndHandler);
|
||||
eaSessionDetector.AddOnSessionStartHandler(OnSessionStartHandler);
|
||||
eaSessionDetector.AddOnOverlapStartHandler(OnOverlapStartHandler);
|
||||
eaSessionDetector.AddOnSessionStateChangeHandler(OnSessionStateChangeHandler);
|
||||
|
||||
//
|
||||
result = eaSessionDetector != NULL;
|
||||
if (!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize Major Pivot Detector ...
|
||||
eaMajorPivotDetector = new XCPivotDetector(
|
||||
eaAlert, // Alert ...
|
||||
"X121SDTMajor", // Pivot Prefix ...
|
||||
false, // Support Inverted Zones ...
|
||||
0 // Processable Zones ...
|
||||
);
|
||||
|
||||
//
|
||||
// Register Event Handlers ...
|
||||
eaMajorPivotDetector.AddOnPeakBreakedHandler(OnMajorPeakBreakedHandler);
|
||||
eaMajorPivotDetector.AddOnValeBreakedHandler(OnMajorValeBreakedHandler);
|
||||
eaMajorPivotDetector.AddOnPeakDetectedHandler(OnMajorPeakDetectedHandler);
|
||||
eaMajorPivotDetector.AddOnValeDetectedHandler(OnMajorValeDetectedHandler);
|
||||
|
||||
//
|
||||
// Pivot Draw Config ...
|
||||
eaMajorPivotsDrawConfig.zonesWidth = 2;
|
||||
eaMajorPivotsDrawConfig.zonesFill = false;
|
||||
eaMajorPivotsDrawConfig.ignoreZonesAt = true;
|
||||
eaMajorPivotsDrawConfig.zonesStyle = STYLE_SOLID;
|
||||
eaMajorPivotsDrawConfig.bullishColor = clrAqua;
|
||||
eaMajorPivotsDrawConfig.bearishColor = clrMagenta;
|
||||
|
||||
//
|
||||
// Pointed Pivot Draw Config ...
|
||||
eaPointedMajorPivotsDrawConfig.zonesWidth = 2;
|
||||
eaPointedMajorPivotsDrawConfig.zonesFill = false;
|
||||
eaPointedMajorPivotsDrawConfig.ignoreZonesAt = true;
|
||||
eaPointedMajorPivotsDrawConfig.zonesStyle = STYLE_SOLID;
|
||||
eaPointedMajorPivotsDrawConfig.bullishColor = clrYellow;
|
||||
eaPointedMajorPivotsDrawConfig.bearishColor = clrOrange;
|
||||
|
||||
//
|
||||
result = eaMajorPivotDetector != NULL;
|
||||
if (!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize Minor Pivot Detector ...
|
||||
eaMinorPivotDetector = new XCPivotDetector(
|
||||
eaAlert, // Alert ...
|
||||
"X121SDTMinor", // Pivot Prefix ...
|
||||
false, // Support Inverted Zones ...
|
||||
0 // Processable Zones ...
|
||||
);
|
||||
|
||||
//
|
||||
// Register Event Handlers ...
|
||||
eaMinorPivotDetector.AddOnPeakBreakedHandler(OnMinorPeakBreakedHandler);
|
||||
eaMinorPivotDetector.AddOnValeBreakedHandler(OnMinorValeBreakedHandler);
|
||||
eaMinorPivotDetector.AddOnPeakDetectedHandler(OnMinorPeakDetectedHandler);
|
||||
eaMinorPivotDetector.AddOnValeDetectedHandler(OnMinorValeDetectedHandler);
|
||||
|
||||
//
|
||||
// Pivot Draw Config ...
|
||||
eaMinorPivotsDrawConfig.zonesWidth = 1;
|
||||
eaMinorPivotsDrawConfig.zonesFill = false;
|
||||
eaMinorPivotsDrawConfig.ignoreZonesAt = true;
|
||||
eaMinorPivotsDrawConfig.bearishColor = clrRed;
|
||||
eaMinorPivotsDrawConfig.bullishColor = clrLime;
|
||||
eaMinorPivotsDrawConfig.zonesStyle = STYLE_DASHDOT;
|
||||
|
||||
//
|
||||
// Pointed Pivot Draw Config ...
|
||||
eaPointedMinorPivotsDrawConfig.zonesWidth = 2;
|
||||
eaPointedMinorPivotsDrawConfig.zonesFill = false;
|
||||
eaPointedMinorPivotsDrawConfig.ignoreZonesAt = true;
|
||||
eaPointedMinorPivotsDrawConfig.bearishColor = clrOrange;
|
||||
eaPointedMinorPivotsDrawConfig.bullishColor = clrYellow;
|
||||
eaPointedMinorPivotsDrawConfig.zonesStyle = STYLE_DASHDOT;
|
||||
|
||||
//
|
||||
result = eaMinorPivotDetector != NULL;
|
||||
if (!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Pivots ...
|
||||
*/
|
||||
void HandleProcessMajorPviots()
|
||||
{
|
||||
//
|
||||
// Initialize Pivot Processing Bar ...
|
||||
XOHCL bar;
|
||||
bool has = bar.Init(
|
||||
_Symbol,
|
||||
PERIOD_H4,
|
||||
1 // Last Completed Bar ...
|
||||
);
|
||||
if (!has)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Validate Passing Bar ...
|
||||
has =
|
||||
!IsXValid(eaLastMajorPivotProcessing) ||
|
||||
bar.time > eaLastMajorPivotProcessing;
|
||||
if (!has)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
eaLastMajorPivotProcessing = bar.time;
|
||||
|
||||
//
|
||||
eaMajorPivotDetector.Process(
|
||||
bar, // XOHCL Bar ...
|
||||
2, // Pivot Approvation Length ...
|
||||
20 // Loopback ...
|
||||
);
|
||||
|
||||
//
|
||||
// Handle Draw Pivots ...
|
||||
if (!eaDrawMajorPivots)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
XBoxZone tmps[];
|
||||
FillNotExists(
|
||||
tmps, // Dest ...
|
||||
eaMajorPivotDetector.peaks, // Source ...
|
||||
false // Force Clean ...
|
||||
);
|
||||
FillNotExists(
|
||||
tmps, // Dest ...
|
||||
eaMajorPivotDetector.vales, // Source ...
|
||||
false // Force Clean ...
|
||||
);
|
||||
if (HasChild(tmps))
|
||||
{
|
||||
eaDrawer.DrawZones(
|
||||
tmps, // Boxes ...
|
||||
eaMajorPivotsDrawConfig // Draw Config ...
|
||||
);
|
||||
}
|
||||
XClean(tmps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Pivots ...
|
||||
*/
|
||||
void HandleProcessMinorPviots()
|
||||
{
|
||||
//
|
||||
// Initialize Pivot Processing Bar ...
|
||||
XOHCL bar;
|
||||
bool has = bar.Init(
|
||||
_Symbol,
|
||||
PERIOD_M15,
|
||||
1 // Last Completed Bar ...
|
||||
);
|
||||
if (!has)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Validate Passing Bar ...
|
||||
has =
|
||||
!IsXValid(eaLastMinorPivotProcessing) ||
|
||||
bar.time > eaLastMinorPivotProcessing;
|
||||
if (!has)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
eaLastMinorPivotProcessing = bar.time;
|
||||
|
||||
//
|
||||
eaMinorPivotDetector.Process(
|
||||
bar, // XOHCL Bar ...
|
||||
5, // Pivot Approvation Length ...
|
||||
40 // Loopback ...
|
||||
);
|
||||
|
||||
//
|
||||
// Handle Draw Pivots ...
|
||||
if (!eaDrawMinorPivots)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
XBoxZone tmps[];
|
||||
FillNotExists(
|
||||
tmps, // Dest ...
|
||||
eaMinorPivotDetector.peaks, // Source ...
|
||||
false // Force Clean ...
|
||||
);
|
||||
FillNotExists(
|
||||
tmps, // Dest ...
|
||||
eaMinorPivotDetector.vales, // Source ...
|
||||
false // Force Clean ...
|
||||
);
|
||||
if (HasChild(tmps))
|
||||
{
|
||||
eaDrawer.DrawZones(
|
||||
tmps, // Boxes ...
|
||||
eaMinorPivotsDrawConfig // Draw Config ...
|
||||
);
|
||||
}
|
||||
XClean(tmps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect Bias based on Pivots ...
|
||||
*/
|
||||
void HanldeBiasDetection()
|
||||
{
|
||||
//
|
||||
bool has = false;
|
||||
|
||||
//
|
||||
// Major Bias ...
|
||||
has =
|
||||
eaMajorPivotDetector.HasPeaks() &&
|
||||
eaMajorPivotDetector.HasVales();
|
||||
if (has)
|
||||
{
|
||||
//
|
||||
int youngestPeakIDX = eaMajorPivotDetector.GetYoungestPeak();
|
||||
int youngestValeIDX = eaMajorPivotDetector.GetYoungestVale();
|
||||
has =
|
||||
IsValidIndex(youngestPeakIDX) &&
|
||||
IsValidIndex(youngestValeIDX);
|
||||
if (has)
|
||||
{
|
||||
//
|
||||
eaMajorBias =
|
||||
eaMajorPivotDetector.peaks[youngestPeakIDX].from > eaMajorPivotDetector.vales[youngestValeIDX].from
|
||||
? X_DIRECTION_BEARISH
|
||||
: eaMajorPivotDetector.vales[youngestValeIDX].from > eaMajorPivotDetector.peaks[youngestPeakIDX].from
|
||||
? X_DIRECTION_BULLISH
|
||||
: X_DIRECTION_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Custom Event Handlers ...
|
||||
|
||||
// #region Pivot Detector Event Handlers ...
|
||||
/**
|
||||
* Calls when a Peak Breaked ...
|
||||
*
|
||||
* @param box: XBoxZone instance ...
|
||||
*/
|
||||
void OnMajorPeakBreakedHandler(XBoxZone &box)
|
||||
{
|
||||
// DebugBreak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when a Vale Breaked ...
|
||||
*
|
||||
* @param box: XBoxZone instance ...
|
||||
*/
|
||||
void OnMajorValeBreakedHandler(XBoxZone &box)
|
||||
{
|
||||
// DebugBreak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when a New Peak Detected ...
|
||||
*
|
||||
* @param box: XBoxZone instance ...
|
||||
*/
|
||||
void OnMajorPeakDetectedHandler(XBoxZone &box)
|
||||
{
|
||||
//
|
||||
eaDrawer.DrawZone(
|
||||
box, //
|
||||
eaPointedMajorPivotsDrawConfig //
|
||||
);
|
||||
|
||||
//
|
||||
DebugBreak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when a New Vale Detected ...
|
||||
*
|
||||
* @param box: XBoxZone instance ...
|
||||
*/
|
||||
void OnMajorValeDetectedHandler(XBoxZone &box)
|
||||
{
|
||||
//
|
||||
eaDrawer.DrawZone(
|
||||
box, //
|
||||
eaPointedMajorPivotsDrawConfig //
|
||||
);
|
||||
|
||||
//
|
||||
DebugBreak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when a Peak Breaked ...
|
||||
*
|
||||
* @param box: XBoxZone instance ...
|
||||
*/
|
||||
void OnMinorPeakBreakedHandler(XBoxZone &box)
|
||||
{
|
||||
// DebugBreak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when a Vale Breaked ...
|
||||
*
|
||||
* @param box: XBoxZone instance ...
|
||||
*/
|
||||
void OnMinorValeBreakedHandler(XBoxZone &box)
|
||||
{
|
||||
// DebugBreak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when a New Peak Detected ...
|
||||
*
|
||||
* @param box: XBoxZone instance ...
|
||||
*/
|
||||
void OnMinorPeakDetectedHandler(XBoxZone &box)
|
||||
{
|
||||
//
|
||||
eaDrawer.DrawZone(
|
||||
box, //
|
||||
eaPointedMinorPivotsDrawConfig //
|
||||
);
|
||||
|
||||
//
|
||||
DebugBreak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when a New Vale Detected ...
|
||||
*
|
||||
* @param box: XBoxZone instance ...
|
||||
*/
|
||||
void OnMinorValeDetectedHandler(XBoxZone &box)
|
||||
{
|
||||
//
|
||||
eaDrawer.DrawZone(
|
||||
box, //
|
||||
eaPointedMinorPivotsDrawConfig //
|
||||
);
|
||||
|
||||
//
|
||||
DebugBreak();
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Session Detector Event Handlers ...
|
||||
/**
|
||||
* Handle Session Start Event ...
|
||||
*
|
||||
* @param session: ENUM_X_SESSION_TYPE
|
||||
* @param startTime: datetime
|
||||
*/
|
||||
void OnSessionStartHandler(
|
||||
ENUM_X_SESSION_TYPE session,
|
||||
datetime startTime //
|
||||
)
|
||||
{
|
||||
// Print("Session Started: ", EnumToString(session), " at ", TimeToString(startTime));
|
||||
|
||||
// //
|
||||
// // Custom Testing ...
|
||||
// if (session == X_SESSION_NEWYORK)
|
||||
// {
|
||||
// DebugBreak();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Session End Event ...
|
||||
*
|
||||
* @param session: ENUM_X_SESSION_TYPE
|
||||
* @param startTime: datetime
|
||||
*/
|
||||
void OnSessionEndHandler(
|
||||
ENUM_X_SESSION_TYPE session,
|
||||
datetime startTime //
|
||||
)
|
||||
{
|
||||
// Print("Session Ended: ", EnumToString(session), " at ", TimeToString(startTime));
|
||||
|
||||
// //
|
||||
// // Custom Testing ...
|
||||
// if (session == X_SESSION_NEWYORK)
|
||||
// {
|
||||
// DebugBreak();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Overlap Start Event ...
|
||||
*
|
||||
* @param session: ENUM_X_SESSION_OVERLAP_TYPE
|
||||
* @param startTime: datetime
|
||||
*/
|
||||
void OnOverlapStartHandler(
|
||||
ENUM_X_SESSION_OVERLAP_TYPE overlap,
|
||||
datetime startTime //
|
||||
)
|
||||
{
|
||||
// Print("Overlap Started: ", EnumToString(overlap), " at ", TimeToString(startTime));
|
||||
|
||||
// //
|
||||
// // Custom Testing ...
|
||||
// if (overlap == X_OVERLAP_LONDON_NEWYORK)
|
||||
// {
|
||||
// DebugBreak();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Overlap End Event ...
|
||||
*
|
||||
* @param session: ENUM_X_SESSION_OVERLAP_TYPE
|
||||
* @param startTime: datetime
|
||||
*/
|
||||
void OnOverlapEndHandler(
|
||||
ENUM_X_SESSION_OVERLAP_TYPE overlap,
|
||||
datetime startTime //
|
||||
)
|
||||
{
|
||||
// Print("Overlap Ended: ", EnumToString(overlap), " at ", TimeToString(startTime));
|
||||
|
||||
// //
|
||||
// // Custom Testing ...
|
||||
// if (overlap == X_OVERLAP_LONDON_NEWYORK)
|
||||
// {
|
||||
// DebugBreak();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Session State Change Event ...
|
||||
*
|
||||
* @param session: ENUM_X_SESSION_TYPE
|
||||
* @param state: ENUM_X_SESSION_STATE
|
||||
*/
|
||||
void OnSessionStateChangeHandler(
|
||||
ENUM_X_SESSION_TYPE session,
|
||||
ENUM_X_SESSION_STATE state //
|
||||
)
|
||||
{
|
||||
// Print("Session State Changed: ", EnumToString(session), " State: ", EnumToString(state));
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #endregion
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3482,6 +3482,74 @@ int FillNotExists(
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int FillNotExists(
|
||||
T &dest[],
|
||||
T &source[],
|
||||
bool forceClean = false,
|
||||
ENUM_X_DIRECTION forDir = X_DIRECTION_NONE,
|
||||
int maxAllowed = 0 //
|
||||
)
|
||||
{
|
||||
//
|
||||
int result = 0;
|
||||
|
||||
//
|
||||
// Prepare ...
|
||||
if (forceClean)
|
||||
{
|
||||
XClean(dest);
|
||||
}
|
||||
|
||||
//
|
||||
// Normalize ...
|
||||
maxAllowed = NormalizeInt(maxAllowed, 0);
|
||||
|
||||
//
|
||||
// Validate ...
|
||||
bool has = HasChild(source);
|
||||
if (!has)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
int count = ArraySize(source);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
//
|
||||
has =
|
||||
source[i].IsValid() &&
|
||||
(!HasDirection(forDir) || forDir == source[i].dir);
|
||||
if (!has)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
has = AddIfNotExists(source[i], dest);
|
||||
if (has)
|
||||
{
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Handle Cleanup Collection
|
||||
// if Max Reached ...
|
||||
if (maxAllowed > 0)
|
||||
{
|
||||
//
|
||||
CleanupArray(
|
||||
dest,
|
||||
maxAllowed //
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -3654,6 +3722,125 @@ bool IsBoxValid(
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a Box based on Price ...
|
||||
*
|
||||
* @param box: XBoxZone instance, box to Validate ...
|
||||
* @param barIndex: int, Start Validating Bar's Index ...
|
||||
* @param pType: ENUM_X_BOUNDARY_PRICE, Price Type for Validation ...
|
||||
*
|
||||
* @return ( bool )
|
||||
*/
|
||||
bool IsBoxValid(
|
||||
XBoxZone &box,
|
||||
int barIndex = 0,
|
||||
ENUM_X_BOUNDARY_PRICE pType = X_BOUNDARY_PRICE_UP_DOWN //
|
||||
)
|
||||
{
|
||||
//
|
||||
bool result = false;
|
||||
|
||||
//
|
||||
// Normalize Bar Index ...
|
||||
barIndex = NormalizeInt(barIndex, 0);
|
||||
|
||||
//
|
||||
// Validaet ...
|
||||
result =
|
||||
box.IsValid() &&
|
||||
IsXValid(pType);
|
||||
if (!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
bool isBullish = box.IsBullish();
|
||||
|
||||
//
|
||||
int fromIDX = box.FromIndex();
|
||||
result =
|
||||
IsValidIndex(fromIDX) &&
|
||||
barIndex < fromIDX;
|
||||
if (!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// Loop through ...
|
||||
XOHCL iBar;
|
||||
bool has = false;
|
||||
double appliedPrice = EMPTY_VALUE;
|
||||
for (int i = barIndex; i < fromIDX; i++)
|
||||
{
|
||||
//
|
||||
// Initialize Bar ...
|
||||
iBar.Clean();
|
||||
has = iBar.Init(box.symbol, box.period, i);
|
||||
if (!has)
|
||||
{
|
||||
//
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Calculate Applied Price ...
|
||||
switch (pType)
|
||||
{
|
||||
//
|
||||
case X_BOUNDARY_PRICE_UP_DOWN:
|
||||
//
|
||||
appliedPrice =
|
||||
isBullish
|
||||
? iBar.GetDown()
|
||||
: iBar.GetUp();
|
||||
break;
|
||||
|
||||
//
|
||||
case X_BOUNDARY_PRICE_HIGH_LOW:
|
||||
//
|
||||
appliedPrice =
|
||||
isBullish
|
||||
? iBar.low
|
||||
: iBar.high;
|
||||
break;
|
||||
|
||||
//
|
||||
default:
|
||||
appliedPrice = EMPTY_VALUE;
|
||||
break;
|
||||
}
|
||||
has = NotEmptyZero(appliedPrice);
|
||||
if (!has)
|
||||
{
|
||||
//
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
has =
|
||||
has &&
|
||||
result &&
|
||||
(isBullish
|
||||
? appliedPrice >= box.lower
|
||||
: appliedPrice <= box.upper);
|
||||
if (!has) {
|
||||
//
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
iBar.Clean();
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
void SortBoxes(
|
||||
XBoxZone &items[],
|
||||
|
||||
+10
-1
@@ -16,6 +16,15 @@
|
||||
"*.mq5": "cpp",
|
||||
"*.mqh": "cpp",
|
||||
"*.ex5": "cpp"
|
||||
},
|
||||
"maptz.regionfolder": {
|
||||
"[cpp]": {
|
||||
"foldStart": "//#region [NAME]",
|
||||
"foldEnd": "//#endregion",
|
||||
"foldEndRegex": "//[\\s]*#endregion",
|
||||
"foldStartRegex": "//[\\s]*#region[\\s]*(.*)",
|
||||
"disableFolding": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Context Generator Module ...
|
||||
* a module for Generating Context ...
|
||||
*
|
||||
* Maintainer
|
||||
*
|
||||
* Hadi Khazaee Asl <https://saherelm.ir> (hadi_khazaee_asl@yahoo.com)
|
||||
*/
|
||||
|
||||
//
|
||||
//#region Imports ...
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
//#endregion
|
||||
|
||||
//
|
||||
//#region Constants ...
|
||||
const outputFilePrefix = "x121XSDT";
|
||||
const allowedPatterns = [
|
||||
//
|
||||
// Base Pilot Files ...
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.pivot-detector.class.mq5$/i
|
||||
},
|
||||
{
|
||||
kind: "file",
|
||||
mode: "allow",
|
||||
match: "name",
|
||||
pattern: /^x-saherelm.x-pivot-detector.test.mq5$/i
|
||||
},
|
||||
]
|
||||
//#endregion
|
||||
|
||||
//
|
||||
// Reading Required Info
|
||||
const inputPath = process.argv[2];
|
||||
const searchPath = path.resolve(inputPath || process.cwd());
|
||||
|
||||
// Check Given Path is Valid or not ...
|
||||
if (!fs.existsSync(searchPath)) {
|
||||
console.error(`Error: path not found: ${searchPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieve Stats of Given Path ...
|
||||
const stat = fs.statSync(searchPath);
|
||||
if (!stat.isDirectory()) {
|
||||
console.error(`Error: path is not a directory: ${searchPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
//
|
||||
// Generate Required Data ...
|
||||
const timestamp = getTimestamp(new Date());
|
||||
const folderName = path.basename(searchPath) || "root";
|
||||
const safeFolderName = sanitizeFileName(folderName);
|
||||
|
||||
// Generate Output file Name ...
|
||||
const outputFileName = inputPath
|
||||
? `${outputFilePrefix}-${safeFolderName}-${timestamp}.txt`
|
||||
: `${outputFilePrefix}-${timestamp}.txt`;
|
||||
|
||||
//
|
||||
const outputPath = path.join(
|
||||
process.cwd(),
|
||||
"Documents",
|
||||
"GeneratedContext",
|
||||
outputFileName
|
||||
);
|
||||
|
||||
// Create Output File ...
|
||||
fs.writeFileSync(outputPath, "", "utf8");
|
||||
|
||||
// Navigate to Context Generation ...
|
||||
walk(searchPath);
|
||||
|
||||
//
|
||||
console.log(`Context generated: ${outputPath}`);
|
||||
|
||||
//
|
||||
//#region Required Tools ...
|
||||
/**
|
||||
* Validate Timestamp ...
|
||||
*
|
||||
* @param {*} value
|
||||
* @returns
|
||||
*/
|
||||
function pad(value) {
|
||||
return String(value).padStart(2, "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Validate ...
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns
|
||||
*/
|
||||
function getTimestamp(date) {
|
||||
//
|
||||
const year = date.getFullYear();
|
||||
const month = pad(date.getMonth() + 1);
|
||||
const day = pad(date.getDate());
|
||||
const hours = pad(date.getHours());
|
||||
const minutes = pad(date.getMinutes());
|
||||
const seconds = pad(date.getSeconds());
|
||||
|
||||
//
|
||||
return `${year}${month}${day}_${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Leaf Folder ...
|
||||
*
|
||||
* @param {*} value
|
||||
* @returns
|
||||
*/
|
||||
function sanitizeFileName(value) {
|
||||
return value.replace(/[\\/:*?"<>|]/g, "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Match Value ...
|
||||
*
|
||||
* @param {*} rule
|
||||
* @param {*} targetPath
|
||||
* @returns
|
||||
*/
|
||||
function getMatchValue(rule, targetPath) {
|
||||
//
|
||||
const normalizedPath = targetPath.replace(/\\/g, "/");
|
||||
if (rule.match === "path") {
|
||||
return normalizedPath;
|
||||
}
|
||||
|
||||
//
|
||||
return path.basename(targetPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match Patterns ...
|
||||
*
|
||||
* @param {*} targetPath
|
||||
* @param {*} rules
|
||||
* @returns
|
||||
*/
|
||||
function matchesPatterns(targetPath, rules) {
|
||||
//
|
||||
return rules.some(rule => {
|
||||
const value = getMatchValue(rule, targetPath);
|
||||
return rule.pattern.test(value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a Directory Path is Valid for Parsing or not ...
|
||||
*
|
||||
* @param {*} fileName
|
||||
* @returns
|
||||
*/
|
||||
function isAllowedFile(filePath) {
|
||||
//
|
||||
// Extract Deny Rules ...
|
||||
const denyRules = allowedPatterns.filter(
|
||||
p => p.kind === "file" && p.mode === "deny"
|
||||
);
|
||||
|
||||
//
|
||||
// Extract Allowed Rules ...
|
||||
const allowRules = allowedPatterns.filter(
|
||||
p => p.kind === "file" && p.mode === "allow"
|
||||
);
|
||||
|
||||
// Check Deny Rules First ...
|
||||
if (matchesPatterns(filePath, denyRules)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check Allowed Rules ...
|
||||
return matchesPatterns(filePath, allowRules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a Directory Path is Valid for Parsing or not ...
|
||||
*
|
||||
* @param {*} directoryPath
|
||||
* @returns
|
||||
*/
|
||||
function isAllowedDirectory(directoryPath) {
|
||||
//
|
||||
// Extract Deny Rules ...
|
||||
const denyRules = allowedPatterns.filter(
|
||||
p => p.kind === "directory" && p.mode === "deny"
|
||||
);
|
||||
|
||||
//
|
||||
// Extract Allowed Rules ...
|
||||
const allowRules = allowedPatterns.filter(
|
||||
p => p.kind === "directory" && p.mode === "allow"
|
||||
);
|
||||
|
||||
//
|
||||
// Check Deny Patterns as First ...
|
||||
if (matchesPatterns(directoryPath, denyRules)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Check Allow Patterns Exists ...
|
||||
if (allowRules.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Chck Allowed Patterns Matched ...
|
||||
return matchesPatterns(directoryPath, allowRules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop through a Directory and Navigate files for including in Context ...
|
||||
*
|
||||
* @param {*} dir
|
||||
* @returns
|
||||
*/
|
||||
function walk(dir) {
|
||||
//
|
||||
// Reading Directory Content ...
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch (error) {
|
||||
fs.appendFileSync(
|
||||
outputPath,
|
||||
`\n### ERROR reading directory: ${dir}\n${error.message}\n`,
|
||||
"utf8"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Loop through Entries ...
|
||||
for (const entry of entries) {
|
||||
//
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
//
|
||||
// Ignore if Symlink ...
|
||||
if (entry.isSymbolicLink()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Handle if requested Path is Directory ...
|
||||
if (entry.isDirectory()) {
|
||||
//
|
||||
// Allowed Folders ...
|
||||
if (!isAllowedDirectory(fullPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
walk(fullPath);
|
||||
}
|
||||
else
|
||||
//
|
||||
// Handle if requested Path is File ...
|
||||
if (entry.isFile()) {
|
||||
//
|
||||
// Allowed File ...
|
||||
if (!isAllowedFile(fullPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Holds File Content ...
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(fullPath, "utf8");
|
||||
} catch (error) {
|
||||
content = `// Error reading file: ${error.message}`;
|
||||
}
|
||||
|
||||
//
|
||||
// Append File Content for Context ...
|
||||
fs.appendFileSync(
|
||||
outputPath,
|
||||
`\n### FILE: ${fullPath}\n\n${content}\n`,
|
||||
"utf8"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
Reference in New Issue
Block a user