mirror of
https://github.com/FxPouya/FxMathQuantWebApp.git
synced 2026-08-09 08:07:44 +00:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,216 @@
|
|||||||
|
# FxMathQuant-Web Deployment Checklist
|
||||||
|
|
||||||
|
## ✅ Pre-Deployment Checklist
|
||||||
|
|
||||||
|
### Files Ready for GitHub Pages
|
||||||
|
|
||||||
|
- [x] DataProvider EAs copied to `downloads/` folder
|
||||||
|
- `FxMathQuant_DataExporter_MT4.ex4`
|
||||||
|
- `FxMathQuant_DataExporter_MT5.ex5`
|
||||||
|
- `README.md` (DataProvider guide)
|
||||||
|
|
||||||
|
- [x] User Manual created
|
||||||
|
- `USER_MANUAL.md` - Comprehensive guide
|
||||||
|
|
||||||
|
- [x] Contact Information Updated
|
||||||
|
- Email: fxmathsolution@gmail.com
|
||||||
|
- Telegram: https://t.me/FxMath
|
||||||
|
|
||||||
|
- [x] Logout Button Added
|
||||||
|
- Located in header (top-right)
|
||||||
|
- Icon: 🚪
|
||||||
|
- Function: `logoutLicense()`
|
||||||
|
|
||||||
|
- [x] Session Management
|
||||||
|
- **Duration**: Persists until manual logout or browser data clear
|
||||||
|
- **No auto-logout**: Session remains active
|
||||||
|
- **Stored in**: localStorage
|
||||||
|
|
||||||
|
- [x] CSV Upload Fix
|
||||||
|
- Accepts capitalized column names (Time, Open, High, Low, Close)
|
||||||
|
- Compatible with MT4/MT5 DataProvider EA exports
|
||||||
|
|
||||||
|
- [x] Cache Busting
|
||||||
|
- `main.js?v=2.0` - Forces browser to load updated file
|
||||||
|
|
||||||
|
## 📋 Deployment Steps
|
||||||
|
|
||||||
|
### 1. Create GitHub Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On GitHub.com
|
||||||
|
1. Click "New Repository"
|
||||||
|
2. Name: FxMathQuant-Web
|
||||||
|
3. Visibility: Public
|
||||||
|
4. Add README: Yes
|
||||||
|
5. Click "Create repository"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Enable GitHub Pages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In repository settings
|
||||||
|
1. Go to Settings → Pages
|
||||||
|
2. Source: Deploy from a branch
|
||||||
|
3. Branch: main
|
||||||
|
4. Folder: / (root)
|
||||||
|
5. Click "Save"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Upload Files
|
||||||
|
|
||||||
|
**Option A: Web Interface**
|
||||||
|
```bash
|
||||||
|
1. Click "Add file" → "Upload files"
|
||||||
|
2. Drag entire FxMathQuant-Web folder contents
|
||||||
|
3. Commit message: "Initial deployment"
|
||||||
|
4. Click "Commit changes"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Git Command Line**
|
||||||
|
```bash
|
||||||
|
cd /home/fxmathai/Desktop/2025-12/FxMathQuant-Web-Version/FxMathQuant-Web
|
||||||
|
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial deployment"
|
||||||
|
git branch -M main
|
||||||
|
git remote add origin https://github.com/YOUR_USERNAME/FxMathQuant-Web.git
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Wait for Deployment
|
||||||
|
|
||||||
|
- GitHub Actions will build and deploy (1-2 minutes)
|
||||||
|
- Check "Actions" tab for progress
|
||||||
|
- Green checkmark = deployed successfully
|
||||||
|
|
||||||
|
### 5. Test Live Site
|
||||||
|
|
||||||
|
```
|
||||||
|
https://YOUR_USERNAME.github.io/FxMathQuant-Web/
|
||||||
|
```
|
||||||
|
|
||||||
|
Test:
|
||||||
|
1. License activation
|
||||||
|
2. CSV upload
|
||||||
|
3. Strategy generation
|
||||||
|
4. Downloads
|
||||||
|
5. Logout button
|
||||||
|
|
||||||
|
## 🔧 Post-Deployment Configuration
|
||||||
|
|
||||||
|
### Update API URL (Already Done)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// js/login.js - Line 7
|
||||||
|
const API_URL = 'https://fxmath.com/quantw/api/validate.php';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server-Side Setup (Already Complete)
|
||||||
|
|
||||||
|
- ✅ Server deployed at: https://fxmath.com/quantw/
|
||||||
|
- ✅ Admin panel: https://fxmath.com/quantw/admin/
|
||||||
|
- ✅ API endpoint: https://fxmath.com/quantw/api/validate.php
|
||||||
|
- ✅ License generation API: https://fxmath.com/quantw/api/create.php
|
||||||
|
|
||||||
|
## 📁 File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
FxMathQuant-Web/
|
||||||
|
├── index.html (main app)
|
||||||
|
├── login.html (license activation)
|
||||||
|
├── USER_MANUAL.md (comprehensive guide)
|
||||||
|
├── css/
|
||||||
|
│ └── style.css
|
||||||
|
├── js/
|
||||||
|
│ ├── main.js (v2.0 - cache busted)
|
||||||
|
│ ├── login.js (API URL updated)
|
||||||
|
│ ├── license-check.js
|
||||||
|
│ ├── backtester.js
|
||||||
|
│ ├── ga-engine.js
|
||||||
|
│ ├── strategy.js
|
||||||
|
│ ├── strategy-details.js
|
||||||
|
│ ├── html-report-generator.js
|
||||||
|
│ ├── ui-controller.js
|
||||||
|
│ ├── rule-parser.js
|
||||||
|
│ ├── mq4-converter.js
|
||||||
|
│ ├── mq5-converter.js
|
||||||
|
│ ├── ctrader-converter.js
|
||||||
|
│ └── pine-converter.js
|
||||||
|
├── downloads/
|
||||||
|
│ ├── FxMathQuant_DataExporter_MT4.ex4
|
||||||
|
│ ├── FxMathQuant_DataExporter_MT5.ex5
|
||||||
|
│ └── README.md
|
||||||
|
├── data/
|
||||||
|
│ └── (sample CSV files)
|
||||||
|
└── assets/
|
||||||
|
└── (images, icons)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Features Summary
|
||||||
|
|
||||||
|
### License System
|
||||||
|
- ✅ License activation required
|
||||||
|
- ✅ Lifetime and time-limited licenses supported
|
||||||
|
- ✅ Session persists until logout
|
||||||
|
- ✅ Logout button in header
|
||||||
|
- ✅ API validation against production server
|
||||||
|
|
||||||
|
### Data Import
|
||||||
|
- ✅ CSV upload (drag-and-drop or browse)
|
||||||
|
- ✅ Accepts MT4/MT5 DataProvider EA format
|
||||||
|
- ✅ Capitalized column names supported
|
||||||
|
- ✅ 10,000+ bars recommended
|
||||||
|
|
||||||
|
### Strategy Generation
|
||||||
|
- ✅ Genetic algorithm optimization
|
||||||
|
- ✅ Configurable parameters
|
||||||
|
- ✅ Real-time progress tracking
|
||||||
|
- ✅ Multiple strategies per run
|
||||||
|
|
||||||
|
### Results & Downloads
|
||||||
|
- ✅ Strategy details modal
|
||||||
|
- ✅ BUY/SELL rules display
|
||||||
|
- ✅ Equity curve chart
|
||||||
|
- ✅ Hourly performance analysis
|
||||||
|
- ✅ Trade statement
|
||||||
|
- ✅ Download formats: MQ4, MQ5, cTrader, Pine Script, HTML, JSON
|
||||||
|
|
||||||
|
### User Support
|
||||||
|
- ✅ Comprehensive user manual
|
||||||
|
- ✅ DataProvider EA guide
|
||||||
|
- ✅ Contact: fxmathsolution@gmail.com
|
||||||
|
- ✅ Telegram: https://t.me/FxMath
|
||||||
|
|
||||||
|
## 🔒 Security
|
||||||
|
|
||||||
|
- ✅ License validation via API
|
||||||
|
- ✅ HTTPS required for production
|
||||||
|
- ✅ CORS enabled for GitHub Pages
|
||||||
|
- ✅ No sensitive data stored client-side
|
||||||
|
- ✅ Session in localStorage (can be cleared)
|
||||||
|
|
||||||
|
## 📊 Session Management
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
1. User activates license on `login.html`
|
||||||
|
2. License validated against API
|
||||||
|
3. License info stored in `localStorage`
|
||||||
|
4. `license-check.js` validates on every page load
|
||||||
|
5. Session persists until:
|
||||||
|
- User clicks logout button
|
||||||
|
- User clears browser data
|
||||||
|
- License expires (for time-limited)
|
||||||
|
|
||||||
|
### Logout Process
|
||||||
|
1. User clicks 🚪 button in header
|
||||||
|
2. Confirmation dialog appears
|
||||||
|
3. `localStorage` cleared
|
||||||
|
4. Redirected to `login.html`
|
||||||
|
|
||||||
|
## 🎉 Ready for Deployment!
|
||||||
|
|
||||||
|
All files are prepared and ready to upload to GitHub Pages.
|
||||||
|
|
||||||
|
**Next Action**: Upload to GitHub and test!
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
# FxMathQuant Web - AI-Powered Strategy Generator
|
||||||
|
|
||||||
|
🚀 **Generate profitable trading strategies using Genetic Algorithms - 100% in your browser!**
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
✅ **Client-Side Processing** - Everything runs in your browser, no server needed
|
||||||
|
✅ **AI-Powered** - Genetic Algorithm evolves profitable trading strategies
|
||||||
|
✅ **Fast Backtesting** - Optimized JavaScript with typed arrays
|
||||||
|
✅ **MQ4/MQ5 Export** - Generate ready-to-use MetaTrader Expert Advisors
|
||||||
|
✅ **HTML Reports** - Beautiful performance reports with interactive charts
|
||||||
|
✅ **GitHub Pages Ready** - Host for FREE on GitHub Pages
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Upload CSV Data** - Export OHLC data from MT4/MT5 and upload
|
||||||
|
2. **Configure GA** - Set population, generations, and performance filters
|
||||||
|
3. **Generate Strategies** - AI evolves profitable trading strategies
|
||||||
|
4. **Download Code** - Get MQ4, MQ5, and HTML reports
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Option 1: Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone or download this folder
|
||||||
|
cd FxMathQuant-Web
|
||||||
|
|
||||||
|
# Serve with any HTTP server
|
||||||
|
python -m http.server 8000
|
||||||
|
# or
|
||||||
|
npx serve
|
||||||
|
|
||||||
|
# Open browser
|
||||||
|
open http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: GitHub Pages (Recommended)
|
||||||
|
|
||||||
|
1. **Create GitHub Repository**
|
||||||
|
```bash
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial commit"
|
||||||
|
git branch -M main
|
||||||
|
git remote add origin https://github.com/yourusername/fxmathquant-web.git
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Enable GitHub Pages**
|
||||||
|
- Go to repository Settings → Pages
|
||||||
|
- Source: Deploy from branch `main`
|
||||||
|
- Folder: `/ (root)`
|
||||||
|
- Save
|
||||||
|
|
||||||
|
3. **Access Your App**
|
||||||
|
- URL: `https://yourusername.github.io/fxmathquant-web/`
|
||||||
|
|
||||||
|
## Preparing Data
|
||||||
|
|
||||||
|
### MT4/MT5 Data Export
|
||||||
|
|
||||||
|
Create a simple EA to export OHLC data:
|
||||||
|
|
||||||
|
**MT4 Script (SaveOHLC.mq4):**
|
||||||
|
```mql4
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Script to export OHLC data to CSV |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnStart()
|
||||||
|
{
|
||||||
|
string filename = Symbol() + "_" + PeriodToString() + ".csv";
|
||||||
|
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV);
|
||||||
|
|
||||||
|
if(handle != INVALID_HANDLE)
|
||||||
|
{
|
||||||
|
FileWrite(handle, "time", "open", "high", "low", "close");
|
||||||
|
|
||||||
|
int bars = iBars(Symbol(), Period());
|
||||||
|
for(int i = bars - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
FileWrite(handle,
|
||||||
|
TimeToString(iTime(Symbol(), Period(), i)),
|
||||||
|
iOpen(Symbol(), Period(), i),
|
||||||
|
iHigh(Symbol(), Period(), i),
|
||||||
|
iLow(Symbol(), Period(), i),
|
||||||
|
iClose(Symbol(), Period(), i)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileClose(handle);
|
||||||
|
Print("Data exported to: ", filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string PeriodToString()
|
||||||
|
{
|
||||||
|
switch(Period())
|
||||||
|
{
|
||||||
|
case PERIOD_M1: return "M1";
|
||||||
|
case PERIOD_M5: return "M5";
|
||||||
|
case PERIOD_M15: return "M15";
|
||||||
|
case PERIOD_M30: return "M30";
|
||||||
|
case PERIOD_H1: return "H1";
|
||||||
|
case PERIOD_H4: return "H4";
|
||||||
|
case PERIOD_D1: return "D1";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**MT5 Script (SaveOHLC.mq5):**
|
||||||
|
```mql5
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Script to export OHLC data to CSV |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnStart()
|
||||||
|
{
|
||||||
|
string filename = _Symbol + "_" + PeriodToString() + ".csv";
|
||||||
|
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV);
|
||||||
|
|
||||||
|
if(handle != INVALID_HANDLE)
|
||||||
|
{
|
||||||
|
FileWrite(handle, "time", "open", "high", "low", "close");
|
||||||
|
|
||||||
|
int bars = Bars(_Symbol, _Period);
|
||||||
|
datetime time[];
|
||||||
|
double open[], high[], low[], close[];
|
||||||
|
|
||||||
|
CopyTime(_Symbol, _Period, 0, bars, time);
|
||||||
|
CopyOpen(_Symbol, _Period, 0, bars, open);
|
||||||
|
CopyHigh(_Symbol, _Period, 0, bars, high);
|
||||||
|
CopyLow(_Symbol, _Period, 0, bars, low);
|
||||||
|
CopyClose(_Symbol, _Period, 0, bars, close);
|
||||||
|
|
||||||
|
for(int i = bars - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
FileWrite(handle,
|
||||||
|
TimeToString(time[i]),
|
||||||
|
open[i],
|
||||||
|
high[i],
|
||||||
|
low[i],
|
||||||
|
close[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileClose(handle);
|
||||||
|
Print("Data exported to: ", filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string PeriodToString()
|
||||||
|
{
|
||||||
|
switch(_Period)
|
||||||
|
{
|
||||||
|
case PERIOD_M1: return "M1";
|
||||||
|
case PERIOD_M5: return "M5";
|
||||||
|
case PERIOD_M15: return "M15";
|
||||||
|
case PERIOD_M30: return "M30";
|
||||||
|
case PERIOD_H1: return "H1";
|
||||||
|
case PERIOD_H4: return "H4";
|
||||||
|
case PERIOD_D1: return "D1";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Guide
|
||||||
|
|
||||||
|
### GA Parameters
|
||||||
|
|
||||||
|
| Parameter | Recommended | Description |
|
||||||
|
|-----------|-------------|-------------|
|
||||||
|
| Population | 100-200 | Strategies per generation |
|
||||||
|
| Generations | 50-100 | Evolution cycles |
|
||||||
|
| Strategies to Find | 5-10 | Target number of strategies |
|
||||||
|
|
||||||
|
### Performance Filters
|
||||||
|
|
||||||
|
| Filter | Recommended | Description |
|
||||||
|
|--------|-------------|-------------|
|
||||||
|
| Min Profit Factor | 1.5-2.0 | Minimum PF required |
|
||||||
|
| Min Win Rate | 45-55% | Minimum win rate |
|
||||||
|
| Max Drawdown | 20-30% | Maximum acceptable DD |
|
||||||
|
| Min Trades | 30-50 | Minimum trade count |
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Processing Speed:** ~50-100 strategies/second (depends on device)
|
||||||
|
- **Recommended Data Size:** 1,000-5,000 bars
|
||||||
|
- **Browser Support:** Chrome, Firefox, Safari, Edge (latest versions)
|
||||||
|
|
||||||
|
## Technical Stack
|
||||||
|
|
||||||
|
- **Frontend:** Vanilla HTML5 + JavaScript (no frameworks)
|
||||||
|
- **Charts:** Chart.js 4.4.0
|
||||||
|
- **CSV Parsing:** PapaParse 5.4.1
|
||||||
|
- **Styling:** Custom CSS with dark theme
|
||||||
|
- **Hosting:** GitHub Pages compatible
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
FxMathQuant-Web/
|
||||||
|
├── index.html # Main page
|
||||||
|
├── css/
|
||||||
|
│ └── style.css # Styles
|
||||||
|
├── js/
|
||||||
|
│ ├── main.js # App controller
|
||||||
|
│ ├── strategy.js # Strategy class
|
||||||
|
│ ├── backtester.js # Backtesting engine
|
||||||
|
│ ├── ga-engine.js # Genetic Algorithm
|
||||||
|
│ ├── mq4-generator.js # MQ4 code generator
|
||||||
|
│ ├── mq5-generator.js # MQ5 code generator
|
||||||
|
│ └── report-generator.js # HTML report generator
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Browser Compatibility
|
||||||
|
|
||||||
|
✅ Chrome 90+
|
||||||
|
✅ Firefox 88+
|
||||||
|
✅ Safari 14+
|
||||||
|
✅ Edge 90+
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- **Dataset Size:** Recommended max 5,000 bars for optimal performance
|
||||||
|
- **Processing Time:** Depends on device CPU (slower on mobile)
|
||||||
|
- **Storage:** All data stays in browser (not saved to server)
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Q: Is my data sent to a server?**
|
||||||
|
A: No! Everything runs 100% in your browser. Your data never leaves your device.
|
||||||
|
|
||||||
|
**Q: Can I use this offline?**
|
||||||
|
A: Yes, once loaded. Download the files and open `index.html` locally.
|
||||||
|
|
||||||
|
**Q: How accurate is the backtesting?**
|
||||||
|
A: Very accurate. Uses the same logic as the Python version with ATR-based SL/TP.
|
||||||
|
|
||||||
|
**Q: Can I modify the generated MQ4/MQ5 code?**
|
||||||
|
A: Yes! The code is clean and well-commented for easy customization.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Website:** https://fxmathquant.com
|
||||||
|
- **Email:** support@fxmath.com
|
||||||
|
- **GitHub:** https://github.com/yourusername/fxmathquant-web
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
© 2025 FxMathQuant. All rights reserved.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
⚠️ **Trading Risk Warning**
|
||||||
|
|
||||||
|
Trading forex and CFDs carries a high level of risk. Past performance is not indicative of future results. Always test strategies on demo accounts before live trading. Never invest more than you can afford to lose.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Made with ❤️ by FxMathQuant**
|
||||||
+443
@@ -0,0 +1,443 @@
|
|||||||
|
# FxMathQuant Strategy Finder - User Manual
|
||||||
|
|
||||||
|
**AI-Powered Trading Strategy Generator**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
1. [Getting Started](#getting-started)
|
||||||
|
2. [License Activation](#license-activation)
|
||||||
|
3. [Exporting Data from MT4/MT5](#exporting-data)
|
||||||
|
4. [Uploading Data](#uploading-data)
|
||||||
|
5. [Configuring Strategy Generation](#configuration)
|
||||||
|
6. [Generating Strategies](#generation)
|
||||||
|
7. [Viewing Results](#results)
|
||||||
|
8. [Downloading Strategies](#downloading)
|
||||||
|
9. [Troubleshooting](#troubleshooting)
|
||||||
|
10. [Contact Support](#support)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
### What is FxMathQuant?
|
||||||
|
|
||||||
|
FxMathQuant is an AI-powered trading strategy generator that uses genetic algorithms to discover profitable trading strategies from your historical price data.
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
|
||||||
|
- **Web Browser**: Chrome, Firefox, Safari, or Edge (latest version)
|
||||||
|
- **MetaTrader**: MT4 or MT5 (for data export)
|
||||||
|
- **License Key**: Required for access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 License Activation
|
||||||
|
|
||||||
|
### Step 1: Obtain License Key
|
||||||
|
|
||||||
|
Purchase a license from:
|
||||||
|
- **Website**: https://fxmath.com
|
||||||
|
- **Email**: fxmathsolution@gmail.com
|
||||||
|
- **Telegram**: https://t.me/FxMath
|
||||||
|
|
||||||
|
### Step 2: Activate License
|
||||||
|
|
||||||
|
1. **Open the application** in your web browser
|
||||||
|
2. **Enter your license key** in the format: `XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX`
|
||||||
|
3. **Click "Activate License"**
|
||||||
|
4. **Wait for validation** (requires internet connection)
|
||||||
|
5. **You'll be redirected** to the main application
|
||||||
|
|
||||||
|
### License Types
|
||||||
|
|
||||||
|
- **Lifetime License**: Never expires, unlimited use
|
||||||
|
- **Time-Limited License**: Valid for specified days (30, 90, 365, etc.)
|
||||||
|
|
||||||
|
### Session Duration
|
||||||
|
|
||||||
|
- **Active Session**: Remains active until you logout or clear browser data
|
||||||
|
- **Auto-Logout**: No automatic logout (session persists)
|
||||||
|
- **Manual Logout**: Click the logout button in the top-right corner
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Exporting Data from MT4/MT5
|
||||||
|
|
||||||
|
### Download Data Provider EA
|
||||||
|
|
||||||
|
1. **Download the EA** from the application:
|
||||||
|
- MT4: `FxMathQuant_DataExporter_MT4.ex4`
|
||||||
|
- MT5: `FxMathQuant_DataExporter_MT5.ex5`
|
||||||
|
|
||||||
|
2. **Install in MetaTrader**:
|
||||||
|
- Copy to `MQL4/Experts/` (MT4) or `MQL5/Experts/` (MT5)
|
||||||
|
- Restart MetaTrader
|
||||||
|
- Find in Navigator → Expert Advisors
|
||||||
|
|
||||||
|
### Export Historical Data
|
||||||
|
|
||||||
|
1. **Open a chart** (e.g., XAUUSD H1)
|
||||||
|
2. **Drag the EA** onto the chart
|
||||||
|
3. **Configure settings**:
|
||||||
|
- **Bars to Export**: 10,000 (recommended)
|
||||||
|
- **Remove Suffix**: true (removes broker suffix)
|
||||||
|
- **Export Folder**: FxMathQuant
|
||||||
|
4. **Click "Export to CSV"** button
|
||||||
|
5. **Wait for completion** message
|
||||||
|
6. **Find your file**: `MQL4/Files/FxMathQuant/XAUUSD_H1.csv`
|
||||||
|
|
||||||
|
### Recommended Data
|
||||||
|
|
||||||
|
| Timeframe | Bars | Use Case |
|
||||||
|
|-----------|------|----------|
|
||||||
|
| **M15** | 10,000 | Scalping strategies |
|
||||||
|
| **H1** | 10,000 | Intraday trading |
|
||||||
|
| **H4** | 5,000 | Swing trading |
|
||||||
|
| **D1** | 3,000 | Position trading |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📤 Uploading Data
|
||||||
|
|
||||||
|
### Step 1: Upload CSV File
|
||||||
|
|
||||||
|
1. **Click "Browse"** or drag-and-drop your CSV file
|
||||||
|
2. **Select the file** exported from MT4/MT5
|
||||||
|
3. **Wait for parsing** (10,000 bars ≈ 2-3 seconds)
|
||||||
|
4. **Confirmation**: "Data loaded successfully: X bars"
|
||||||
|
|
||||||
|
### Supported Format
|
||||||
|
|
||||||
|
```csv
|
||||||
|
Time,Open,High,Low,Close,Tick_Volume,Spread,Real_Volume
|
||||||
|
2024-04-18 01:00:00,2361.73,2367.69,2361.37,2365.29,2325,7,0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Column names can be lowercase or capitalized.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Configuring Strategy Generation
|
||||||
|
|
||||||
|
### Genetic Algorithm Settings
|
||||||
|
|
||||||
|
| Parameter | Default | Range | Description |
|
||||||
|
|-----------|---------|-------|-------------|
|
||||||
|
| **Population Size** | 100 | 50-500 | Number of strategies per generation |
|
||||||
|
| **Generations** | 50 | 10-200 | Number of evolution cycles |
|
||||||
|
| **Mutation Rate** | 0.1 | 0.01-0.5 | Probability of random changes |
|
||||||
|
| **Crossover Rate** | 0.7 | 0.3-0.9 | Probability of combining strategies |
|
||||||
|
| **Elite Size** | 5 | 1-20 | Best strategies preserved each generation |
|
||||||
|
|
||||||
|
### Strategy Criteria
|
||||||
|
|
||||||
|
| Criteria | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| **Min Profit Factor** | 1.5 | Minimum ratio of profit to loss |
|
||||||
|
| **Min Win Rate** | 50% | Minimum percentage of winning trades |
|
||||||
|
| **Min Total Trades** | 30 | Minimum number of trades |
|
||||||
|
| **Max Drawdown** | 30% | Maximum equity drawdown allowed |
|
||||||
|
|
||||||
|
### Recommended Settings
|
||||||
|
|
||||||
|
**For Beginners:**
|
||||||
|
- Population: 100
|
||||||
|
- Generations: 50
|
||||||
|
- Min Profit Factor: 1.5
|
||||||
|
- Min Win Rate: 50%
|
||||||
|
|
||||||
|
**For Advanced Users:**
|
||||||
|
- Population: 200-300
|
||||||
|
- Generations: 100-150
|
||||||
|
- Min Profit Factor: 2.0
|
||||||
|
- Min Win Rate: 55%
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Generating Strategies
|
||||||
|
|
||||||
|
### Step 1: Start Generation
|
||||||
|
|
||||||
|
1. **Review your settings**
|
||||||
|
2. **Click "Start Generation"**
|
||||||
|
3. **Monitor progress**:
|
||||||
|
- Current generation
|
||||||
|
- Strategies found
|
||||||
|
- Best fitness score
|
||||||
|
- Time elapsed
|
||||||
|
|
||||||
|
### Step 2: Wait for Completion
|
||||||
|
|
||||||
|
- **Time estimate**: 30 seconds to 5 minutes
|
||||||
|
- **Depends on**:
|
||||||
|
- Population size
|
||||||
|
- Number of generations
|
||||||
|
- Data size
|
||||||
|
- Computer speed
|
||||||
|
|
||||||
|
### Step 3: Review Results
|
||||||
|
|
||||||
|
- **Strategies found**: Number of strategies meeting criteria
|
||||||
|
- **Best strategy**: Highest fitness score
|
||||||
|
- **Generation**: When the strategy was discovered
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 Viewing Results
|
||||||
|
|
||||||
|
### Strategy List
|
||||||
|
|
||||||
|
Each strategy shows:
|
||||||
|
- **Profit Factor**: Ratio of gross profit to gross loss
|
||||||
|
- **Win Rate**: Percentage of winning trades
|
||||||
|
- **Total Trades**: Number of trades executed
|
||||||
|
- **Net Profit**: Total profit in currency
|
||||||
|
- **Max Drawdown**: Largest equity drop
|
||||||
|
|
||||||
|
### Strategy Details
|
||||||
|
|
||||||
|
Click "View Details" to see:
|
||||||
|
|
||||||
|
1. **Performance Metrics**:
|
||||||
|
- Profit Factor, Win Rate, Total Trades
|
||||||
|
- Net Profit, Max Drawdown, Recovery Factor
|
||||||
|
- Average Win/Loss, Largest Win/Loss
|
||||||
|
|
||||||
|
2. **BUY Rules**:
|
||||||
|
- Entry conditions (e.g., `RSI(14) < 30`)
|
||||||
|
- All rules must be true for BUY signal
|
||||||
|
|
||||||
|
3. **SELL Rules**:
|
||||||
|
- Entry conditions (inverted from BUY)
|
||||||
|
- All rules must be true for SELL signal
|
||||||
|
|
||||||
|
4. **Equity Curve**:
|
||||||
|
- Visual representation of account growth
|
||||||
|
- Shows drawdown periods
|
||||||
|
|
||||||
|
5. **Hourly Performance**:
|
||||||
|
- Best/worst trading hours
|
||||||
|
- Win rate by hour
|
||||||
|
- Profit by hour
|
||||||
|
|
||||||
|
6. **Trade Statement**:
|
||||||
|
- Complete list of all trades
|
||||||
|
- Entry/exit times and prices
|
||||||
|
- Profit/loss per trade
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💾 Downloading Strategies
|
||||||
|
|
||||||
|
### Available Formats
|
||||||
|
|
||||||
|
1. **MetaTrader 4 (.mq4)**:
|
||||||
|
- For MT4 platform
|
||||||
|
- Includes all input parameters
|
||||||
|
- Ready to compile and use
|
||||||
|
|
||||||
|
2. **MetaTrader 5 (.mq5)**:
|
||||||
|
- For MT5 platform
|
||||||
|
- Includes all input parameters
|
||||||
|
- Ready to compile and use
|
||||||
|
|
||||||
|
3. **cTrader (.cs)**:
|
||||||
|
- For cTrader platform
|
||||||
|
- C# source code
|
||||||
|
- Ready to compile
|
||||||
|
|
||||||
|
4. **TradingView (Pine Script)**:
|
||||||
|
- For TradingView platform
|
||||||
|
- Pine Script v5
|
||||||
|
- Ready to use
|
||||||
|
|
||||||
|
5. **HTML Report**:
|
||||||
|
- Comprehensive performance report
|
||||||
|
- Includes all charts and metrics
|
||||||
|
- Shareable and printable
|
||||||
|
|
||||||
|
6. **JSON Data**:
|
||||||
|
- Raw strategy data
|
||||||
|
- For custom integrations
|
||||||
|
- Machine-readable format
|
||||||
|
|
||||||
|
### How to Download
|
||||||
|
|
||||||
|
1. **Click "Download"** button on strategy card
|
||||||
|
2. **Select format** from dropdown
|
||||||
|
3. **File downloads** automatically
|
||||||
|
4. **Install in your platform**:
|
||||||
|
- MT4/MT5: Copy to `Experts/` folder
|
||||||
|
- cTrader: Import as cBot
|
||||||
|
- TradingView: Copy-paste Pine Script
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### CSV Upload Issues
|
||||||
|
|
||||||
|
**Error: "CSV must contain: time, open, high, low, close columns"**
|
||||||
|
- **Solution**: Ensure CSV has required columns
|
||||||
|
- **Check**: Column names can be lowercase or capitalized
|
||||||
|
- **Try**: Re-export from MT4/MT5 using DataProvider EA
|
||||||
|
|
||||||
|
**Error: "CSV file is empty"**
|
||||||
|
- **Solution**: Check file has data rows
|
||||||
|
- **Try**: Open CSV in Excel/Notepad to verify
|
||||||
|
|
||||||
|
### License Issues
|
||||||
|
|
||||||
|
**Error: "Invalid license key"**
|
||||||
|
- **Solution**: Check key format (32 characters with dashes)
|
||||||
|
- **Try**: Copy-paste from email (avoid typing)
|
||||||
|
|
||||||
|
**Error: "License has expired"**
|
||||||
|
- **Solution**: Contact support for renewal
|
||||||
|
- **Email**: fxmathsolution@gmail.com
|
||||||
|
|
||||||
|
**Error: "License validation failed"**
|
||||||
|
- **Solution**: Check internet connection
|
||||||
|
- **Try**: Disable VPN/proxy
|
||||||
|
- **Contact**: Support if issue persists
|
||||||
|
|
||||||
|
### Generation Issues
|
||||||
|
|
||||||
|
**No strategies found**
|
||||||
|
- **Solution**: Relax criteria (lower Min Profit Factor, Win Rate)
|
||||||
|
- **Try**: Increase population size and generations
|
||||||
|
- **Check**: Data quality (enough bars, no gaps)
|
||||||
|
|
||||||
|
**Generation too slow**
|
||||||
|
- **Solution**: Reduce population size and generations
|
||||||
|
- **Try**: Use smaller dataset (fewer bars)
|
||||||
|
- **Check**: Close other browser tabs
|
||||||
|
|
||||||
|
### Browser Issues
|
||||||
|
|
||||||
|
**Page not loading**
|
||||||
|
- **Solution**: Clear browser cache (Ctrl+Shift+R)
|
||||||
|
- **Try**: Use incognito/private mode
|
||||||
|
- **Check**: JavaScript is enabled
|
||||||
|
|
||||||
|
**Charts not displaying**
|
||||||
|
- **Solution**: Disable ad blockers
|
||||||
|
- **Try**: Different browser
|
||||||
|
- **Check**: Internet connection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Contact Support
|
||||||
|
|
||||||
|
### Get Help
|
||||||
|
|
||||||
|
**Email**: fxmathsolution@gmail.com
|
||||||
|
- Response time: 24-48 hours
|
||||||
|
- Include: License key, error message, screenshots
|
||||||
|
|
||||||
|
**Telegram**: https://t.me/FxMath
|
||||||
|
- Faster response
|
||||||
|
- Community support
|
||||||
|
- Updates and announcements
|
||||||
|
|
||||||
|
**Website**: https://fxmath.com
|
||||||
|
- Documentation
|
||||||
|
- Video tutorials
|
||||||
|
- FAQ
|
||||||
|
|
||||||
|
### Before Contacting Support
|
||||||
|
|
||||||
|
Please provide:
|
||||||
|
1. **License key** (first 8 characters)
|
||||||
|
2. **Error message** (exact text or screenshot)
|
||||||
|
3. **Browser** (Chrome, Firefox, etc.) and version
|
||||||
|
4. **Steps to reproduce** the issue
|
||||||
|
5. **CSV file** (if upload issue)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Additional Resources
|
||||||
|
|
||||||
|
### Video Tutorials
|
||||||
|
|
||||||
|
- **Getting Started**: https://fxmath.com/tutorials/getting-started
|
||||||
|
- **Data Export**: https://fxmath.com/tutorials/data-export
|
||||||
|
- **Strategy Generation**: https://fxmath.com/tutorials/generation
|
||||||
|
- **Advanced Settings**: https://fxmath.com/tutorials/advanced
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- **DataProvider EA Guide**: See `downloads/README.md`
|
||||||
|
- **API Documentation**: For developers
|
||||||
|
- **License System**: Setup and management
|
||||||
|
|
||||||
|
### Community
|
||||||
|
|
||||||
|
- **Telegram Group**: https://t.me/FxMath
|
||||||
|
- **Discord**: https://discord.gg/fxmath
|
||||||
|
- **Forum**: https://forum.fxmath.com
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Important Notes
|
||||||
|
|
||||||
|
### Disclaimer
|
||||||
|
|
||||||
|
- **Past performance** does not guarantee future results
|
||||||
|
- **Backtest results** may differ from live trading
|
||||||
|
- **Always test** strategies on demo account first
|
||||||
|
- **Risk management** is essential
|
||||||
|
- **No guarantee** of profitability
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
1. **Test thoroughly** on demo before live
|
||||||
|
2. **Use proper lot sizing** (1-2% risk per trade)
|
||||||
|
3. **Monitor performance** regularly
|
||||||
|
4. **Update data** monthly for fresh strategies
|
||||||
|
5. **Diversify** across multiple strategies and pairs
|
||||||
|
6. **Set stop-loss** on all trades
|
||||||
|
7. **Keep records** of all trades
|
||||||
|
|
||||||
|
### Data Privacy
|
||||||
|
|
||||||
|
- **All data** is processed locally in your browser
|
||||||
|
- **No data** is sent to external servers
|
||||||
|
- **License validation** only sends license key
|
||||||
|
- **CSV files** remain on your computer
|
||||||
|
- **Strategies** are yours to keep
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 Tips for Success
|
||||||
|
|
||||||
|
### Data Quality
|
||||||
|
|
||||||
|
- Use **quality broker** with good historical data
|
||||||
|
- Export **enough bars** (10,000+ recommended)
|
||||||
|
- Check for **data gaps** (weekends are normal)
|
||||||
|
- **Update regularly** (monthly) for current market conditions
|
||||||
|
|
||||||
|
### Strategy Selection
|
||||||
|
|
||||||
|
- **Don't overfit**: Avoid too many rules
|
||||||
|
- **Diversify**: Use multiple strategies
|
||||||
|
- **Validate**: Test on different time periods
|
||||||
|
- **Monitor**: Track live performance
|
||||||
|
|
||||||
|
### Risk Management
|
||||||
|
|
||||||
|
- **Never risk** more than 2% per trade
|
||||||
|
- **Use stop-loss** always
|
||||||
|
- **Position sizing**: Based on account size
|
||||||
|
- **Drawdown limit**: Stop trading at 20% drawdown
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy Trading! 🎯**
|
||||||
|
|
||||||
|
For support: fxmathsolution@gmail.com | https://t.me/FxMath
|
||||||
|
|
||||||
|
© 2025 FxMath Solution. All rights reserved.
|
||||||
File diff suppressed because it is too large
Load Diff
+817
@@ -0,0 +1,817 @@
|
|||||||
|
/* FxMathQuant Web - Modern Dark Theme */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg-primary: #0a0e27;
|
||||||
|
--bg-secondary: #151932;
|
||||||
|
--bg-card: #1a1f3a;
|
||||||
|
--bg-hover: #252b4a;
|
||||||
|
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #a0aec0;
|
||||||
|
--text-muted: #718096;
|
||||||
|
|
||||||
|
--accent-primary: #667eea;
|
||||||
|
--accent-secondary: #764ba2;
|
||||||
|
--accent-success: #48bb78;
|
||||||
|
--accent-danger: #f56565;
|
||||||
|
--accent-warning: #ed8936;
|
||||||
|
|
||||||
|
--border-color: #2d3748;
|
||||||
|
--shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||||
|
--shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
|
--font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
background: linear-gradient(135deg, var(--bg-primary) 0%, #1a1f3a 100%);
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-height: 100vh;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.header {
|
||||||
|
background: rgba(26, 31, 58, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
padding: 20px 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo .subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
.main-content {
|
||||||
|
padding: 40px 0;
|
||||||
|
min-height: calc(100vh - 200px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
display: none;
|
||||||
|
animation: fadeIn 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upload Zone */
|
||||||
|
.upload-zone {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 2px dashed var(--border-color);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 60px 40px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
background: var(--bg-hover);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone.dragover {
|
||||||
|
border-color: var(--accent-success);
|
||||||
|
background: rgba(72, 187, 120, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon {
|
||||||
|
font-size: 64px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data Preview */
|
||||||
|
.data-preview {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-preview.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-info span {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configuration */
|
||||||
|
.config-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-card h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group small {
|
||||||
|
display: block;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-inputs input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-inputs span {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn-primary,
|
||||||
|
.btn-secondary,
|
||||||
|
.btn-success,
|
||||||
|
.btn-danger {
|
||||||
|
padding: 12px 30px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: var(--accent-success);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--accent-danger);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover,
|
||||||
|
.btn-success:hover,
|
||||||
|
.btn-danger:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small:hover {
|
||||||
|
background: var(--accent-secondary);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress */
|
||||||
|
.progress-container {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-log {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-log::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-log::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-log::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--accent-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strategy Cards */
|
||||||
|
.strategies-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 25px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-metrics {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value.positive {
|
||||||
|
color: var(--accent-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value.negative {
|
||||||
|
color: var(--accent-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-actions button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 900px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 25px 30px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-grid-detailed {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-detailed {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-detailed .metric-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-detailed .metric-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-section h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-section canvas {
|
||||||
|
height: 300px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-section h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-list {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-item {
|
||||||
|
padding: 8px 0;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.parameters-box {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Select */
|
||||||
|
.form-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
padding: 30px 0;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.config-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategies-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-stats {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comparison View */
|
||||||
|
.comparison-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 20px 0;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comparison-item {
|
||||||
|
flex: 0 0 400px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 25px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comparison-item h3 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox Style */
|
||||||
|
.strategy-select {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--accent-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-card {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trades Table in Modal */
|
||||||
|
.trades-section {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trades-table-container {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trades-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trades-table th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: var(--bg-card);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trades-table td,
|
||||||
|
.trades-table th {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badge for trade types */
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes pulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,465 @@
|
|||||||
|
# FxMath Quant Data Exporter
|
||||||
|
|
||||||
|
**Export historical price data from MetaTrader 4/5 to CSV format for FxMath Quant Strategy Finder**
|
||||||
|
|
||||||
|
## 📋 Overview
|
||||||
|
|
||||||
|
These Expert Advisors (EAs) allow you to easily export historical price data from your MetaTrader platform in the exact format required by FxMath Quant Strategy Finder and Optimizer.
|
||||||
|
|
||||||
|
## 📦 What's Included
|
||||||
|
|
||||||
|
- **FxMathQuant_DataExporter_MT4.mq4** - Expert Advisor for MetaTrader 4
|
||||||
|
- **FxMathQuant_DataExporter_MT5.mq5** - Expert Advisor for MetaTrader 5
|
||||||
|
- **README.md** - This file with complete instructions
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
✅ **Easy to Use** - Just attach to chart and click export button (or press 'E' key)
|
||||||
|
✅ **Configurable Bars** - Set how many bars to export (default: 10,000)
|
||||||
|
✅ **Automatic Suffix Removal** - Removes broker suffixes (e.g., XAUUSDpro → XAUUSD)
|
||||||
|
✅ **Progress Tracking** - Shows export progress in Expert log
|
||||||
|
✅ **CSV Format** - Exact format required by FxMath Quant application
|
||||||
|
✅ **On-Chart Button** - Visual button to trigger export
|
||||||
|
✅ **Keyboard Shortcut** - Press 'E' key to export
|
||||||
|
✅ **Auto-Export Option** - Can export automatically when EA starts
|
||||||
|
|
||||||
|
## 📋 CSV Format Generated
|
||||||
|
|
||||||
|
The exported file follows this format (matching FxMath Quant requirements):
|
||||||
|
|
||||||
|
```csv
|
||||||
|
Time,Open,High,Low,Close,Tick_Volume,Spread,Real_Volume
|
||||||
|
2023-05-11 21:00:00,2013.87,2015.11,2013.40,2014.39,4858,24,0
|
||||||
|
2023-05-11 22:00:00,2014.40,2016.50,2013.41,2014.15,4271,24,0
|
||||||
|
2023-05-11 23:00:00,2014.17,2015.08,2013.68,2014.90,1616,24,0
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Installation
|
||||||
|
|
||||||
|
### For MetaTrader 4
|
||||||
|
|
||||||
|
1. **Copy the File**
|
||||||
|
- Copy `FxMathQuant_DataExporter_MT4.mq4` to:
|
||||||
|
- `MetaTrader 4/MQL4/Experts/`
|
||||||
|
|
||||||
|
2. **Compile**
|
||||||
|
- Open MetaEditor (F4 in MT4)
|
||||||
|
- Navigate to Experts folder
|
||||||
|
- Double-click `FxMathQuant_DataExporter_MT4.mq4`
|
||||||
|
- Click "Compile" (F7)
|
||||||
|
- Check for success message
|
||||||
|
|
||||||
|
3. **Verify Installation**
|
||||||
|
- In MT4, open Navigator (Ctrl+N)
|
||||||
|
- Expand "Expert Advisors"
|
||||||
|
- You should see "FxMathQuant_DataExporter_MT4"
|
||||||
|
|
||||||
|
### For MetaTrader 5
|
||||||
|
|
||||||
|
1. **Copy the File**
|
||||||
|
- Copy `FxMathQuant_DataExporter_MT5.mq5` to:
|
||||||
|
- `MetaTrader 5/MQL5/Experts/`
|
||||||
|
|
||||||
|
2. **Compile**
|
||||||
|
- Open MetaEditor (F4 in MT5)
|
||||||
|
- Navigate to Experts folder
|
||||||
|
- Double-click `FxMathQuant_DataExporter_MT5.mq5`
|
||||||
|
- Click "Compile" (F7)
|
||||||
|
- Check for success message
|
||||||
|
|
||||||
|
3. **Verify Installation**
|
||||||
|
- In MT5, open Navigator (Ctrl+N)
|
||||||
|
- Expand "Expert Advisors"
|
||||||
|
- You should see "FxMathQuant_DataExporter_MT5"
|
||||||
|
|
||||||
|
## 📖 Usage Instructions
|
||||||
|
|
||||||
|
### Method 1: Using On-Chart Button (Easiest)
|
||||||
|
|
||||||
|
1. **Open Chart**
|
||||||
|
- Open the chart for the symbol you want to export (e.g., XAUUSD, EURUSD)
|
||||||
|
- Select the timeframe (H1, H4, D1, etc.)
|
||||||
|
|
||||||
|
2. **Attach EA**
|
||||||
|
- Drag and drop the EA from Navigator onto the chart
|
||||||
|
- Or right-click chart → Expert Advisors → FxMathQuant_DataExporter_MT4/MT5
|
||||||
|
|
||||||
|
3. **Configure Settings** (Optional)
|
||||||
|
- In the EA parameters window:
|
||||||
|
- **BarsToExport**: Number of bars (default: 10000)
|
||||||
|
- **RemoveSuffix**: true/false (removes broker suffix)
|
||||||
|
- **ExportFolder**: Folder name (default: "FxMathQuant")
|
||||||
|
- **AutoExportOnStart**: false (set true for automatic export)
|
||||||
|
- **ShowSuccessMessage**: true (shows completion dialog)
|
||||||
|
- Click "OK"
|
||||||
|
|
||||||
|
4. **Export Data**
|
||||||
|
- Click the blue "Export Data to CSV" button that appears on the chart
|
||||||
|
- OR press the 'E' key on your keyboard
|
||||||
|
- Wait for completion message
|
||||||
|
|
||||||
|
5. **Find Your File**
|
||||||
|
- **MT4**: `MetaTrader 4/MQL4/Files/FxMathQuant/SYMBOL_TIMEFRAME.csv`
|
||||||
|
- **MT5**: `MetaTrader 5/MQL5/Files/FxMathQuant/SYMBOL_TIMEFRAME.csv`
|
||||||
|
- Example: `XAUUSD_H1.csv`, `EURUSD_M15.csv`
|
||||||
|
|
||||||
|
### Method 2: Auto-Export on Start
|
||||||
|
|
||||||
|
1. **Attach EA with Auto-Export Enabled**
|
||||||
|
- Drag EA to chart
|
||||||
|
- Set **AutoExportOnStart = true**
|
||||||
|
- Click "OK"
|
||||||
|
- Export starts immediately
|
||||||
|
|
||||||
|
2. **Wait for Completion**
|
||||||
|
- Check Expert log (Tools → Experts tab)
|
||||||
|
- Success message appears when done
|
||||||
|
|
||||||
|
### Method 3: Using Keyboard Shortcut
|
||||||
|
|
||||||
|
1. **Attach EA to Chart**
|
||||||
|
- Drag EA onto chart
|
||||||
|
- Click "OK" with default settings
|
||||||
|
|
||||||
|
2. **Press 'E' Key**
|
||||||
|
- Make sure the chart is active
|
||||||
|
- Press the 'E' key on your keyboard
|
||||||
|
- Export begins immediately
|
||||||
|
|
||||||
|
## ⚙️ Parameter Reference
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| **BarsToExport** | int | 10000 | Number of historical bars to export |
|
||||||
|
| **RemoveSuffix** | bool | true | Remove broker suffix from symbol name |
|
||||||
|
| **ExportFolder** | string | "FxMathQuant" | Subfolder in MQL4/5/Files/ for exports |
|
||||||
|
| **AutoExportOnStart** | bool | false | Export automatically when EA starts |
|
||||||
|
| **ShowSuccessMessage** | bool | true | Show popup when export completes |
|
||||||
|
|
||||||
|
## 📁 File Naming Convention
|
||||||
|
|
||||||
|
The exported files are automatically named using this format:
|
||||||
|
|
||||||
|
```
|
||||||
|
{SYMBOL}_{TIMEFRAME}.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `XAUUSD_H1.csv` - Gold on 1-hour chart
|
||||||
|
- `EURUSD_M15.csv` - EUR/USD on 15-minute chart
|
||||||
|
- `BTCUSD_D1.csv` - Bitcoin on daily chart
|
||||||
|
|
||||||
|
### Suffix Removal Examples
|
||||||
|
|
||||||
|
When **RemoveSuffix = true**, broker-specific suffixes are automatically removed:
|
||||||
|
|
||||||
|
| Broker Symbol | Exported As |
|
||||||
|
|---------------|-------------|
|
||||||
|
| XAUUSDpro | XAUUSD |
|
||||||
|
| EURUSD.ecn | EURUSD |
|
||||||
|
| GBPUSD.lmax | GBPUSD |
|
||||||
|
| BTCUSDm | BTCUSD |
|
||||||
|
| USDJPY.raw | USDJPY |
|
||||||
|
|
||||||
|
## 🔍 Supported Timeframes
|
||||||
|
|
||||||
|
### MetaTrader 4
|
||||||
|
- M1, M5, M15, M30 (minutes)
|
||||||
|
- H1, H4 (hours)
|
||||||
|
- D1 (daily)
|
||||||
|
- W1 (weekly)
|
||||||
|
- MN1 (monthly)
|
||||||
|
|
||||||
|
### MetaTrader 5
|
||||||
|
- M1, M2, M3, M4, M5, M6, M10, M12, M15, M20, M30 (minutes)
|
||||||
|
- H1, H2, H3, H4, H6, H8, H12 (hours)
|
||||||
|
- D1 (daily)
|
||||||
|
- W1 (weekly)
|
||||||
|
- MN1 (monthly)
|
||||||
|
|
||||||
|
## 📊 What Data is Exported
|
||||||
|
|
||||||
|
Each exported bar contains:
|
||||||
|
|
||||||
|
1. **Time** - Bar opening time (YYYY-MM-DD HH:MM:SS format)
|
||||||
|
2. **Open** - Opening price
|
||||||
|
3. **High** - Highest price
|
||||||
|
4. **Low** - Lowest price
|
||||||
|
5. **Close** - Closing price
|
||||||
|
6. **Tick_Volume** - Number of ticks (always available)
|
||||||
|
7. **Spread** - Bid/Ask spread in points
|
||||||
|
8. **Real_Volume** - Exchange volume (0 if not available for symbol)
|
||||||
|
|
||||||
|
## 💡 Tips for Best Results
|
||||||
|
|
||||||
|
### Recommended Number of Bars
|
||||||
|
|
||||||
|
| Use Case | Recommended Bars |
|
||||||
|
|----------|------------------|
|
||||||
|
| **Quick Testing** | 1,000 - 3,000 |
|
||||||
|
| **Standard Analysis** | 5,000 - 10,000 |
|
||||||
|
| **Comprehensive Analysis** | 15,000 - 30,000 |
|
||||||
|
| **Maximum Data** | 50,000+ |
|
||||||
|
|
||||||
|
**Note:** More bars = more reliable backtesting results, but slower processing in FxMath Quant.
|
||||||
|
|
||||||
|
### Best Timeframes for Strategy Development
|
||||||
|
|
||||||
|
- **H1 (1 Hour)** - Good balance of data and trading frequency
|
||||||
|
- **H4 (4 Hours)** - Swing trading strategies
|
||||||
|
- **D1 (Daily)** - Position trading strategies
|
||||||
|
- **M15 (15 Minutes)** - Short-term trading strategies
|
||||||
|
|
||||||
|
### Data Quality Tips
|
||||||
|
|
||||||
|
✅ **Use Quality Brokers** - Choose brokers with good historical data
|
||||||
|
✅ **Check Data Gaps** - Weekends and holidays create gaps (normal)
|
||||||
|
✅ **Download More Than Needed** - Export 20-30% more bars as buffer
|
||||||
|
✅ **Update Regularly** - Re-export data monthly for latest market conditions
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Issue: "Failed to create file"
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- Permissions issue
|
||||||
|
- Folder doesn't exist
|
||||||
|
- Disk full
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Check MT4/MT5 has write permissions
|
||||||
|
2. Try changing `ExportFolder` to empty string ""
|
||||||
|
3. Free up disk space
|
||||||
|
4. Run MT4/MT5 as administrator
|
||||||
|
|
||||||
|
### Issue: "Not enough bars available"
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- Symbol doesn't have enough historical data
|
||||||
|
- Data not downloaded yet
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. In MT4/MT5, press F2 to open History Center
|
||||||
|
2. Select the symbol and timeframe
|
||||||
|
3. Download more history from broker
|
||||||
|
4. Reduce `BarsToExport` parameter
|
||||||
|
|
||||||
|
### Issue: "Suffix not removed correctly"
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- Unusual broker suffix not in the list
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Set `RemoveSuffix = false`
|
||||||
|
2. Manually rename the file after export
|
||||||
|
3. Contact support to add your broker's suffix
|
||||||
|
|
||||||
|
### Issue: Export button doesn't appear
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- EA not attached correctly
|
||||||
|
- AutoTrading disabled
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Enable AutoTrading (Ctrl+E or click button in toolbar)
|
||||||
|
2. Re-attach EA to chart
|
||||||
|
3. Check Expert log for error messages
|
||||||
|
|
||||||
|
### Issue: CSV format not recognized by FxMath Quant
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- Different locale/region settings
|
||||||
|
- Decimal separator issues
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Check exported file opens correctly in Excel/Notepad
|
||||||
|
2. Verify format matches: `Time,Open,High,Low,Close,Tick_Volume,Spread,Real_Volume`
|
||||||
|
3. Ensure decimal separator is period (.) not comma (,)
|
||||||
|
|
||||||
|
## 📂 Example Workflow
|
||||||
|
|
||||||
|
### Complete Export Process
|
||||||
|
|
||||||
|
1. **Open MetaTrader**
|
||||||
|
- Launch MT4 or MT5
|
||||||
|
|
||||||
|
2. **Select Symbol and Timeframe**
|
||||||
|
- Open XAUUSD chart
|
||||||
|
- Switch to H1 timeframe
|
||||||
|
|
||||||
|
3. **Attach EA**
|
||||||
|
- Drag `FxMathQuant_DataExporter_MT4` to chart
|
||||||
|
- Set BarsToExport = 10000
|
||||||
|
- Click OK
|
||||||
|
|
||||||
|
4. **Export Data**
|
||||||
|
- Click "Export Data to CSV" button
|
||||||
|
- Wait ~5-10 seconds (for 10,000 bars)
|
||||||
|
|
||||||
|
5. **Verify Export**
|
||||||
|
- Check success message
|
||||||
|
- Note file location
|
||||||
|
|
||||||
|
6. **Use in FxMath Quant**
|
||||||
|
- Copy `XAUUSD_H1.csv` to your data folder
|
||||||
|
- Or load directly in FxMath Quant application
|
||||||
|
|
||||||
|
7. **Repeat for Other Symbols/Timeframes**
|
||||||
|
- EURUSD H1
|
||||||
|
- GBPUSD H4
|
||||||
|
- BTCUSD D1
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
## 🔄 Updating Data
|
||||||
|
|
||||||
|
### When to Re-Export
|
||||||
|
|
||||||
|
- **Monthly**: For active strategy development
|
||||||
|
- **After Major Events**: Brexit, elections, etc.
|
||||||
|
- **When Results Degrade**: Market conditions changed
|
||||||
|
- **Before Going Live**: Use latest data for final validation
|
||||||
|
|
||||||
|
### Quick Update Process
|
||||||
|
|
||||||
|
1. Set `AutoExportOnStart = true`
|
||||||
|
2. Attach EA to multiple charts
|
||||||
|
3. Wait for all exports to complete
|
||||||
|
4. Copy files to FxMath Quant data folder
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
1. **Check Expert Log**
|
||||||
|
- In MT4/MT5: View → Toolbox → Experts tab
|
||||||
|
- Look for error messages
|
||||||
|
- Note the error code
|
||||||
|
|
||||||
|
2. **Common Error Codes**
|
||||||
|
- **4103**: File cannot be opened
|
||||||
|
- **4051**: Invalid function parameter
|
||||||
|
- **5004**: History not loaded
|
||||||
|
|
||||||
|
3. **Contact Support**
|
||||||
|
- Email: support@fxmathquant.com
|
||||||
|
- Include: Symbol, Timeframe, Error message, MT4/MT5 version
|
||||||
|
|
||||||
|
## 📄 Technical Details
|
||||||
|
|
||||||
|
### File Format Specification
|
||||||
|
|
||||||
|
```
|
||||||
|
Header: Time,Open,High,Low,Close,Tick_Volume,Spread,Real_Volume
|
||||||
|
Data: YYYY-MM-DD HH:MM:SS,FLOAT,FLOAT,FLOAT,FLOAT,INT,INT,INT
|
||||||
|
Sorting: Chronological (oldest to newest)
|
||||||
|
Encoding: ANSI/UTF-8
|
||||||
|
Separator: Comma (,)
|
||||||
|
Line Ending: CRLF (Windows)
|
||||||
|
Decimal: Period (.)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
| Bars | Export Time | File Size |
|
||||||
|
|------|-------------|-----------|
|
||||||
|
| 1,000 | 1-2 seconds | ~100 KB |
|
||||||
|
| 10,000 | 5-10 seconds | ~1 MB |
|
||||||
|
| 50,000 | 30-60 seconds | ~5 MB |
|
||||||
|
| 100,000 | 1-2 minutes | ~10 MB |
|
||||||
|
|
||||||
|
**Note:** Times vary based on CPU speed and disk I/O.
|
||||||
|
|
||||||
|
## 📋 Checklist - First Time Export
|
||||||
|
|
||||||
|
Before your first export, make sure:
|
||||||
|
|
||||||
|
- [ ] EA file copied to correct MQL4/Experts or MQL5/Experts folder
|
||||||
|
- [ ] EA compiled successfully in MetaEditor
|
||||||
|
- [ ] AutoTrading enabled in MT4/MT5 (Ctrl+E)
|
||||||
|
- [ ] Chart opened for desired symbol
|
||||||
|
- [ ] Correct timeframe selected
|
||||||
|
- [ ] Historical data downloaded (F2 → History Center)
|
||||||
|
- [ ] Sufficient disk space available
|
||||||
|
- [ ] EA attached to chart successfully
|
||||||
|
- [ ] Export button visible on chart
|
||||||
|
|
||||||
|
## 🎓 Advanced Usage
|
||||||
|
|
||||||
|
### Exporting Multiple Symbols
|
||||||
|
|
||||||
|
Create a script to attach EA to multiple charts:
|
||||||
|
|
||||||
|
1. Open multiple charts (EURUSD, GBPUSD, XAUUSD, etc.)
|
||||||
|
2. Set `AutoExportOnStart = true`
|
||||||
|
3. Attach EA to all charts
|
||||||
|
4. All exports run automatically
|
||||||
|
|
||||||
|
### Custom Export Folders
|
||||||
|
|
||||||
|
Organize by strategy or date:
|
||||||
|
|
||||||
|
```
|
||||||
|
ExportFolder = "FxMathQuant/Strategy1"
|
||||||
|
ExportFolder = "FxMathQuant/2025-12"
|
||||||
|
ExportFolder = "FxMathQuant/Forex"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Processing
|
||||||
|
|
||||||
|
For power users:
|
||||||
|
|
||||||
|
1. Create template with EA attached
|
||||||
|
2. Apply template to multiple charts
|
||||||
|
3. Set `AutoExportOnStart = true`
|
||||||
|
4. All charts export simultaneously
|
||||||
|
|
||||||
|
## 🔒 Data Privacy
|
||||||
|
|
||||||
|
- All data is exported **locally** to your computer
|
||||||
|
- No data is sent to external servers
|
||||||
|
- No internet connection required for export
|
||||||
|
- Files remain in your MT4/MT5 Files folder
|
||||||
|
|
||||||
|
## 📜 License
|
||||||
|
|
||||||
|
© 2025 FxMath Quant. All rights reserved.
|
||||||
|
|
||||||
|
This software is provided as a free tool for FxMath Quant users.
|
||||||
|
|
||||||
|
## 🚀 Version History
|
||||||
|
|
||||||
|
### Version 1.0 (December 2025)
|
||||||
|
- Initial release
|
||||||
|
- MT4 and MT5 support
|
||||||
|
- Automatic suffix removal
|
||||||
|
- On-chart button
|
||||||
|
- Keyboard shortcut support
|
||||||
|
- Configurable parameters
|
||||||
|
- Progress tracking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Quick Reference
|
||||||
|
|
||||||
|
### Common Commands
|
||||||
|
|
||||||
|
| Action | Method 1 | Method 2 |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| **Export** | Click button on chart | Press 'E' key |
|
||||||
|
| **Re-export** | Click button again | Restart EA |
|
||||||
|
| **Find file** | MQL4/5/Files/FxMathQuant/ | Check success message |
|
||||||
|
| **Change bars** | Edit EA settings | Detach and re-attach EA |
|
||||||
|
|
||||||
|
### Default File Locations
|
||||||
|
|
||||||
|
**MT4:** `C:\Users\YourName\AppData\Roaming\MetaQuotes\Terminal\{ID}\MQL4\Files\FxMathQuant\`
|
||||||
|
|
||||||
|
**MT5:** `C:\Users\YourName\AppData\Roaming\MetaQuotes\Terminal\{ID}\MQL5\Files\FxMathQuant\`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy Trading! 🎯**
|
||||||
|
|
||||||
|
For more information about FxMath Quant Strategy Finder and Optimizer, visit:
|
||||||
|
https://www.fxmathquant.com
|
||||||
+306
@@ -0,0 +1,306 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FxMathQuant Strategy Generator - AI-Powered Trading Strategies</title>
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="assets/favicon.png">
|
||||||
|
|
||||||
|
<!-- Fonts -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Chart.js -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
|
|
||||||
|
<!-- PapaParse for CSV -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
|
||||||
|
|
||||||
|
<!-- License Check (must load first) -->
|
||||||
|
<script src="js/license-check.js"></script>
|
||||||
|
|
||||||
|
<!-- Styles -->
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="logo">
|
||||||
|
<h1>FxMathQuant</h1>
|
||||||
|
<span class="subtitle">AI Strategy Generator</span>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<button id="theme-toggle" class="btn-icon" title="Toggle Theme">
|
||||||
|
<span class="icon">🌙</span>
|
||||||
|
</button>
|
||||||
|
<button id="logout-btn" class="btn-icon" title="Logout" onclick="logoutLicense()"
|
||||||
|
style="margin-left: 10px;">
|
||||||
|
<span class="icon">🚪</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Step 1: Upload Data -->
|
||||||
|
<section id="upload-section" class="section active">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Step 1: Upload OHLC Data</h2>
|
||||||
|
<p>Upload CSV file exported from MT4/MT5 (Time, Open, High, Low, Close)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="upload-zone" id="upload-zone">
|
||||||
|
<div class="upload-icon">📁</div>
|
||||||
|
<h3>Drag & Drop CSV File</h3>
|
||||||
|
<p>or click to browse</p>
|
||||||
|
<input type="file" id="file-input" accept=".csv" hidden>
|
||||||
|
<button class="btn-primary" id="browse-btn">Browse Files</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DataProvider Download Links -->
|
||||||
|
<div
|
||||||
|
style="text-align: center; margin-top: 20px; padding: 15px; background: rgba(102, 126, 234, 0.1); border-radius: 10px;">
|
||||||
|
<p style="margin-bottom: 10px; font-weight: 600;">📥 Need to export data from MT4/MT5?</p>
|
||||||
|
<div style="display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;">
|
||||||
|
<a href="downloads/FxMathQuant_DataExporter_MT4.ex4" download class="btn-secondary"
|
||||||
|
style="text-decoration: none; padding: 8px 16px; background: #667eea; color: white; border-radius: 6px; font-size: 14px;">
|
||||||
|
📊 Download MT4 EA
|
||||||
|
</a>
|
||||||
|
<a href="downloads/FxMathQuant_DataExporter_MT5.ex5" download class="btn-secondary"
|
||||||
|
style="text-decoration: none; padding: 8px 16px; background: #667eea; color: white; border-radius: 6px; font-size: 14px;">
|
||||||
|
📊 Download MT5 EA
|
||||||
|
</a>
|
||||||
|
<a href="downloads/README.md" download class="btn-secondary"
|
||||||
|
style="text-decoration: none; padding: 8px 16px; background: #764ba2; color: white; border-radius: 6px; font-size: 14px;">
|
||||||
|
📖 EA Guide
|
||||||
|
</a>
|
||||||
|
<a href="manual.html" target="_blank" class="btn-secondary"
|
||||||
|
style="text-decoration: none; padding: 8px 16px; background: #28a745; color: white; border-radius: 6px; font-size: 14px;">
|
||||||
|
📚 User Manual
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="data-preview" class="data-preview hidden">
|
||||||
|
<h3>Data Preview</h3>
|
||||||
|
<div class="data-info">
|
||||||
|
<span id="data-symbol">Symbol: -</span>
|
||||||
|
<span id="data-bars">Bars: -</span>
|
||||||
|
<span id="data-timeframe">Timeframe: -</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-container">
|
||||||
|
<table id="preview-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Open</th>
|
||||||
|
<th>High</th>
|
||||||
|
<th>Low</th>
|
||||||
|
<th>Close</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="preview-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-top: 20px;">
|
||||||
|
<label>Data Size Limit (bars)</label>
|
||||||
|
<select id="data-size-limit" class="form-select">
|
||||||
|
<option value="0">Use All Data</option>
|
||||||
|
<option value="1000">Last 1,000 bars</option>
|
||||||
|
<option value="2000">Last 2,000 bars</option>
|
||||||
|
<option value="5000" selected>Last 5,000 bars (Recommended)</option>
|
||||||
|
<option value="10000">Last 10,000 bars</option>
|
||||||
|
</select>
|
||||||
|
<small>Limit data size for faster processing. Recommended: 5,000 bars</small>
|
||||||
|
</div>
|
||||||
|
<button class="btn-success" id="continue-btn">Continue to Configuration →</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Step 2: Configure GA -->
|
||||||
|
<section id="config-section" class="section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Step 2: Configure Genetic Algorithm</h2>
|
||||||
|
<p>Set parameters for strategy generation</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-grid">
|
||||||
|
<!-- GA Parameters -->
|
||||||
|
<div class="config-card">
|
||||||
|
<h3>GA Parameters</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Population Size</label>
|
||||||
|
<input type="number" id="population" value="100" min="50" max="500">
|
||||||
|
<small>Number of strategies per generation (50-500)</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Generations</label>
|
||||||
|
<input type="number" id="generations" value="50" min="10" max="200">
|
||||||
|
<small>Evolution cycles (10-200)</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Strategies to Find</label>
|
||||||
|
<input type="number" id="strategies-count" value="5" min="1" max="20">
|
||||||
|
<small>Target number of strategies (1-20)</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rule Configuration -->
|
||||||
|
<div class="config-card">
|
||||||
|
<h3>Rule Configuration</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Rules per Strategy</label>
|
||||||
|
<div class="range-inputs">
|
||||||
|
<input type="number" id="rules-min" value="3" min="1" max="10">
|
||||||
|
<span>to</span>
|
||||||
|
<input type="number" id="rules-max" value="8" min="1" max="10">
|
||||||
|
</div>
|
||||||
|
<small>Number of conditions (1-10)</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Lookback Period</label>
|
||||||
|
<div class="range-inputs">
|
||||||
|
<input type="number" id="shift-min" value="1" min="1" max="20">
|
||||||
|
<span>to</span>
|
||||||
|
<input type="number" id="shift-max" value="10" min="1" max="20">
|
||||||
|
</div>
|
||||||
|
<small>Bars to look back (1-20)</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Performance Filters -->
|
||||||
|
<div class="config-card">
|
||||||
|
<h3>Performance Filters</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Min Profit Factor</label>
|
||||||
|
<input type="number" id="min-pf" value="1.5" step="0.1" min="1.0" max="5.0">
|
||||||
|
<small>Minimum PF required (1.0-5.0)</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Min Win Rate (%)</label>
|
||||||
|
<input type="number" id="min-wr" value="45" min="30" max="80">
|
||||||
|
<small>Minimum win rate (30-80%)</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Max Drawdown (%)</label>
|
||||||
|
<input type="number" id="max-dd" value="25" min="5" max="50">
|
||||||
|
<small>Maximum acceptable DD (5-50%)</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Min Trades</label>
|
||||||
|
<input type="number" id="min-trades" value="30" min="10" max="200">
|
||||||
|
<small>Minimum trade count (10-200)</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn-secondary" id="back-btn">← Back to Upload</button>
|
||||||
|
<button class="btn-success" id="start-btn">Start Generation 🚀</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Step 3: Generation Progress -->
|
||||||
|
<section id="progress-section" class="section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Step 3: Generating Strategies</h2>
|
||||||
|
<p>AI is evolving profitable trading strategies...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-stats">
|
||||||
|
<div class="stat-card">
|
||||||
|
<span class="stat-label">Current Generation</span>
|
||||||
|
<span class="stat-value" id="current-gen">0 / 0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<span class="stat-label">Best Fitness</span>
|
||||||
|
<span class="stat-value" id="best-fitness">0.00</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<span class="stat-label">Strategies Found</span>
|
||||||
|
<span class="stat-value" id="found-count">0 / 0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<span class="stat-label">Elapsed Time</span>
|
||||||
|
<span class="stat-value" id="elapsed-time">0s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-bar-container">
|
||||||
|
<div class="progress-bar" id="progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-log" id="progress-log"></div>
|
||||||
|
|
||||||
|
<button class="btn-danger" id="stop-btn">Stop Generation</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Step 4: Results -->
|
||||||
|
<section id="results-section" class="section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Step 4: Generated Strategies</h2>
|
||||||
|
<p>Your AI-discovered trading strategies are ready!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="strategies-grid" class="strategies-grid"></div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn-secondary" id="new-search-btn">← New Search</button>
|
||||||
|
<button class="btn-primary" id="compare-btn">Compare Selected (0)</button>
|
||||||
|
<button class="btn-success" id="download-all-btn">Download All Strategies</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comparison Results Area (Hidden by default) -->
|
||||||
|
<div id="comparison-section" class="hidden"
|
||||||
|
style="margin-top: 40px; padding-top: 40px; border-top: 1px solid var(--border-color);">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Strategy Comparison</h2>
|
||||||
|
<p>Side-by-side performance analysis</p>
|
||||||
|
</div>
|
||||||
|
<div id="comparison-container" class="comparison-container"></div>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn-secondary" onclick="hideComparison()">Close Comparison</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<p>© 2025 FxMathQuant. All rights reserved. | AI-Powered Trading Strategy Generator</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="js/strategy.js"></script>
|
||||||
|
<script src="js/backtester.js"></script>
|
||||||
|
<script src="js/ga-engine.js"></script>
|
||||||
|
<script src="js/rule-parser.js"></script>
|
||||||
|
<script src="js/mq4-converter.js"></script>
|
||||||
|
<script src="js/mq5-converter.js"></script>
|
||||||
|
<script src="js/ctrader-converter.js"></script>
|
||||||
|
<script src="js/pine-converter.js"></script>
|
||||||
|
<script src="js/mq4-generator.js"></script>
|
||||||
|
<script src="js/mq5-generator.js"></script>
|
||||||
|
<script src="js/report-generator.js"></script>
|
||||||
|
<script src="js/strategy-details.js"></script>
|
||||||
|
<script src="js/html-report-generator.js"></script>
|
||||||
|
<script src="js/ui-controller.js"></script>
|
||||||
|
<script src="js/main.js?v=2.0"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,464 @@
|
|||||||
|
/**
|
||||||
|
* Backtester - Fast backtesting engine using typed arrays
|
||||||
|
*/
|
||||||
|
class Backtester {
|
||||||
|
constructor(data, strategy) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
this.dataLength = data.length;
|
||||||
|
|
||||||
|
// Use typed arrays for performance
|
||||||
|
this.open = new Float64Array(data.length);
|
||||||
|
this.high = new Float64Array(data.length);
|
||||||
|
this.low = new Float64Array(data.length);
|
||||||
|
this.close = new Float64Array(data.length);
|
||||||
|
this.time = new Array(data.length);
|
||||||
|
|
||||||
|
// Populate arrays
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
this.open[i] = parseFloat(data[i].open) || 0;
|
||||||
|
this.high[i] = parseFloat(data[i].high) || 0;
|
||||||
|
this.low[i] = parseFloat(data[i].low) || 0;
|
||||||
|
this.close[i] = parseFloat(data[i].close) || 0;
|
||||||
|
this.time[i] = data[i].time || i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run backtest and return metrics
|
||||||
|
*/
|
||||||
|
run() {
|
||||||
|
console.log('📊 Backtester.run() - Data length:', this.dataLength);
|
||||||
|
const atr = this.calculateATR(this.strategy.atrPeriod);
|
||||||
|
console.log('✅ ATR calculated, period:', this.strategy.atrPeriod);
|
||||||
|
|
||||||
|
let balance = 10000;
|
||||||
|
let position = null;
|
||||||
|
const trades = [];
|
||||||
|
const equity = [balance];
|
||||||
|
|
||||||
|
// Start from ATR period to have enough data
|
||||||
|
for (let i = this.strategy.atrPeriod + 10; i < this.dataLength; i++) {
|
||||||
|
// Check for close at opposite signal first (if enabled)
|
||||||
|
if (position && this.strategy.closeAtOpposite) {
|
||||||
|
if (position.type === 'BUY' && this.checkSellSignal(i)) {
|
||||||
|
// Close BUY and open SELL
|
||||||
|
const closePrice = this.close[i];
|
||||||
|
const profit = closePrice - position.entry;
|
||||||
|
balance += profit;
|
||||||
|
trades.push({
|
||||||
|
type: 'BUY',
|
||||||
|
entry: position.entry,
|
||||||
|
exit: closePrice,
|
||||||
|
profit: profit,
|
||||||
|
reason: 'opposite',
|
||||||
|
openTime: position.openTime,
|
||||||
|
closeTime: this.time[i]
|
||||||
|
});
|
||||||
|
equity.push(balance);
|
||||||
|
position = this.openPosition('SELL', i, atr[i]);
|
||||||
|
continue;
|
||||||
|
} else if (position.type === 'SELL' && this.checkBuySignal(i)) {
|
||||||
|
// Close SELL and open BUY
|
||||||
|
const closePrice = this.close[i];
|
||||||
|
const profit = position.entry - closePrice;
|
||||||
|
balance += profit;
|
||||||
|
trades.push({
|
||||||
|
type: 'SELL',
|
||||||
|
entry: position.entry,
|
||||||
|
exit: closePrice,
|
||||||
|
profit: profit,
|
||||||
|
reason: 'opposite',
|
||||||
|
openTime: position.openTime,
|
||||||
|
closeTime: this.time[i]
|
||||||
|
});
|
||||||
|
equity.push(balance);
|
||||||
|
position = this.openPosition('BUY', i, atr[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for normal exit (TP/SL)
|
||||||
|
if (position) {
|
||||||
|
const exitResult = this.checkExit(position, i);
|
||||||
|
if (exitResult) {
|
||||||
|
balance += exitResult.profit;
|
||||||
|
trades.push(exitResult);
|
||||||
|
equity.push(balance);
|
||||||
|
position = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for entry signals (only if no position)
|
||||||
|
if (!position) {
|
||||||
|
if (this.checkBuySignal(i)) {
|
||||||
|
position = this.openPosition('BUY', i, atr[i]);
|
||||||
|
} else if (this.checkSellSignal(i)) {
|
||||||
|
position = this.openPosition('SELL', i, atr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close any open position at the end
|
||||||
|
if (position) {
|
||||||
|
const exitPrice = this.close[this.dataLength - 1];
|
||||||
|
const profit = exitPrice - position.entry;
|
||||||
|
balance += profit;
|
||||||
|
trades.push({
|
||||||
|
type: 'BUY',
|
||||||
|
entry: position.entry,
|
||||||
|
exit: exitPrice,
|
||||||
|
profit: profit,
|
||||||
|
reason: 'end_of_data'
|
||||||
|
});
|
||||||
|
equity.push(balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.calculateMetrics(trades, balance, equity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if BUY signal is triggered
|
||||||
|
*/
|
||||||
|
checkBuySignal(index) {
|
||||||
|
// All rules must be true
|
||||||
|
for (const rule of this.strategy.rules) {
|
||||||
|
if (!this.evaluateRule(rule, index)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if SELL signal is triggered (symmetrical to BUY with inverted operators)
|
||||||
|
*/
|
||||||
|
checkSellSignal(index) {
|
||||||
|
// All SELL rules must be true (SELL rules = BUY rules with inverted operators)
|
||||||
|
for (const rule of this.strategy.rules) {
|
||||||
|
if (!this.evaluateSellRule(rule, index)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a SELL rule (BUY rule with inverted operator)
|
||||||
|
*/
|
||||||
|
evaluateSellRule(rule, index) {
|
||||||
|
let leftValue, rightValue;
|
||||||
|
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
leftValue = this.getPriceValue(rule.left.price, index - rule.left.shift);
|
||||||
|
rightValue = this.getPriceValue(rule.right.price, index - rule.right.shift);
|
||||||
|
} else {
|
||||||
|
// Arithmetic rule
|
||||||
|
const left1 = this.getPriceValue(rule.left.price1, index - rule.left.shift1);
|
||||||
|
const left2 = this.getPriceValue(rule.left.price2, index - rule.left.shift2);
|
||||||
|
const rightPrice = this.getPriceValue(rule.right.price, index - rule.right.shift);
|
||||||
|
|
||||||
|
switch (rule.left.op) {
|
||||||
|
case '+': leftValue = left1 + left2; break;
|
||||||
|
case '-': leftValue = left1 - left2; break;
|
||||||
|
case '*': leftValue = left1 * left2; break;
|
||||||
|
default: leftValue = left1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rightValue = rightPrice * rule.right.multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert the operator for SELL
|
||||||
|
switch (rule.operator) {
|
||||||
|
case '>': return leftValue <= rightValue;
|
||||||
|
case '<': return leftValue >= rightValue;
|
||||||
|
case '>=': return leftValue < rightValue;
|
||||||
|
case '<=': return leftValue > rightValue;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a single rule
|
||||||
|
*/
|
||||||
|
evaluateRule(rule, index) {
|
||||||
|
let leftValue, rightValue;
|
||||||
|
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
leftValue = this.getPriceValue(rule.left.price, index - rule.left.shift);
|
||||||
|
rightValue = this.getPriceValue(rule.right.price, index - rule.right.shift);
|
||||||
|
} else {
|
||||||
|
// Arithmetic rule
|
||||||
|
const left1 = this.getPriceValue(rule.left.price1, index - rule.left.shift1);
|
||||||
|
const left2 = this.getPriceValue(rule.left.price2, index - rule.left.shift2);
|
||||||
|
|
||||||
|
switch (rule.left.op) {
|
||||||
|
case '+': leftValue = left1 + left2; break;
|
||||||
|
case '-': leftValue = left1 - left2; break;
|
||||||
|
case '*': leftValue = left1 * left2; break;
|
||||||
|
default: leftValue = left1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rightPrice = this.getPriceValue(rule.right.price, index - rule.right.shift);
|
||||||
|
rightValue = rightPrice * rule.right.multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate operator
|
||||||
|
switch (rule.operator) {
|
||||||
|
case '>': return leftValue > rightValue;
|
||||||
|
case '<': return leftValue < rightValue;
|
||||||
|
case '>=': return leftValue >= rightValue;
|
||||||
|
case '<=': return leftValue <= rightValue;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get price value at specific index
|
||||||
|
*/
|
||||||
|
getPriceValue(priceType, index) {
|
||||||
|
if (index < 0 || index >= this.dataLength) return 0;
|
||||||
|
|
||||||
|
switch (priceType) {
|
||||||
|
case 'open': return this.open[index];
|
||||||
|
case 'high': return this.high[index];
|
||||||
|
case 'low': return this.low[index];
|
||||||
|
case 'close': return this.close[index];
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a new position
|
||||||
|
*/
|
||||||
|
openPosition(type, index, atr) {
|
||||||
|
const entry = this.close[index];
|
||||||
|
let sl, tp;
|
||||||
|
|
||||||
|
if (type === 'BUY') {
|
||||||
|
sl = entry - (atr * this.strategy.slMultiplier);
|
||||||
|
tp = entry + (atr * this.strategy.tpMultiplier);
|
||||||
|
} else { // SELL
|
||||||
|
sl = entry + (atr * this.strategy.slMultiplier);
|
||||||
|
tp = entry - (atr * this.strategy.tpMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: type,
|
||||||
|
entry: entry,
|
||||||
|
sl: sl,
|
||||||
|
tp: tp,
|
||||||
|
openIndex: index,
|
||||||
|
openTime: this.time[index]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if position should be exited
|
||||||
|
*/
|
||||||
|
checkExit(position, index) {
|
||||||
|
const high = this.high[index];
|
||||||
|
const low = this.low[index];
|
||||||
|
|
||||||
|
if (position.type === 'BUY') {
|
||||||
|
// BUY: TP is above entry, SL is below
|
||||||
|
if (high >= position.tp) {
|
||||||
|
return {
|
||||||
|
type: position.type,
|
||||||
|
entry: position.entry,
|
||||||
|
exit: position.tp,
|
||||||
|
profit: position.tp - position.entry,
|
||||||
|
reason: 'take_profit',
|
||||||
|
openTime: position.openTime,
|
||||||
|
closeTime: this.time[index]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (low <= position.sl) {
|
||||||
|
return {
|
||||||
|
type: position.type,
|
||||||
|
entry: position.entry,
|
||||||
|
exit: position.sl,
|
||||||
|
profit: position.sl - position.entry,
|
||||||
|
reason: 'stop_loss',
|
||||||
|
openTime: position.openTime,
|
||||||
|
closeTime: this.time[index]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else { // SELL
|
||||||
|
// SELL: TP is below entry, SL is above
|
||||||
|
if (low <= position.tp) {
|
||||||
|
return {
|
||||||
|
type: position.type,
|
||||||
|
entry: position.entry,
|
||||||
|
exit: position.tp,
|
||||||
|
profit: position.entry - position.tp,
|
||||||
|
reason: 'take_profit',
|
||||||
|
openTime: position.openTime,
|
||||||
|
closeTime: this.time[index]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (high >= position.sl) {
|
||||||
|
return {
|
||||||
|
type: position.type,
|
||||||
|
entry: position.entry,
|
||||||
|
exit: position.sl,
|
||||||
|
profit: position.entry - position.sl,
|
||||||
|
reason: 'stop_loss',
|
||||||
|
openTime: position.openTime,
|
||||||
|
closeTime: this.time[index]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate ATR (Average True Range)
|
||||||
|
*/
|
||||||
|
calculateATR(period) {
|
||||||
|
const atr = new Float64Array(this.dataLength);
|
||||||
|
const tr = new Float64Array(this.dataLength);
|
||||||
|
|
||||||
|
// Calculate True Range
|
||||||
|
for (let i = 1; i < this.dataLength; i++) {
|
||||||
|
const hl = this.high[i] - this.low[i];
|
||||||
|
const hc = Math.abs(this.high[i] - this.close[i - 1]);
|
||||||
|
const lc = Math.abs(this.low[i] - this.close[i - 1]);
|
||||||
|
tr[i] = Math.max(hl, hc, lc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate ATR using SMA
|
||||||
|
for (let i = period; i < this.dataLength; i++) {
|
||||||
|
let sum = 0;
|
||||||
|
for (let j = 0; j < period; j++) {
|
||||||
|
sum += tr[i - j];
|
||||||
|
}
|
||||||
|
atr[i] = sum / period;
|
||||||
|
}
|
||||||
|
|
||||||
|
return atr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate performance metrics
|
||||||
|
*/
|
||||||
|
calculateMetrics(trades, finalBalance, equity) {
|
||||||
|
if (trades.length === 0) {
|
||||||
|
return {
|
||||||
|
totalTrades: 0,
|
||||||
|
buyTrades: 0,
|
||||||
|
sellTrades: 0,
|
||||||
|
winRate: 0,
|
||||||
|
profitFactor: 0,
|
||||||
|
maxDrawdown: 0,
|
||||||
|
finalBalance: finalBalance,
|
||||||
|
totalProfit: 0,
|
||||||
|
avgWin: 0,
|
||||||
|
avgLoss: 0,
|
||||||
|
largestWin: 0,
|
||||||
|
largestLoss: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const winners = trades.filter(t => t.profit > 0);
|
||||||
|
const losers = trades.filter(t => t.profit <= 0);
|
||||||
|
const buyTrades = trades.filter(t => t.type === 'BUY');
|
||||||
|
const sellTrades = trades.filter(t => t.type === 'SELL');
|
||||||
|
|
||||||
|
const grossProfit = winners.reduce((sum, t) => sum + t.profit, 0);
|
||||||
|
const grossLoss = Math.abs(losers.reduce((sum, t) => sum + t.profit, 0));
|
||||||
|
|
||||||
|
const profitFactor = grossLoss > 0 ? grossProfit / grossLoss : (grossProfit > 0 ? 10 : 0);
|
||||||
|
const winRate = (winners.length / trades.length) * 100;
|
||||||
|
|
||||||
|
const maxDD = this.calculateMaxDrawdown(equity);
|
||||||
|
|
||||||
|
const avgWin = winners.length > 0 ? grossProfit / winners.length : 0;
|
||||||
|
const avgLoss = losers.length > 0 ? grossLoss / losers.length : 0;
|
||||||
|
|
||||||
|
const largestWin = winners.length > 0 ? Math.max(...winners.map(t => t.profit)) : 0;
|
||||||
|
const largestLoss = losers.length > 0 ? Math.min(...losers.map(t => t.profit)) : 0;
|
||||||
|
|
||||||
|
// Calculate hourly performance (0-23 hours)
|
||||||
|
const hourlyStats = Array.from({ length: 24 }, () => ({
|
||||||
|
trades: 0,
|
||||||
|
wins: 0,
|
||||||
|
losses: 0,
|
||||||
|
profit: 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
trades.forEach(trade => {
|
||||||
|
if (trade.openTime) {
|
||||||
|
const hour = new Date(trade.openTime).getHours();
|
||||||
|
hourlyStats[hour].trades++;
|
||||||
|
if (trade.profit > 0) {
|
||||||
|
hourlyStats[hour].wins++;
|
||||||
|
} else {
|
||||||
|
hourlyStats[hour].losses++;
|
||||||
|
}
|
||||||
|
hourlyStats[hour].profit += trade.profit;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find best and worst hours
|
||||||
|
let bestHour = { hour: 0, profit: -Infinity };
|
||||||
|
let worstHour = { hour: 0, profit: Infinity };
|
||||||
|
|
||||||
|
hourlyStats.forEach((stats, hour) => {
|
||||||
|
if (stats.trades > 0) {
|
||||||
|
if (stats.profit > bestHour.profit) {
|
||||||
|
bestHour = { hour, profit: stats.profit };
|
||||||
|
}
|
||||||
|
if (stats.profit < worstHour.profit) {
|
||||||
|
worstHour = { hour, profit: stats.profit };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalTrades: trades.length,
|
||||||
|
buyTrades: buyTrades.length,
|
||||||
|
sellTrades: sellTrades.length,
|
||||||
|
winningTrades: winners.length,
|
||||||
|
losingTrades: losers.length,
|
||||||
|
winRate: winRate,
|
||||||
|
profitFactor: profitFactor,
|
||||||
|
maxDrawdown: maxDD,
|
||||||
|
finalBalance: finalBalance,
|
||||||
|
totalProfit: finalBalance - 10000,
|
||||||
|
grossProfit: grossProfit,
|
||||||
|
grossLoss: grossLoss,
|
||||||
|
avgWin: avgWin,
|
||||||
|
avgLoss: avgLoss,
|
||||||
|
largestWin: largestWin,
|
||||||
|
largestLoss: largestLoss,
|
||||||
|
hourlyStats: hourlyStats,
|
||||||
|
bestHour: bestHour,
|
||||||
|
worstHour: worstHour,
|
||||||
|
equity: equity,
|
||||||
|
trades: trades
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate maximum drawdown
|
||||||
|
*/
|
||||||
|
calculateMaxDrawdown(equity) {
|
||||||
|
let maxDD = 0;
|
||||||
|
let peak = equity[0];
|
||||||
|
|
||||||
|
for (let i = 1; i < equity.length; i++) {
|
||||||
|
if (equity[i] > peak) {
|
||||||
|
peak = equity[i];
|
||||||
|
}
|
||||||
|
const dd = ((peak - equity[i]) / peak) * 100;
|
||||||
|
if (dd > maxDD) {
|
||||||
|
maxDD = dd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxDD;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
// cTrader (cBot) Converter
|
||||||
|
class CTraderConverter {
|
||||||
|
constructor(strategy) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
this.parser = new RuleParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
const { parameters, buy_rules, sell_rules } = this.strategy;
|
||||||
|
const symbol = parameters.symbol || 'EURUSD';
|
||||||
|
const atrPeriod = parameters.atr_period || 14;
|
||||||
|
const slMultiplier = parameters.sl_multiplier || 2.0;
|
||||||
|
const tpMultiplier = parameters.tp_multiplier || 3.0;
|
||||||
|
|
||||||
|
// Generate random 6-digit magic number
|
||||||
|
const magicNumber = Math.floor(100000 + Math.random() * 900000);
|
||||||
|
|
||||||
|
const buyConditions = this.parser.parseRules(buy_rules, 'csharp');
|
||||||
|
const sellConditions = this.parser.parseRules(sell_rules, 'csharp');
|
||||||
|
|
||||||
|
// Convert MQL-style array access to cTrader MarketSeries
|
||||||
|
const convertToCTrader = (condition) => {
|
||||||
|
return condition
|
||||||
|
.replace(/Open\[(\d+)\]/g, 'MarketSeries.Open.Last($1)')
|
||||||
|
.replace(/High\[(\d+)\]/g, 'MarketSeries.High.Last($1)')
|
||||||
|
.replace(/Low\[(\d+)\]/g, 'MarketSeries.Low.Last($1)')
|
||||||
|
.replace(/Close\[(\d+)\]/g, 'MarketSeries.Close.Last($1)');
|
||||||
|
};
|
||||||
|
|
||||||
|
const buyConditionsCTrader = buyConditions.map(convertToCTrader);
|
||||||
|
const sellConditionsCTrader = sellConditions.map(convertToCTrader);
|
||||||
|
|
||||||
|
return `using System;
|
||||||
|
using System.Linq;
|
||||||
|
using cAlgo.API;
|
||||||
|
using cAlgo.API.Indicators;
|
||||||
|
using cAlgo.API.Internals;
|
||||||
|
using cAlgo.Indicators;
|
||||||
|
|
||||||
|
namespace cAlgo.Robots
|
||||||
|
{
|
||||||
|
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
|
||||||
|
public class Strategy_${symbol} : Robot
|
||||||
|
{
|
||||||
|
//=== TRADE SETTINGS ===
|
||||||
|
[Parameter("Lot Size", DefaultValue = 0.1, MinValue = 0.01)]
|
||||||
|
public double LotSize { get; set; }
|
||||||
|
|
||||||
|
[Parameter("ATR Period", DefaultValue = ${atrPeriod}, MinValue = 1)]
|
||||||
|
public int ATR_Period { get; set; }
|
||||||
|
|
||||||
|
[Parameter("SL ATR Multiplier", DefaultValue = ${slMultiplier.toFixed(2)}, MinValue = 0.1)]
|
||||||
|
public double SL_Multiplier { get; set; }
|
||||||
|
|
||||||
|
[Parameter("TP ATR Multiplier", DefaultValue = ${tpMultiplier.toFixed(2)}, MinValue = 0.1)]
|
||||||
|
public double TP_Multiplier { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Magic Number", DefaultValue = ${magicNumber})]
|
||||||
|
public int MagicNumber { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Trade Comment", DefaultValue = "FxMath EA")]
|
||||||
|
public string TradeComment { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Slippage", DefaultValue = 3, MinValue = 0)]
|
||||||
|
public int Slippage { get; set; }
|
||||||
|
|
||||||
|
//=== SL/TP SETTINGS ===
|
||||||
|
[Parameter("Enable Hard SL", DefaultValue = true, Group = "SL/TP")]
|
||||||
|
public bool EnableHardSL { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Enable Hard TP", DefaultValue = true, Group = "SL/TP")]
|
||||||
|
public bool EnableHardTP { get; set; }
|
||||||
|
|
||||||
|
//=== TRAILING STOP ===
|
||||||
|
[Parameter("Enable Trailing", DefaultValue = false, Group = "Trailing")]
|
||||||
|
public bool EnableTrailing { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Trailing Start (pips)", DefaultValue = 30, MinValue = 1, Group = "Trailing")]
|
||||||
|
public double TrailingStart { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Trailing Step (pips)", DefaultValue = 10, MinValue = 1, Group = "Trailing")]
|
||||||
|
public double TrailingStep { get; set; }
|
||||||
|
|
||||||
|
//=== BREAKEVEN ===
|
||||||
|
[Parameter("Enable Breakeven", DefaultValue = false, Group = "Breakeven")]
|
||||||
|
public bool EnableBreakeven { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Breakeven Start (pips)", DefaultValue = 20, MinValue = 1, Group = "Breakeven")]
|
||||||
|
public double BreakevenStart { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Breakeven Offset (pips)", DefaultValue = 2, MinValue = 0, Group = "Breakeven")]
|
||||||
|
public double BreakevenOffset { get; set; }
|
||||||
|
|
||||||
|
//=== TIME FILTER ===
|
||||||
|
[Parameter("Enable Time Filter", DefaultValue = false, Group = "Time Filter")]
|
||||||
|
public bool EnableTimeFilter { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Start Hour", DefaultValue = 8, MinValue = 0, MaxValue = 23, Group = "Time Filter")]
|
||||||
|
public int StartHour { get; set; }
|
||||||
|
|
||||||
|
[Parameter("Start Minute", DefaultValue = 0, MinValue = 0, MaxValue = 59, Group = "Time Filter")]
|
||||||
|
public int StartMinute { get; set; }
|
||||||
|
|
||||||
|
[Parameter("End Hour", DefaultValue = 22, MinValue = 0, MaxValue = 23, Group = "Time Filter")]
|
||||||
|
public int EndHour { get; set; }
|
||||||
|
|
||||||
|
[Parameter("End Minute", DefaultValue = 0, MinValue = 0, MaxValue = 59, Group = "Time Filter")]
|
||||||
|
public int EndMinute { get; set; }
|
||||||
|
|
||||||
|
//=== SIGNAL SETTINGS ===
|
||||||
|
[Parameter("Close on Opposite", DefaultValue = true, Group = "Signals")]
|
||||||
|
public bool CloseOnOpposite { get; set; }
|
||||||
|
|
||||||
|
//=== DISPLAY SETTINGS ===
|
||||||
|
[Parameter("Show Chart Info", DefaultValue = true, Group = "Display")]
|
||||||
|
public bool ShowChartInfo { get; set; }
|
||||||
|
|
||||||
|
private AverageTrueRange atr;
|
||||||
|
private Position currentPosition;
|
||||||
|
|
||||||
|
protected override void OnStart()
|
||||||
|
{
|
||||||
|
atr = Indicators.AverageTrueRange(ATR_Period, MovingAverageType.Simple);
|
||||||
|
Print("Strategy cBot initialized for ${symbol}");
|
||||||
|
Print("Magic Number: " + MagicNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTick()
|
||||||
|
{
|
||||||
|
currentPosition = Positions.Find(TradeComment, SymbolName);
|
||||||
|
|
||||||
|
if (currentPosition != null)
|
||||||
|
{
|
||||||
|
ManagePosition();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check time filter
|
||||||
|
if (EnableTimeFilter && !IsTimeAllowed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check signals
|
||||||
|
if (CheckBuySignal())
|
||||||
|
{
|
||||||
|
OpenBuyOrder();
|
||||||
|
}
|
||||||
|
else if (CheckSellSignal())
|
||||||
|
{
|
||||||
|
OpenSellOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShowChartInfo)
|
||||||
|
DisplayChartInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTimeAllowed()
|
||||||
|
{
|
||||||
|
var currentTime = Server.Time;
|
||||||
|
int currentMinutes = currentTime.Hour * 60 + currentTime.Minute;
|
||||||
|
int startMinutes = StartHour * 60 + StartMinute;
|
||||||
|
int endMinutes = EndHour * 60 + EndMinute;
|
||||||
|
|
||||||
|
if (startMinutes < endMinutes)
|
||||||
|
return currentMinutes >= startMinutes && currentMinutes < endMinutes;
|
||||||
|
else
|
||||||
|
return currentMinutes >= startMinutes || currentMinutes < endMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ManagePosition()
|
||||||
|
{
|
||||||
|
if (currentPosition == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check for opposite signal
|
||||||
|
if (CloseOnOpposite)
|
||||||
|
{
|
||||||
|
if (currentPosition.TradeType == TradeType.Buy && CheckSellSignal())
|
||||||
|
{
|
||||||
|
ClosePosition(currentPosition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (currentPosition.TradeType == TradeType.Sell && CheckBuySignal())
|
||||||
|
{
|
||||||
|
ClosePosition(currentPosition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentPrice = currentPosition.TradeType == TradeType.Buy ? Symbol.Bid : Symbol.Ask;
|
||||||
|
double pipValue = Symbol.PipSize;
|
||||||
|
|
||||||
|
// Breakeven
|
||||||
|
if (EnableBreakeven)
|
||||||
|
{
|
||||||
|
double profit = currentPosition.TradeType == TradeType.Buy ?
|
||||||
|
(currentPrice - currentPosition.EntryPrice) / pipValue :
|
||||||
|
(currentPosition.EntryPrice - currentPrice) / pipValue;
|
||||||
|
|
||||||
|
if (profit >= BreakevenStart)
|
||||||
|
{
|
||||||
|
double newSL = currentPosition.EntryPrice + (BreakevenOffset * pipValue *
|
||||||
|
(currentPosition.TradeType == TradeType.Buy ? 1 : -1));
|
||||||
|
|
||||||
|
if ((currentPosition.TradeType == TradeType.Buy && newSL > currentPosition.StopLoss) ||
|
||||||
|
(currentPosition.TradeType == TradeType.Sell && (currentPosition.StopLoss == null || newSL < currentPosition.StopLoss)))
|
||||||
|
{
|
||||||
|
ModifyPosition(currentPosition, newSL, currentPosition.TakeProfit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailing Stop
|
||||||
|
if (EnableTrailing)
|
||||||
|
{
|
||||||
|
double profit = currentPosition.TradeType == TradeType.Buy ?
|
||||||
|
(currentPrice - currentPosition.EntryPrice) / pipValue :
|
||||||
|
(currentPosition.EntryPrice - currentPrice) / pipValue;
|
||||||
|
|
||||||
|
if (profit >= TrailingStart)
|
||||||
|
{
|
||||||
|
double newSL = currentPrice - (TrailingStep * pipValue *
|
||||||
|
(currentPosition.TradeType == TradeType.Buy ? 1 : -1));
|
||||||
|
|
||||||
|
if ((currentPosition.TradeType == TradeType.Buy && newSL > currentPosition.StopLoss) ||
|
||||||
|
(currentPosition.TradeType == TradeType.Sell && (currentPosition.StopLoss == null || newSL < currentPosition.StopLoss)))
|
||||||
|
{
|
||||||
|
ModifyPosition(currentPosition, newSL, currentPosition.TakeProfit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayChartInfo()
|
||||||
|
{
|
||||||
|
string info = "\\n=== " + TradeComment + " ===\\n";
|
||||||
|
info += "Symbol: " + SymbolName + "\\n";
|
||||||
|
info += "Magic: " + MagicNumber + "\\n";
|
||||||
|
|
||||||
|
if (currentPosition != null)
|
||||||
|
{
|
||||||
|
info += "Position: " + currentPosition.TradeType + "\\n";
|
||||||
|
info += "Profit: " + currentPosition.NetProfit.ToString("F2") + "\\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info += "No Position\\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
Chart.DrawStaticText("info", info, VerticalAlignment.Top, HorizontalAlignment.Left, Color.White);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckBuySignal()
|
||||||
|
{
|
||||||
|
return ${buyConditionsCTrader.join(' &&\n ')};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckSellSignal()
|
||||||
|
{
|
||||||
|
return ${sellConditionsCTrader.join(' &&\n ')};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenBuyOrder()
|
||||||
|
{
|
||||||
|
double atrValue = atr.Result.LastValue;
|
||||||
|
double price = Symbol.Ask;
|
||||||
|
double sl = EnableHardSL ? price - (atrValue * SL_Multiplier) : 0;
|
||||||
|
double tp = EnableHardTP ? price + (atrValue * TP_Multiplier) : 0;
|
||||||
|
|
||||||
|
double volumeInUnits = Symbol.QuantityToVolumeInUnits(LotSize);
|
||||||
|
|
||||||
|
var result = ExecuteMarketOrder(TradeType.Buy, SymbolName, volumeInUnits, TradeComment,
|
||||||
|
EnableHardSL ? sl : (double?)null,
|
||||||
|
EnableHardTP ? tp : (double?)null);
|
||||||
|
|
||||||
|
if (result.IsSuccessful)
|
||||||
|
{
|
||||||
|
Print("Buy order opened at " + price + " SL: " + sl + " TP: " + tp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Print("Error opening buy order: " + result.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSellOrder()
|
||||||
|
{
|
||||||
|
double atrValue = atr.Result.LastValue;
|
||||||
|
double price = Symbol.Bid;
|
||||||
|
double sl = EnableHardSL ? price + (atrValue * SL_Multiplier) : 0;
|
||||||
|
double tp = EnableHardTP ? price - (atrValue * TP_Multiplier) : 0;
|
||||||
|
|
||||||
|
double volumeInUnits = Symbol.QuantityToVolumeInUnits(LotSize);
|
||||||
|
|
||||||
|
var result = ExecuteMarketOrder(TradeType.Sell, SymbolName, volumeInUnits, TradeComment,
|
||||||
|
EnableHardSL ? sl : (double?)null,
|
||||||
|
EnableHardTP ? tp : (double?)null);
|
||||||
|
|
||||||
|
if (result.IsSuccessful)
|
||||||
|
{
|
||||||
|
Print("Sell order opened at " + price + " SL: " + sl + " TP: " + tp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Print("Error opening sell order: " + result.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.CTraderConverter = CTraderConverter;
|
||||||
+331
@@ -0,0 +1,331 @@
|
|||||||
|
/**
|
||||||
|
* Genetic Algorithm Engine - Evolves trading strategies
|
||||||
|
*/
|
||||||
|
class GeneticOptimizer {
|
||||||
|
constructor(data, config) {
|
||||||
|
this.data = data;
|
||||||
|
this.config = {
|
||||||
|
populationSize: config.populationSize || 100,
|
||||||
|
generations: config.generations || 50,
|
||||||
|
strategiesCount: config.strategiesCount || 5,
|
||||||
|
rulesRange: config.rulesRange || [3, 8],
|
||||||
|
shiftRange: config.shiftRange || [1, 10],
|
||||||
|
minTrades: config.minTrades || 30,
|
||||||
|
minPF: config.minPF || 1.5,
|
||||||
|
maxDD: config.maxDD || 25,
|
||||||
|
minWR: config.minWR || 45
|
||||||
|
};
|
||||||
|
|
||||||
|
this.population = [];
|
||||||
|
this.foundStrategies = [];
|
||||||
|
this.isRunning = false;
|
||||||
|
this.currentGeneration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize random population
|
||||||
|
*/
|
||||||
|
initializePopulation() {
|
||||||
|
this.population = [];
|
||||||
|
for (let i = 0; i < this.config.populationSize; i++) {
|
||||||
|
const strategy = new Strategy();
|
||||||
|
strategy.generateRandomRules(this.config.rulesRange, this.config.shiftRange);
|
||||||
|
strategy.randomizeParameters();
|
||||||
|
this.population.push(strategy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate fitness for all strategies
|
||||||
|
*/
|
||||||
|
evaluatePopulation() {
|
||||||
|
for (const strategy of this.population) {
|
||||||
|
const backtester = new Backtester(this.data, strategy);
|
||||||
|
strategy.metrics = backtester.run();
|
||||||
|
strategy.fitness = this.calculateFitness(strategy.metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by fitness (descending)
|
||||||
|
this.population.sort((a, b) => b.fitness - a.fitness);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate fitness score
|
||||||
|
*/
|
||||||
|
calculateFitness(metrics) {
|
||||||
|
if (metrics.totalTrades === 0) return 0;
|
||||||
|
|
||||||
|
const pf = Math.min(metrics.profitFactor, 10);
|
||||||
|
const trades = Math.sqrt(metrics.totalTrades);
|
||||||
|
const wrBonus = metrics.winRate > 50 ? 1 + (metrics.winRate - 50) / 100 : 1;
|
||||||
|
const ddPenalty = 1 + (metrics.maxDrawdown / 100);
|
||||||
|
|
||||||
|
// Calculate BUY/SELL balance bonus
|
||||||
|
const buyRatio = (metrics.buyTrades / metrics.totalTrades) * 100;
|
||||||
|
const sellRatio = (metrics.sellTrades / metrics.totalTrades) * 100;
|
||||||
|
|
||||||
|
// Reward strategies with 30-70% split (most balanced)
|
||||||
|
// Penalize strategies with <20% or >80% of one type
|
||||||
|
let balanceBonus = 1.0;
|
||||||
|
if (buyRatio >= 30 && buyRatio <= 70) {
|
||||||
|
balanceBonus = 1.2; // 20% bonus for good balance
|
||||||
|
} else if (buyRatio >= 20 && buyRatio <= 80) {
|
||||||
|
balanceBonus = 1.1; // 10% bonus for acceptable balance
|
||||||
|
} else {
|
||||||
|
balanceBonus = 0.5; // 50% penalty for poor balance
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Math.pow(pf, 1.5) * trades * wrBonus * balanceBonus) / ddPenalty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if strategy meets criteria
|
||||||
|
*/
|
||||||
|
meetsCriteria(strategy) {
|
||||||
|
const m = strategy.metrics;
|
||||||
|
|
||||||
|
// Check basic criteria
|
||||||
|
const meetsBasic = (
|
||||||
|
m.totalTrades >= this.config.minTrades &&
|
||||||
|
m.profitFactor >= this.config.minPF &&
|
||||||
|
m.maxDrawdown <= this.config.maxDD &&
|
||||||
|
m.winRate >= this.config.minWR
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!meetsBasic) return false;
|
||||||
|
|
||||||
|
// Check BUY/SELL balance (should be between 20-80%)
|
||||||
|
const buyRatio = (m.buyTrades / m.totalTrades) * 100;
|
||||||
|
const sellRatio = (m.sellTrades / m.totalTrades) * 100;
|
||||||
|
|
||||||
|
// Require at least 20% of each type for balance
|
||||||
|
const isBalanced = buyRatio >= 20 && sellRatio >= 20;
|
||||||
|
|
||||||
|
console.log(`📊 Strategy balance: BUY=${buyRatio.toFixed(1)}%, SELL=${sellRatio.toFixed(1)}%, Balanced=${isBalanced}`);
|
||||||
|
|
||||||
|
return isBalanced;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tournament selection
|
||||||
|
*/
|
||||||
|
tournamentSelection() {
|
||||||
|
const tournamentSize = 3;
|
||||||
|
let best = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < tournamentSize; i++) {
|
||||||
|
const candidate = this.population[Math.floor(Math.random() * this.population.length)];
|
||||||
|
if (!best || candidate.fitness > best.fitness) {
|
||||||
|
best = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crossover two parent strategies
|
||||||
|
*/
|
||||||
|
crossover(parent1, parent2) {
|
||||||
|
const child = new Strategy();
|
||||||
|
|
||||||
|
// Crossover rules
|
||||||
|
const splitPoint = Math.floor(Math.random() * Math.min(parent1.rules.length, parent2.rules.length));
|
||||||
|
child.rules = [
|
||||||
|
...parent1.rules.slice(0, splitPoint),
|
||||||
|
...parent2.rules.slice(splitPoint)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Ensure rules count is within range
|
||||||
|
while (child.rules.length < this.config.rulesRange[0]) {
|
||||||
|
child.rules.push(child.generateRule(this.config.shiftRange));
|
||||||
|
}
|
||||||
|
while (child.rules.length > this.config.rulesRange[1]) {
|
||||||
|
child.rules.splice(Math.floor(Math.random() * child.rules.length), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crossover parameters
|
||||||
|
child.atrPeriod = Math.random() < 0.5 ? parent1.atrPeriod : parent2.atrPeriod;
|
||||||
|
child.slMultiplier = Math.random() < 0.5 ? parent1.slMultiplier : parent2.slMultiplier;
|
||||||
|
child.tpMultiplier = Math.random() < 0.5 ? parent1.tpMultiplier : parent2.tpMultiplier;
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutate a strategy
|
||||||
|
*/
|
||||||
|
mutate(strategy) {
|
||||||
|
const mutationRate = 0.2;
|
||||||
|
|
||||||
|
if (Math.random() < mutationRate) {
|
||||||
|
const mutationType = Math.random();
|
||||||
|
|
||||||
|
if (mutationType < 0.4) {
|
||||||
|
// Mutate a rule
|
||||||
|
if (strategy.rules.length > 0) {
|
||||||
|
const index = Math.floor(Math.random() * strategy.rules.length);
|
||||||
|
strategy.rules[index] = strategy.generateRule(this.config.shiftRange);
|
||||||
|
}
|
||||||
|
} else if (mutationType < 0.55) {
|
||||||
|
// Add a rule
|
||||||
|
if (strategy.rules.length < this.config.rulesRange[1]) {
|
||||||
|
strategy.rules.push(strategy.generateRule(this.config.shiftRange));
|
||||||
|
}
|
||||||
|
} else if (mutationType < 0.7) {
|
||||||
|
// Remove a rule
|
||||||
|
if (strategy.rules.length > this.config.rulesRange[0]) {
|
||||||
|
strategy.rules.splice(Math.floor(Math.random() * strategy.rules.length), 1);
|
||||||
|
}
|
||||||
|
} else if (mutationType < 0.85) {
|
||||||
|
// Mutate ATR period
|
||||||
|
strategy.atrPeriod = Math.floor(Math.random() * 31) + 10;
|
||||||
|
} else {
|
||||||
|
// Mutate SL/TP
|
||||||
|
strategy.slMultiplier = (Math.random() * 3) + 1;
|
||||||
|
strategy.tpMultiplier = strategy.slMultiplier + (Math.random() * 5) + 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evolve one generation
|
||||||
|
*/
|
||||||
|
evolveGeneration() {
|
||||||
|
const newPopulation = [];
|
||||||
|
|
||||||
|
// Elitism: keep top 2 strategies
|
||||||
|
newPopulation.push(this.population[0].copy());
|
||||||
|
newPopulation.push(this.population[1].copy());
|
||||||
|
|
||||||
|
// Generate rest through crossover and mutation
|
||||||
|
while (newPopulation.length < this.config.populationSize) {
|
||||||
|
const parent1 = this.tournamentSelection();
|
||||||
|
const parent2 = this.tournamentSelection();
|
||||||
|
|
||||||
|
const child = this.crossover(parent1, parent2);
|
||||||
|
this.mutate(child);
|
||||||
|
|
||||||
|
newPopulation.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.population = newPopulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the genetic algorithm
|
||||||
|
*/
|
||||||
|
async run(progressCallback) {
|
||||||
|
console.log('🧬 GA.run() started');
|
||||||
|
console.log('📊 Data rows:', this.data.length);
|
||||||
|
console.log('⚙️ Config:', this.config);
|
||||||
|
this.isRunning = true;
|
||||||
|
this.foundStrategies = [];
|
||||||
|
this.currentGeneration = 0;
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
while (this.isRunning && this.foundStrategies.length < this.config.strategiesCount) {
|
||||||
|
console.log('🔄 Starting new GA search, found so far:', this.foundStrategies.length);
|
||||||
|
// Initialize new population for each search
|
||||||
|
this.initializePopulation();
|
||||||
|
console.log('✅ Population initialized:', this.population.length, 'strategies');
|
||||||
|
|
||||||
|
// Evolve for specified generations
|
||||||
|
for (let gen = 0; gen < this.config.generations && this.isRunning; gen++) {
|
||||||
|
this.currentGeneration = gen + 1;
|
||||||
|
|
||||||
|
// Evaluate fitness
|
||||||
|
console.log(`Gen ${gen + 1}/${this.config.generations}: Evaluating population...`);
|
||||||
|
this.evaluatePopulation();
|
||||||
|
console.log(`Gen ${gen + 1}: Best fitness = ${this.population[0].fitness.toFixed(2)}, Trades = ${this.population[0].metrics.totalTrades}`);
|
||||||
|
|
||||||
|
// Report progress
|
||||||
|
if (progressCallback) {
|
||||||
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||||
|
progressCallback({
|
||||||
|
generation: gen + 1,
|
||||||
|
totalGenerations: this.config.generations,
|
||||||
|
bestFitness: this.population[0].fitness,
|
||||||
|
avgFitness: this.population.reduce((sum, s) => sum + s.fitness, 0) / this.population.length,
|
||||||
|
foundCount: this.foundStrategies.length,
|
||||||
|
targetCount: this.config.strategiesCount,
|
||||||
|
elapsedTime: elapsed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if best strategy meets criteria
|
||||||
|
const best = this.population[0];
|
||||||
|
console.log(`Checking criteria: PF=${best.metrics.profitFactor.toFixed(2)}, WR=${best.metrics.winRate.toFixed(1)}%, Trades=${best.metrics.totalTrades}, DD=${best.metrics.maxDrawdown.toFixed(2)}%`);
|
||||||
|
if (this.meetsCriteria(best) && !this.isDuplicate(best)) {
|
||||||
|
console.log('✅ Strategy meets criteria!');
|
||||||
|
this.foundStrategies.push(best.copy());
|
||||||
|
|
||||||
|
if (progressCallback) {
|
||||||
|
progressCallback({
|
||||||
|
strategyFound: best,
|
||||||
|
foundCount: this.foundStrategies.length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found enough, break
|
||||||
|
if (this.foundStrategies.length >= this.config.strategiesCount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evolve to next generation
|
||||||
|
if (gen < this.config.generations - 1) {
|
||||||
|
this.evolveGeneration();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow UI to update
|
||||||
|
await this.sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isRunning = false;
|
||||||
|
console.log('🏁 GA.run() complete. Returning', this.foundStrategies.length, 'strategies');
|
||||||
|
console.log('Strategies to return:', this.foundStrategies);
|
||||||
|
return this.foundStrategies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if strategy is duplicate
|
||||||
|
*/
|
||||||
|
isDuplicate(strategy) {
|
||||||
|
for (const existing of this.foundStrategies) {
|
||||||
|
if (this.strategiesSimilar(strategy, existing)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two strategies are similar
|
||||||
|
*/
|
||||||
|
strategiesSimilar(s1, s2) {
|
||||||
|
// Simple check: if rules are identical
|
||||||
|
if (s1.rules.length !== s2.rules.length) return false;
|
||||||
|
|
||||||
|
const rules1 = JSON.stringify(s1.rules);
|
||||||
|
const rules2 = JSON.stringify(s2.rules);
|
||||||
|
|
||||||
|
return rules1 === rules2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the optimization
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
this.isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleep helper for async
|
||||||
|
*/
|
||||||
|
sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,493 @@
|
|||||||
|
/**
|
||||||
|
* HTML Report Generator
|
||||||
|
* Generates comprehensive HTML reports with full trading statement
|
||||||
|
*/
|
||||||
|
|
||||||
|
function generateHTMLReport(strategy, index) {
|
||||||
|
const m = strategy.metrics;
|
||||||
|
const name = `FxMath_${String(index + 1).padStart(3, '0')}_PF${(m.profitFactor || 0).toFixed(2).replace('.', '_')}_WR${Math.round(m.winRate || 0)}`;
|
||||||
|
|
||||||
|
// Generate SELL rules (inverted operators)
|
||||||
|
const sellRulesHTML = strategy.rules.map((rule, i) => {
|
||||||
|
const invertedOp = rule.operator === '>' ? '<=' :
|
||||||
|
rule.operator === '<' ? '>=' :
|
||||||
|
rule.operator === '>=' ? '<' :
|
||||||
|
rule.operator === '<=' ? '>' : rule.operator;
|
||||||
|
|
||||||
|
let ruleText = '';
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${invertedOp} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||||
|
} else {
|
||||||
|
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${invertedOp} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||||
|
}
|
||||||
|
return `<div class="rule-item">${i + 1}. ${ruleText}</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Generate BUY rules
|
||||||
|
const buyRulesHTML = strategy.rules.map((rule, i) => {
|
||||||
|
let ruleText = '';
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||||
|
} else {
|
||||||
|
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||||
|
}
|
||||||
|
return `<div class="rule-item">${i + 1}. ${ruleText}</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Generate trades table
|
||||||
|
const tradesHTML = m.trades.map((trade, i) => {
|
||||||
|
const profitClass = trade.profit > 0 ? 'profit-positive' : 'profit-negative';
|
||||||
|
const typeClass = trade.type === 'BUY' ? 'type-buy' : 'type-sell';
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
<td>${i + 1}</td>
|
||||||
|
<td><span class="badge ${typeClass}">${trade.type}</span></td>
|
||||||
|
<td>${new Date(trade.openTime).toLocaleString()}</td>
|
||||||
|
<td>${new Date(trade.closeTime).toLocaleString()}</td>
|
||||||
|
<td>$${trade.entry.toFixed(2)}</td>
|
||||||
|
<td>$${trade.exit.toFixed(2)}</td>
|
||||||
|
<td class="${profitClass}">$${trade.profit.toFixed(2)}</td>
|
||||||
|
<td>${trade.reason.toUpperCase()}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Generate hourly stats table
|
||||||
|
let hourlyStatsHTML = '';
|
||||||
|
if (m.hourlyStats) {
|
||||||
|
hourlyStatsHTML = m.hourlyStats.map((stats, hour) => {
|
||||||
|
if (stats.trades === 0) return '';
|
||||||
|
const profitClass = stats.profit > 0 ? 'profit-positive' : 'profit-negative';
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
<td>${String(hour).padStart(2, '0')}:00</td>
|
||||||
|
<td>${stats.trades}</td>
|
||||||
|
<td>${stats.wins}</td>
|
||||||
|
<td>${stats.losses}</td>
|
||||||
|
<td class="${profitClass}">$${stats.profit.toFixed(2)}</td>
|
||||||
|
<td>${stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) : 0}%</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>${name} - Trading Strategy Report</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
padding: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header h1 { font-size: 32px; margin-bottom: 10px; }
|
||||||
|
.header p { font-size: 16px; opacity: 0.9; }
|
||||||
|
.content { padding: 30px; }
|
||||||
|
.section { margin-bottom: 40px; }
|
||||||
|
.section h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
.metrics-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.metric-card {
|
||||||
|
background: #f7fafc;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
.metric-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #718096;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.metric-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
.metric-value.positive { color: #48bb78; }
|
||||||
|
.metric-value.negative { color: #f56565; }
|
||||||
|
.rules-box {
|
||||||
|
background: #f7fafc;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.rules-box h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.rule-item {
|
||||||
|
padding: 8px 0;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: #f7fafc;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #4a5568;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
tr:hover { background: #f7fafc; }
|
||||||
|
.badge {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.type-buy {
|
||||||
|
background: #c6f6d5;
|
||||||
|
color: #22543d;
|
||||||
|
}
|
||||||
|
.type-sell {
|
||||||
|
background: #fed7d7;
|
||||||
|
color: #742a2a;
|
||||||
|
}
|
||||||
|
.profit-positive { color: #48bb78; font-weight: 600; }
|
||||||
|
.profit-negative { color: #f56565; font-weight: 600; }
|
||||||
|
.best-worst {
|
||||||
|
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.best-worst div {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.best-worst h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #718096;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.best-worst .value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.chart-container {
|
||||||
|
background: #f7fafc;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 20px 0;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
background: #f7fafc;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #718096;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
body { background: white; padding: 0; }
|
||||||
|
.container { box-shadow: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>${name}</h1>
|
||||||
|
<p>FxMath Quant - Automated Trading Strategy Report</p>
|
||||||
|
<p>Generated: ${new Date().toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<!-- Performance Metrics -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>Performance Metrics</h2>
|
||||||
|
<div class="metrics-grid">
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Profit Factor</div>
|
||||||
|
<div class="metric-value positive">${(m.profitFactor || 0).toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Win Rate</div>
|
||||||
|
<div class="metric-value">${(m.winRate || 0).toFixed(1)}%</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Total Trades</div>
|
||||||
|
<div class="metric-value">${m.totalTrades || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">BUY Trades</div>
|
||||||
|
<div class="metric-value" style="color: #48bb78;">${m.buyTrades || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">SELL Trades</div>
|
||||||
|
<div class="metric-value" style="color: #f56565;">${m.sellTrades || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Max Drawdown</div>
|
||||||
|
<div class="metric-value negative">${(m.maxDrawdown || 0).toFixed(2)}%</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Total Profit</div>
|
||||||
|
<div class="metric-value ${m.totalProfit >= 0 ? 'positive' : 'negative'}">$${(m.totalProfit || 0).toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Avg Win</div>
|
||||||
|
<div class="metric-value positive">$${(m.avgWin || 0).toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Avg Loss</div>
|
||||||
|
<div class="metric-value negative">$${Math.abs(m.avgLoss || 0).toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Equity Curve Chart -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>Equity Curve</h2>
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="equityChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${m.hourlyStats ? `
|
||||||
|
<!-- Hourly Performance Chart -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>Hourly Performance (24h)</h2>
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="hourlyChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
${m.bestHour && m.worstHour ? `
|
||||||
|
<!-- Best/Worst Hours -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>Time-Based Performance</h2>
|
||||||
|
<div class="best-worst">
|
||||||
|
<div>
|
||||||
|
<h4>Best Trading Hour</h4>
|
||||||
|
<div class="value positive">${String(m.bestHour.hour).padStart(2, '0')}:00</div>
|
||||||
|
<div class="profit-positive">$${m.bestHour.profit.toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4>Worst Trading Hour</h4>
|
||||||
|
<div class="value negative">${String(m.worstHour.hour).padStart(2, '0')}:00</div>
|
||||||
|
<div class="profit-negative">$${m.worstHour.profit.toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<!-- Strategy Rules -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>Strategy Rules</h2>
|
||||||
|
<div class="rules-box">
|
||||||
|
<h3>BUY when ALL of the following conditions are true:</h3>
|
||||||
|
${buyRulesHTML}
|
||||||
|
</div>
|
||||||
|
<div class="rules-box">
|
||||||
|
<h3>SELL when ALL of the following conditions are true:</h3>
|
||||||
|
${sellRulesHTML}
|
||||||
|
</div>
|
||||||
|
<div class="rules-box">
|
||||||
|
<h3>Parameters</h3>
|
||||||
|
<div class="rule-item">ATR Period: ${strategy.atrPeriod}</div>
|
||||||
|
<div class="rule-item">SL Multiplier: ${strategy.slMultiplier.toFixed(2)}</div>
|
||||||
|
<div class="rule-item">TP Multiplier: ${strategy.tpMultiplier.toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${hourlyStatsHTML ? `
|
||||||
|
<!-- Hourly Performance -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>Hourly Performance Analysis</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Hour</th>
|
||||||
|
<th>Trades</th>
|
||||||
|
<th>Wins</th>
|
||||||
|
<th>Losses</th>
|
||||||
|
<th>Profit/Loss</th>
|
||||||
|
<th>Win Rate</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${hourlyStatsHTML}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<!-- Full Trading Statement -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>Complete Trading Statement</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Open Time</th>
|
||||||
|
<th>Close Time</th>
|
||||||
|
<th>Entry</th>
|
||||||
|
<th>Exit</th>
|
||||||
|
<th>Profit/Loss</th>
|
||||||
|
<th>Exit Reason</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${tradesHTML}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>FxMath Quant - Automated Trading Strategy Generator</p>
|
||||||
|
<p>This report is for informational purposes only. Past performance does not guarantee future results.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Render Equity Curve Chart
|
||||||
|
const equityCtx = document.getElementById('equityChart');
|
||||||
|
if (equityCtx) {
|
||||||
|
new Chart(equityCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: ${JSON.stringify(m.equity.map((_, i) => i))},
|
||||||
|
datasets: [{
|
||||||
|
label: 'Account Balance',
|
||||||
|
data: ${JSON.stringify(m.equity)},
|
||||||
|
borderColor: '#667eea',
|
||||||
|
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true,
|
||||||
|
pointRadius: 0
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: '#1a1f3a',
|
||||||
|
titleColor: '#fff',
|
||||||
|
bodyColor: '#a0aec0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: false,
|
||||||
|
grid: { color: 'rgba(0, 0, 0, 0.1)' },
|
||||||
|
ticks: { color: '#4a5568' }
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: { display: false },
|
||||||
|
ticks: { color: '#4a5568' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Hourly Performance Chart
|
||||||
|
const hourlyCtx = document.getElementById('hourlyChart');
|
||||||
|
if (hourlyCtx) {
|
||||||
|
const hourlyData = ${JSON.stringify(m.hourlyStats || [])};
|
||||||
|
new Chart(hourlyCtx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0') + ':00'),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Profit/Loss',
|
||||||
|
data: hourlyData.map(h => h.profit),
|
||||||
|
backgroundColor: hourlyData.map(h => h.profit >= 0 ? 'rgba(72, 187, 120, 0.6)' : 'rgba(245, 101, 101, 0.6)'),
|
||||||
|
borderColor: hourlyData.map(h => h.profit >= 0 ? 'rgba(72, 187, 120, 1)' : 'rgba(245, 101, 101, 1)'),
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
const hour = context.dataIndex;
|
||||||
|
const stats = hourlyData[hour];
|
||||||
|
return [
|
||||||
|
'Profit: $' + stats.profit.toFixed(2),
|
||||||
|
'Trades: ' + stats.trades,
|
||||||
|
'Wins: ' + stats.wins + ' | Losses: ' + stats.losses
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: { color: 'rgba(0, 0, 0, 0.1)' },
|
||||||
|
ticks: { color: '#4a5568' }
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: { display: false },
|
||||||
|
ticks: { color: '#4a5568', maxRotation: 45, minRotation: 45 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export function
|
||||||
|
window.generateHTMLReport = generateHTMLReport;
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* License Check Module
|
||||||
|
* Verifies license on app load and redirects if invalid
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Check license immediately when script loads
|
||||||
|
checkLicense();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main license check function
|
||||||
|
*/
|
||||||
|
function checkLicense() {
|
||||||
|
const licenseData = localStorage.getItem('fxmath_license');
|
||||||
|
|
||||||
|
// No license found
|
||||||
|
if (!licenseData) {
|
||||||
|
redirectToLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const license = JSON.parse(licenseData);
|
||||||
|
|
||||||
|
// Validate license structure
|
||||||
|
if (!license.key || !license.type || !license.status) {
|
||||||
|
console.warn('Invalid license data structure');
|
||||||
|
redirectToLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if license is active
|
||||||
|
if (license.status !== 'active') {
|
||||||
|
console.warn('License is not active');
|
||||||
|
redirectToLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check expiry for limited licenses
|
||||||
|
if (license.type === 'limited') {
|
||||||
|
if (!license.expiry_date) {
|
||||||
|
console.warn('Limited license missing expiry date');
|
||||||
|
redirectToLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiryDate = new Date(license.expiry_date);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (now > expiryDate) {
|
||||||
|
console.warn('License has expired');
|
||||||
|
localStorage.removeItem('fxmath_license');
|
||||||
|
alert('Your license has expired. Please renew your license to continue using FxMathQuant.');
|
||||||
|
redirectToLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate days remaining
|
||||||
|
const daysRemaining = Math.ceil((expiryDate - now) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
// Warn if license is expiring soon (7 days or less)
|
||||||
|
if (daysRemaining <= 7 && daysRemaining > 0) {
|
||||||
|
console.warn(`License expiring in ${daysRemaining} days`);
|
||||||
|
showExpiryWarning(daysRemaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// License is valid
|
||||||
|
console.log('License validated successfully');
|
||||||
|
displayLicenseInfo(license);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('License check error:', error);
|
||||||
|
redirectToLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to login page
|
||||||
|
*/
|
||||||
|
function redirectToLogin() {
|
||||||
|
// Only redirect if not already on login page
|
||||||
|
if (!window.location.pathname.includes('login.html')) {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show expiry warning
|
||||||
|
*/
|
||||||
|
function showExpiryWarning(daysRemaining) {
|
||||||
|
// Create warning banner if it doesn't exist
|
||||||
|
let banner = document.getElementById('license-expiry-warning');
|
||||||
|
|
||||||
|
if (!banner) {
|
||||||
|
banner = document.createElement('div');
|
||||||
|
banner.id = 'license-expiry-warning';
|
||||||
|
banner.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
z-index: 10000;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||||
|
`;
|
||||||
|
|
||||||
|
banner.innerHTML = `
|
||||||
|
⚠️ Your license will expire in ${daysRemaining} day${daysRemaining !== 1 ? 's' : ''}.
|
||||||
|
Please renew to continue using FxMathQuant.
|
||||||
|
<button onclick="this.parentElement.remove()" style="
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 4px 12px;
|
||||||
|
margin-left: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
">Dismiss</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.insertBefore(banner, document.body.firstChild);
|
||||||
|
|
||||||
|
// Adjust body padding to account for banner
|
||||||
|
document.body.style.paddingTop = '50px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display license info in console (for debugging)
|
||||||
|
*/
|
||||||
|
function displayLicenseInfo(license) {
|
||||||
|
console.log('%c License Information ', 'background: #667eea; color: white; font-weight: bold; padding: 5px 10px;');
|
||||||
|
console.log('Type:', license.type);
|
||||||
|
console.log('Status:', license.status);
|
||||||
|
|
||||||
|
if (license.type === 'limited') {
|
||||||
|
console.log('Expiry Date:', license.expiry_date);
|
||||||
|
console.log('Days Remaining:', license.days_remaining);
|
||||||
|
} else {
|
||||||
|
console.log('Expiry:', 'Never (Lifetime)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose logout function globally
|
||||||
|
*/
|
||||||
|
window.logoutLicense = function () {
|
||||||
|
if (confirm('Are you sure you want to logout? You will need to enter your license key again.')) {
|
||||||
|
localStorage.removeItem('fxmath_license');
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* License Login Handler
|
||||||
|
* FxMathQuant-Web
|
||||||
|
*/
|
||||||
|
|
||||||
|
// API endpoint - Production server
|
||||||
|
const API_URL = 'https://fxmath.com/quantw/api/validate.php';
|
||||||
|
|
||||||
|
// DOM elements
|
||||||
|
const licenseForm = document.getElementById('licenseForm');
|
||||||
|
const licenseKeyInput = document.getElementById('licenseKey');
|
||||||
|
const activateBtn = document.getElementById('activateBtn');
|
||||||
|
const alertBox = document.getElementById('alertBox');
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Check if already licensed
|
||||||
|
if (isLicenseValid()) {
|
||||||
|
window.location.href = 'index.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format license key input
|
||||||
|
licenseKeyInput.addEventListener('input', formatLicenseKey);
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
licenseForm.addEventListener('submit', handleLicenseSubmit);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format license key as user types (add dashes)
|
||||||
|
*/
|
||||||
|
function formatLicenseKey(e) {
|
||||||
|
let value = e.target.value.replace(/[^A-Z0-9]/gi, '').toUpperCase();
|
||||||
|
let formatted = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < value.length && i < 32; i++) {
|
||||||
|
if (i > 0 && i % 4 === 0) {
|
||||||
|
formatted += '-';
|
||||||
|
}
|
||||||
|
formatted += value[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
e.target.value = formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle license form submission
|
||||||
|
*/
|
||||||
|
async function handleLicenseSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const licenseKey = licenseKeyInput.value.trim();
|
||||||
|
|
||||||
|
// Validate format
|
||||||
|
if (!isValidLicenseFormat(licenseKey)) {
|
||||||
|
showAlert('Please enter a valid license key format', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable button and show loading
|
||||||
|
activateBtn.disabled = true;
|
||||||
|
activateBtn.innerHTML = '<span class="spinner"></span>Validating...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call validation API
|
||||||
|
const response = await fetch(API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ license_key: licenseKey })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// Store license info in localStorage
|
||||||
|
const licenseInfo = {
|
||||||
|
key: licenseKey,
|
||||||
|
type: data.license.type,
|
||||||
|
status: data.license.status,
|
||||||
|
validated_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (data.license.type === 'limited') {
|
||||||
|
licenseInfo.expiry_date = data.license.expiry_date;
|
||||||
|
licenseInfo.days_remaining = data.license.days_remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('fxmath_license', JSON.stringify(licenseInfo));
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showAlert('License activated successfully! Redirecting...', 'success');
|
||||||
|
|
||||||
|
// Redirect to main app
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = 'index.html';
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Show error message
|
||||||
|
showAlert(data.message || 'License validation failed', 'error');
|
||||||
|
activateBtn.disabled = false;
|
||||||
|
activateBtn.innerHTML = 'Activate License';
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Validation error:', error);
|
||||||
|
showAlert('Connection error. Please check your server configuration.', 'error');
|
||||||
|
activateBtn.disabled = false;
|
||||||
|
activateBtn.innerHTML = 'Activate License';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate license key format
|
||||||
|
*/
|
||||||
|
function isValidLicenseFormat(key) {
|
||||||
|
// Should be 32 alphanumeric characters with dashes every 4 characters
|
||||||
|
const pattern = /^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
|
||||||
|
return pattern.test(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if license is valid
|
||||||
|
*/
|
||||||
|
function isLicenseValid() {
|
||||||
|
const licenseData = localStorage.getItem('fxmath_license');
|
||||||
|
|
||||||
|
if (!licenseData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const license = JSON.parse(licenseData);
|
||||||
|
|
||||||
|
// Check if license exists
|
||||||
|
if (!license.key || license.status !== 'active') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check expiry for limited licenses
|
||||||
|
if (license.type === 'limited') {
|
||||||
|
const expiryDate = new Date(license.expiry_date);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (now > expiryDate) {
|
||||||
|
// License expired
|
||||||
|
localStorage.removeItem('fxmath_license');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('License validation error:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show alert message
|
||||||
|
*/
|
||||||
|
function showAlert(message, type) {
|
||||||
|
alertBox.textContent = message;
|
||||||
|
alertBox.className = `alert alert-${type} show`;
|
||||||
|
|
||||||
|
// Auto-hide after 5 seconds for non-success messages
|
||||||
|
if (type !== 'success') {
|
||||||
|
setTimeout(() => {
|
||||||
|
alertBox.classList.remove('show');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
+725
@@ -0,0 +1,725 @@
|
|||||||
|
/**
|
||||||
|
* Main Application Controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global state
|
||||||
|
let appData = {
|
||||||
|
csvData: null,
|
||||||
|
dataName: '',
|
||||||
|
optimizer: null,
|
||||||
|
foundStrategies: [],
|
||||||
|
selectedStrategies: new Set(),
|
||||||
|
isGenerating: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize app when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initializeApp();
|
||||||
|
});
|
||||||
|
|
||||||
|
function initializeApp() {
|
||||||
|
setupEventListeners();
|
||||||
|
showSection('upload-section');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup all event listeners
|
||||||
|
*/
|
||||||
|
function setupEventListeners() {
|
||||||
|
// Upload zone
|
||||||
|
const uploadZone = document.getElementById('upload-zone');
|
||||||
|
const fileInput = document.getElementById('file-input');
|
||||||
|
const browseBtn = document.getElementById('browse-btn');
|
||||||
|
|
||||||
|
uploadZone.addEventListener('click', () => fileInput.click());
|
||||||
|
browseBtn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', (e) => {
|
||||||
|
if (e.target.files.length > 0) {
|
||||||
|
handleFileUpload(e.target.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag and drop
|
||||||
|
uploadZone.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadZone.classList.add('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadZone.addEventListener('dragleave', () => {
|
||||||
|
uploadZone.classList.remove('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadZone.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadZone.classList.remove('dragover');
|
||||||
|
|
||||||
|
if (e.dataTransfer.files.length > 0) {
|
||||||
|
handleFileUpload(e.dataTransfer.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigation buttons
|
||||||
|
document.getElementById('continue-btn').addEventListener('click', () => {
|
||||||
|
showSection('config-section');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('back-btn').addEventListener('click', () => {
|
||||||
|
showSection('upload-section');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('start-btn').addEventListener('click', startGeneration);
|
||||||
|
document.getElementById('stop-btn').addEventListener('click', stopGeneration);
|
||||||
|
document.getElementById('new-search-btn').addEventListener('click', () => {
|
||||||
|
showSection('upload-section');
|
||||||
|
appData.foundStrategies = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('download-all-btn').addEventListener('click', downloadAllStrategies);
|
||||||
|
|
||||||
|
// Comparison button
|
||||||
|
const compareBtn = document.getElementById('compare-btn');
|
||||||
|
if (compareBtn) {
|
||||||
|
compareBtn.addEventListener('click', () => {
|
||||||
|
if (appData.selectedStrategies.size < 2) {
|
||||||
|
alert('Please select at least 2 strategies to compare.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showComparison();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle CSV file upload
|
||||||
|
*/
|
||||||
|
function handleFileUpload(file) {
|
||||||
|
console.log('📁 File upload started:', file.name, 'Size:', file.size);
|
||||||
|
if (!file.name.endsWith('.csv')) {
|
||||||
|
alert('Please upload a CSV file');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Papa.parse(file, {
|
||||||
|
header: true,
|
||||||
|
dynamicTyping: true,
|
||||||
|
skipEmptyLines: true,
|
||||||
|
complete: (results) => {
|
||||||
|
console.log('📊 CSV parsed:', results.data.length, 'rows');
|
||||||
|
if (results.data.length === 0) {
|
||||||
|
alert('CSV file is empty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate CSV structure (accept both lowercase and capitalized column names)
|
||||||
|
const firstRow = results.data[0];
|
||||||
|
const requiredFields = ['open', 'high', 'low', 'close'];
|
||||||
|
const hasRequiredFields = requiredFields.every(field => {
|
||||||
|
// Check lowercase, capitalized, and uppercase versions
|
||||||
|
return field in firstRow ||
|
||||||
|
field.charAt(0).toUpperCase() + field.slice(1) in firstRow ||
|
||||||
|
field.toUpperCase() in firstRow;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasRequiredFields) {
|
||||||
|
alert('CSV must contain: time, open, high, low, close columns');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize column names to lowercase
|
||||||
|
appData.csvData = results.data.map(row => ({
|
||||||
|
time: row.time || row.Time || row.TIME,
|
||||||
|
open: parseFloat(row.open || row.Open || row.OPEN),
|
||||||
|
high: parseFloat(row.high || row.High || row.HIGH),
|
||||||
|
low: parseFloat(row.low || row.Low || row.LOW),
|
||||||
|
close: parseFloat(row.close || row.Close || row.CLOSE)
|
||||||
|
}));
|
||||||
|
|
||||||
|
appData.dataName = file.name;
|
||||||
|
console.log('✅ Data loaded successfully:', appData.csvData.length, 'bars');
|
||||||
|
|
||||||
|
// Apply data size limit
|
||||||
|
applyDataSizeLimit();
|
||||||
|
|
||||||
|
displayDataPreview();
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
alert('Error parsing CSV: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display data preview
|
||||||
|
*/
|
||||||
|
function displayDataPreview() {
|
||||||
|
const preview = document.getElementById('data-preview');
|
||||||
|
preview.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Update data info
|
||||||
|
document.getElementById('data-symbol').textContent = `File: ${appData.dataName}`;
|
||||||
|
document.getElementById('data-bars').textContent = `Bars: ${appData.csvData.length}`;
|
||||||
|
|
||||||
|
// Detect timeframe (simple heuristic)
|
||||||
|
const timeframe = detectTimeframe();
|
||||||
|
document.getElementById('data-timeframe').textContent = `Timeframe: ${timeframe}`;
|
||||||
|
|
||||||
|
// Show first 10 rows
|
||||||
|
const tbody = document.getElementById('preview-body');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
const previewRows = appData.csvData.slice(0, 10);
|
||||||
|
previewRows.forEach(row => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>${row.time || '-'}</td>
|
||||||
|
<td>${row.open.toFixed(5)}</td>
|
||||||
|
<td>${row.high.toFixed(5)}</td>
|
||||||
|
<td>${row.low.toFixed(5)}</td>
|
||||||
|
<td>${row.close.toFixed(5)}</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply data size limit
|
||||||
|
*/
|
||||||
|
function applyDataSizeLimit() {
|
||||||
|
const limit = parseInt(document.getElementById('data-size-limit').value);
|
||||||
|
if (limit > 0 && appData.csvData.length > limit) {
|
||||||
|
console.log(`✂️ Limiting data from ${appData.csvData.length} to ${limit} bars`);
|
||||||
|
appData.csvData = appData.csvData.slice(-limit); // Take last N bars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect timeframe from data
|
||||||
|
*/
|
||||||
|
function detectTimeframe() {
|
||||||
|
if (appData.csvData.length < 2) return 'Unknown';
|
||||||
|
|
||||||
|
// Try to parse time difference
|
||||||
|
const time1 = new Date(appData.csvData[0].time);
|
||||||
|
const time2 = new Date(appData.csvData[1].time);
|
||||||
|
|
||||||
|
if (isNaN(time1) || isNaN(time2)) return 'Unknown';
|
||||||
|
|
||||||
|
const diffMinutes = Math.abs(time2 - time1) / (1000 * 60);
|
||||||
|
|
||||||
|
if (diffMinutes <= 1) return 'M1';
|
||||||
|
if (diffMinutes <= 5) return 'M5';
|
||||||
|
if (diffMinutes <= 15) return 'M15';
|
||||||
|
if (diffMinutes <= 30) return 'M30';
|
||||||
|
if (diffMinutes <= 60) return 'H1';
|
||||||
|
if (diffMinutes <= 240) return 'H4';
|
||||||
|
if (diffMinutes <= 1440) return 'D1';
|
||||||
|
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start strategy generation
|
||||||
|
*/
|
||||||
|
async function startGeneration() {
|
||||||
|
console.log('🚀 Starting strategy generation...');
|
||||||
|
if (!appData.csvData) {
|
||||||
|
console.error('❌ No data loaded!');
|
||||||
|
alert('Please upload data first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get configuration
|
||||||
|
console.log('⚙️ Reading configuration...');
|
||||||
|
const config = {
|
||||||
|
populationSize: parseInt(document.getElementById('population').value),
|
||||||
|
generations: parseInt(document.getElementById('generations').value),
|
||||||
|
strategiesCount: parseInt(document.getElementById('strategies-count').value),
|
||||||
|
rulesRange: [
|
||||||
|
parseInt(document.getElementById('rules-min').value),
|
||||||
|
parseInt(document.getElementById('rules-max').value)
|
||||||
|
],
|
||||||
|
shiftRange: [
|
||||||
|
parseInt(document.getElementById('shift-min').value),
|
||||||
|
parseInt(document.getElementById('shift-max').value)
|
||||||
|
],
|
||||||
|
minPF: parseFloat(document.getElementById('min-pf').value),
|
||||||
|
minWR: parseFloat(document.getElementById('min-wr').value),
|
||||||
|
maxDD: parseFloat(document.getElementById('max-dd').value),
|
||||||
|
minTrades: parseInt(document.getElementById('min-trades').value)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show progress section
|
||||||
|
showSection('progress-section');
|
||||||
|
appData.isGenerating = true;
|
||||||
|
appData.foundStrategies = [];
|
||||||
|
|
||||||
|
// Reset buttons visibility
|
||||||
|
const stopBtn = document.getElementById('stop-btn');
|
||||||
|
if (stopBtn) stopBtn.classList.remove('hidden');
|
||||||
|
|
||||||
|
const viewResultsBtn = document.getElementById('view-results-btn');
|
||||||
|
if (viewResultsBtn) viewResultsBtn.classList.add('hidden');
|
||||||
|
|
||||||
|
// Clear progress log
|
||||||
|
document.getElementById('progress-log').innerHTML = '';
|
||||||
|
|
||||||
|
// Create optimizer
|
||||||
|
console.log('🧬 Creating GA optimizer with config:', config);
|
||||||
|
appData.optimizer = new GeneticOptimizer(appData.csvData, config);
|
||||||
|
|
||||||
|
// Start time
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// Run optimization
|
||||||
|
console.log('▶️ Starting GA run...');
|
||||||
|
const strategies = await appData.optimizer.run((progress) => {
|
||||||
|
updateProgress(progress, startTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle completion
|
||||||
|
console.log('✅ GA complete! Found', strategies.length, 'strategies');
|
||||||
|
appData.foundStrategies = strategies;
|
||||||
|
appData.isGenerating = false;
|
||||||
|
|
||||||
|
// Stay on page as requested, show redirection button
|
||||||
|
showGenerationCompleteUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update progress display
|
||||||
|
*/
|
||||||
|
function updateProgress(progress, startTime) {
|
||||||
|
// Update stats
|
||||||
|
if (progress.generation !== undefined) {
|
||||||
|
document.getElementById('current-gen').textContent =
|
||||||
|
`${progress.generation} / ${progress.totalGenerations}`;
|
||||||
|
document.getElementById('best-fitness').textContent =
|
||||||
|
progress.bestFitness.toFixed(2);
|
||||||
|
document.getElementById('found-count').textContent =
|
||||||
|
`${progress.foundCount} / ${progress.targetCount}`;
|
||||||
|
|
||||||
|
// Update progress bar
|
||||||
|
const progressPercent = (progress.generation / progress.totalGenerations) * 100;
|
||||||
|
document.getElementById('progress-bar').style.width = progressPercent + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update elapsed time
|
||||||
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||||
|
document.getElementById('elapsed-time').textContent = formatTime(elapsed);
|
||||||
|
|
||||||
|
// Log strategy found
|
||||||
|
if (progress.strategyFound) {
|
||||||
|
// Don't add to appData.foundStrategies here - we'll get them from GA at the end
|
||||||
|
// This prevents duplicate storage and ensures metrics are preserved
|
||||||
|
|
||||||
|
const log = document.getElementById('progress-log');
|
||||||
|
const m = progress.strategyFound.metrics;
|
||||||
|
const logEntry = document.createElement('div');
|
||||||
|
logEntry.className = 'log-entry';
|
||||||
|
logEntry.style.display = 'flex';
|
||||||
|
logEntry.style.justifyContent = 'space-between';
|
||||||
|
logEntry.style.alignItems = 'center';
|
||||||
|
logEntry.style.padding = '5px 0';
|
||||||
|
logEntry.style.color = '#48bb78';
|
||||||
|
|
||||||
|
const text = document.createElement('span');
|
||||||
|
text.textContent = `✓ Strategy ${progress.foundCount}: PF=${m.profitFactor.toFixed(2)}, WR=${m.winRate.toFixed(1)}%, Trades=${m.totalTrades}`;
|
||||||
|
|
||||||
|
const viewBtn = document.createElement('button');
|
||||||
|
viewBtn.className = 'btn-small';
|
||||||
|
viewBtn.textContent = 'View Details';
|
||||||
|
viewBtn.onclick = () => {
|
||||||
|
const index = progress.foundCount - 1;
|
||||||
|
viewStrategyDetails(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
logEntry.appendChild(text);
|
||||||
|
logEntry.appendChild(viewBtn);
|
||||||
|
log.appendChild(logEntry);
|
||||||
|
log.scrollTop = log.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop generation
|
||||||
|
*/
|
||||||
|
function stopGeneration() {
|
||||||
|
if (appData.optimizer) {
|
||||||
|
appData.optimizer.stop();
|
||||||
|
appData.isGenerating = false;
|
||||||
|
|
||||||
|
// Update collection with whatever was found so far
|
||||||
|
if (appData.optimizer.foundStrategies) {
|
||||||
|
appData.foundStrategies = appData.optimizer.foundStrategies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appData.foundStrategies.length > 0) {
|
||||||
|
showGenerationCompleteUI();
|
||||||
|
} else {
|
||||||
|
alert('No strategies found yet');
|
||||||
|
showSection('config-section');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show UI when generation is complete/stopped
|
||||||
|
*/
|
||||||
|
function showGenerationCompleteUI() {
|
||||||
|
const stopBtn = document.getElementById('stop-btn');
|
||||||
|
if (stopBtn) stopBtn.classList.add('hidden');
|
||||||
|
|
||||||
|
let viewResultsBtn = document.getElementById('view-results-btn');
|
||||||
|
if (!viewResultsBtn) {
|
||||||
|
viewResultsBtn = document.createElement('button');
|
||||||
|
viewResultsBtn.id = 'view-results-btn';
|
||||||
|
viewResultsBtn.className = 'btn-success';
|
||||||
|
viewResultsBtn.textContent = 'View All Results →';
|
||||||
|
viewResultsBtn.style.marginLeft = '10px';
|
||||||
|
viewResultsBtn.onclick = displayResults;
|
||||||
|
if (stopBtn && stopBtn.parentNode) {
|
||||||
|
stopBtn.parentNode.appendChild(viewResultsBtn);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewResultsBtn.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display results
|
||||||
|
*/
|
||||||
|
function displayResults() {
|
||||||
|
console.log('📊 Displaying results...', appData.foundStrategies.length, 'strategies');
|
||||||
|
showSection('results-section');
|
||||||
|
|
||||||
|
const grid = document.getElementById('strategies-grid');
|
||||||
|
grid.innerHTML = '';
|
||||||
|
|
||||||
|
if (appData.foundStrategies.length === 0) {
|
||||||
|
console.warn('⚠️ No strategies to display!');
|
||||||
|
grid.innerHTML = '<p style="color: #a0aec0; text-align: center; padding: 40px;">No strategies found. Try adjusting your criteria.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
appData.foundStrategies.forEach((strategy, index) => {
|
||||||
|
console.log(`Creating card for strategy ${index + 1}:`, strategy.metrics);
|
||||||
|
const card = createStrategyCard(strategy, index + 1);
|
||||||
|
grid.appendChild(card);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset selection on display
|
||||||
|
appData.selectedStrategies.clear();
|
||||||
|
updateCompareButton();
|
||||||
|
|
||||||
|
console.log('✅ Strategy cards created:', grid.children.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create strategy card
|
||||||
|
*/
|
||||||
|
function createStrategyCard(strategy, number) {
|
||||||
|
console.log('Creating card for strategy number:', number, 'Strategy:', strategy);
|
||||||
|
|
||||||
|
if (!strategy || !strategy.metrics) {
|
||||||
|
console.error('Invalid strategy object:', strategy);
|
||||||
|
const errorCard = document.createElement('div');
|
||||||
|
errorCard.className = 'strategy-card';
|
||||||
|
errorCard.innerHTML = '<p style="color: red;">Error: Invalid strategy data</p>';
|
||||||
|
return errorCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = strategy.metrics;
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'strategy-card';
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="strategy-header">
|
||||||
|
<div class="strategy-name">${name}</div>
|
||||||
|
<input type="checkbox" class="strategy-select" onchange="toggleStrategySelection(${number - 1}, this.checked)">
|
||||||
|
</div>
|
||||||
|
<div class="strategy-metrics">
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Profit Factor</span>
|
||||||
|
<span class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">
|
||||||
|
${m.profitFactor.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Win Rate</span>
|
||||||
|
<span class="metric-value">${m.winRate.toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Trades</span>
|
||||||
|
<span class="metric-value">${m.totalTrades}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Max DD</span>
|
||||||
|
<span class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="strategy-actions">
|
||||||
|
<button class="btn-secondary" onclick="viewStrategyDetails(${number - 1})">View Details</button>
|
||||||
|
<button class="btn-primary" onclick="downloadStrategy(${number - 1}, 'mq4')">MQ4</button>
|
||||||
|
<button class="btn-primary" onclick="downloadStrategy(${number - 1}, 'mq5')">MQ5</button>
|
||||||
|
<button class="btn-success" onclick="downloadStrategy(${number - 1}, 'report')">Report</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
console.log('Card HTML created successfully');
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download single strategy
|
||||||
|
*/
|
||||||
|
function downloadStrategy(index, type) {
|
||||||
|
console.log(`💾 Downloading strategy ${index} type ${type}...`);
|
||||||
|
const strategy = appData.foundStrategies[index];
|
||||||
|
if (!strategy) {
|
||||||
|
console.error(`❌ Strategy at index ${index} not found!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = strategy.metrics;
|
||||||
|
const name = `FxMath_${String(index + 1).padStart(3, '0')}_PF${m.profitFactor.toFixed(2).replace('.', '_')}_WR${Math.round(m.winRate)}`;
|
||||||
|
|
||||||
|
let content, filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Convert strategy to EA-Convertor format
|
||||||
|
const strategyData = {
|
||||||
|
parameters: {
|
||||||
|
symbol: 'EURUSD',
|
||||||
|
atr_period: strategy.atrPeriod,
|
||||||
|
sl_multiplier: strategy.slMultiplier,
|
||||||
|
tp_multiplier: strategy.tpMultiplier,
|
||||||
|
close_at_opposite: strategy.closeAtOpposite || true
|
||||||
|
},
|
||||||
|
buy_rules: strategy.rules,
|
||||||
|
sell_rules: strategy.rules // SELL rules are inverted in the converters
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === 'mq4') {
|
||||||
|
const converter = new MQ4Converter(strategyData);
|
||||||
|
content = converter.generate();
|
||||||
|
filename = name + '.mq4';
|
||||||
|
} else if (type === 'mq5') {
|
||||||
|
const converter = new MQ5Converter(strategyData);
|
||||||
|
content = converter.generate();
|
||||||
|
filename = name + '.mq5';
|
||||||
|
} else if (type === 'ctrader') {
|
||||||
|
const converter = new CTraderConverter(strategyData);
|
||||||
|
content = converter.generate();
|
||||||
|
filename = name + '.cs';
|
||||||
|
} else if (type === 'pine') {
|
||||||
|
const converter = new PineConverter(strategyData);
|
||||||
|
content = converter.generate();
|
||||||
|
filename = name + '.pine';
|
||||||
|
} else if (type === 'report') {
|
||||||
|
// Use new HTML report generator
|
||||||
|
content = generateHTMLReport(strategy, index);
|
||||||
|
filename = name + '_report.html';
|
||||||
|
} else if (type === 'json') {
|
||||||
|
content = JSON.stringify(strategy.toJSON(), null, 2);
|
||||||
|
filename = name + '.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Content generated for ${filename}, size: ${content.length}`);
|
||||||
|
downloadFile(content, filename);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error generating ${type} for strategy ${index}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download all strategies
|
||||||
|
*/
|
||||||
|
function downloadAllStrategies() {
|
||||||
|
console.log('📥 Download All Strategies started...', appData.foundStrategies.length, 'strategies');
|
||||||
|
|
||||||
|
if (appData.foundStrategies.length === 0) {
|
||||||
|
alert('No strategies to download');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = document.getElementById('download-all-btn');
|
||||||
|
const originalText = btn.textContent;
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Preparing Downloads...';
|
||||||
|
|
||||||
|
const total = appData.foundStrategies.length;
|
||||||
|
let completed = 0;
|
||||||
|
|
||||||
|
appData.foundStrategies.forEach((strategy, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(`⏱️ Triggering downloads for strategy ${index + 1}/${total}...`);
|
||||||
|
btn.textContent = `Downloading ${index + 1}/${total}...`;
|
||||||
|
|
||||||
|
downloadStrategy(index, 'mq4');
|
||||||
|
setTimeout(() => downloadStrategy(index, 'mq5'), 150);
|
||||||
|
setTimeout(() => downloadStrategy(index, 'report'), 300);
|
||||||
|
setTimeout(() => downloadStrategy(index, 'json'), 450);
|
||||||
|
|
||||||
|
completed++;
|
||||||
|
if (completed === total) {
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = originalText;
|
||||||
|
console.log('✅ All downloads triggered!');
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}, index * 1200); // 1.2s per strategy to be safe
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download file helper
|
||||||
|
*/
|
||||||
|
function downloadFile(content, filename) {
|
||||||
|
const blob = new Blob([content], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
// Delay removal and revocation to ensure browser captures the click
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show specific section
|
||||||
|
*/
|
||||||
|
function showSection(sectionId) {
|
||||||
|
const sections = document.querySelectorAll('.section');
|
||||||
|
sections.forEach(section => section.classList.remove('active'));
|
||||||
|
document.getElementById(sectionId).classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle strategy selection for comparison
|
||||||
|
*/
|
||||||
|
function toggleStrategySelection(index, isSelected) {
|
||||||
|
if (isSelected) {
|
||||||
|
appData.selectedStrategies.add(index);
|
||||||
|
} else {
|
||||||
|
appData.selectedStrategies.delete(index);
|
||||||
|
}
|
||||||
|
updateCompareButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update comparison button text
|
||||||
|
*/
|
||||||
|
function updateCompareButton() {
|
||||||
|
const btn = document.getElementById('compare-btn');
|
||||||
|
if (btn) {
|
||||||
|
const count = appData.selectedStrategies.size;
|
||||||
|
btn.textContent = `Compare Selected (${count})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show comparison section
|
||||||
|
*/
|
||||||
|
function showComparison() {
|
||||||
|
const container = document.getElementById('comparison-container');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
appData.selectedStrategies.forEach(index => {
|
||||||
|
const strategy = appData.foundStrategies[index];
|
||||||
|
const m = strategy.metrics;
|
||||||
|
const name = `Strategy ${index + 1}`;
|
||||||
|
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'comparison-item';
|
||||||
|
item.innerHTML = `
|
||||||
|
<h3>${name}</h3>
|
||||||
|
<div class="strategy-metrics">
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">PF</span>
|
||||||
|
<span class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">${m.profitFactor.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Win Rate</span>
|
||||||
|
<span class="metric-value">${m.winRate.toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Trades</span>
|
||||||
|
<span class="metric-value">${m.totalTrades}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Max DD</span>
|
||||||
|
<span class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 15px;">
|
||||||
|
<canvas id="comp-chart-${index}" style="height: 150px;"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="strategy-actions" style="margin-top: 15px;">
|
||||||
|
<button class="btn-small" onclick="viewStrategyDetails(${index})">Full Details</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
container.appendChild(item);
|
||||||
|
|
||||||
|
// Draw small chart
|
||||||
|
setTimeout(() => {
|
||||||
|
const ctx = document.getElementById(`comp-chart-${index}`).getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: m.equity.map((_, i) => i),
|
||||||
|
datasets: [{
|
||||||
|
data: m.equity,
|
||||||
|
borderColor: '#667eea',
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 0,
|
||||||
|
fill: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: { legend: { display: false } },
|
||||||
|
scales: { x: { display: false }, y: { display: false } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
showSection('results-section'); // Ensure we are in results
|
||||||
|
document.getElementById('comparison-section').classList.remove('hidden');
|
||||||
|
document.getElementById('comparison-section').scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide comparison section
|
||||||
|
*/
|
||||||
|
function hideComparison() {
|
||||||
|
document.getElementById('comparison-section').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format time in seconds to readable format
|
||||||
|
*/
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (seconds < 60) return seconds + 's';
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = seconds % 60;
|
||||||
|
return `${minutes}m ${secs}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly expose functions to global scope for inline onclick handlers
|
||||||
|
window.downloadStrategy = downloadStrategy;
|
||||||
|
window.downloadAllStrategies = downloadAllStrategies;
|
||||||
|
window.toggleStrategySelection = toggleStrategySelection;
|
||||||
|
window.showComparison = showComparison;
|
||||||
|
window.hideComparison = hideComparison;
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
// MQ4 Converter
|
||||||
|
class MQ4Converter {
|
||||||
|
constructor(strategy) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
this.parser = new RuleParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
const { parameters, buy_rules, sell_rules } = this.strategy;
|
||||||
|
const symbol = parameters.symbol || 'EURUSD';
|
||||||
|
const atrPeriod = parameters.atr_period || 14;
|
||||||
|
const slMultiplier = parameters.sl_multiplier || 2.0;
|
||||||
|
const tpMultiplier = parameters.tp_multiplier || 3.0;
|
||||||
|
|
||||||
|
// Generate random 6-digit magic number
|
||||||
|
const magicNumber = Math.floor(100000 + Math.random() * 900000);
|
||||||
|
|
||||||
|
const buyConditions = this.parser.parseRules(buy_rules, 'mql');
|
||||||
|
const sellConditions = this.parser.parseRules(sell_rules, 'mql');
|
||||||
|
|
||||||
|
return `//+------------------------------------------------------------------+
|
||||||
|
//| Strategy_${symbol}.mq4 |
|
||||||
|
//| Generated by Strategy Converter |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
#property copyright "FxMath Quant"
|
||||||
|
#property link "https://fxmath.com"
|
||||||
|
#property version "1.00"
|
||||||
|
#property strict
|
||||||
|
|
||||||
|
//=== TRADE SETTINGS ===
|
||||||
|
input double LotSize = 0.1; // Lot Size
|
||||||
|
input int ATR_Period = ${atrPeriod}; // ATR Period
|
||||||
|
input double SL_Multiplier = ${slMultiplier.toFixed(2)}; // SL ATR Multiplier
|
||||||
|
input double TP_Multiplier = ${tpMultiplier.toFixed(2)}; // TP ATR Multiplier
|
||||||
|
input int MagicNumber = ${magicNumber}; // Magic Number
|
||||||
|
input string TradeComment = "FxMath EA"; // Trade Comment
|
||||||
|
input int Slippage = 3; // Slippage
|
||||||
|
|
||||||
|
//=== SL/TP SETTINGS ===
|
||||||
|
input bool EnableHardSL = true; // Enable Hard Stop Loss
|
||||||
|
input bool EnableHardTP = true; // Enable Hard Take Profit
|
||||||
|
|
||||||
|
//=== TRAILING STOP ===
|
||||||
|
input bool EnableTrailing = false; // Enable Trailing Stop
|
||||||
|
input double TrailingStart = 30; // Trailing Start (pips)
|
||||||
|
input double TrailingStep = 10; // Trailing Step (pips)
|
||||||
|
|
||||||
|
//=== BREAKEVEN ===
|
||||||
|
input bool EnableBreakeven = false; // Enable Breakeven
|
||||||
|
input double BreakevenStart = 20; // Breakeven Start (pips)
|
||||||
|
input double BreakevenOffset = 2; // Breakeven Offset (pips)
|
||||||
|
|
||||||
|
//=== TIME FILTER ===
|
||||||
|
input bool EnableTimeFilter = false; // Enable Time Filter
|
||||||
|
input int StartHour = 8; // Start Hour (Server Time)
|
||||||
|
input int StartMinute = 0; // Start Minute
|
||||||
|
input int EndHour = 22; // End Hour (Server Time)
|
||||||
|
input int EndMinute = 0; // End Minute
|
||||||
|
|
||||||
|
//=== SIGNAL SETTINGS ===
|
||||||
|
input bool CloseOnOpposite = true; // Close Trade on Opposite Signal
|
||||||
|
|
||||||
|
//=== DISPLAY SETTINGS ===
|
||||||
|
input bool ShowChartInfo = true; // Show Info on Chart
|
||||||
|
|
||||||
|
// Global Variables
|
||||||
|
double atrValue;
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert initialization function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
int OnInit()
|
||||||
|
{
|
||||||
|
Print("Strategy EA initialized for ${symbol}");
|
||||||
|
Print("Magic Number: ", MagicNumber);
|
||||||
|
return(INIT_SUCCEEDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert deinitialization function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnDeinit(const int reason)
|
||||||
|
{
|
||||||
|
Print("Strategy EA deinitialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert tick function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnTick()
|
||||||
|
{
|
||||||
|
// Calculate ATR
|
||||||
|
atrValue = iATR(Symbol(), 0, ATR_Period, 1);
|
||||||
|
|
||||||
|
// Check for open positions
|
||||||
|
bool hasPosition = false;
|
||||||
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
|
||||||
|
{
|
||||||
|
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
|
||||||
|
{
|
||||||
|
hasPosition = true;
|
||||||
|
ManagePosition();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no position, check for entry signals
|
||||||
|
if(!hasPosition)
|
||||||
|
{
|
||||||
|
// Check time filter
|
||||||
|
if(EnableTimeFilter && !IsTimeAllowed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check Buy Signal
|
||||||
|
if(CheckBuySignal())
|
||||||
|
{
|
||||||
|
OpenBuyOrder();
|
||||||
|
}
|
||||||
|
// Check Sell Signal
|
||||||
|
else if(CheckSellSignal())
|
||||||
|
{
|
||||||
|
OpenSellOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display chart info
|
||||||
|
if(ShowChartInfo)
|
||||||
|
DisplayChartInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Check if current time is allowed for trading |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
bool IsTimeAllowed()
|
||||||
|
{
|
||||||
|
int currentHour = Hour();
|
||||||
|
int currentMinute = Minute();
|
||||||
|
int currentMinutes = currentHour * 60 + currentMinute;
|
||||||
|
int startMinutes = StartHour * 60 + StartMinute;
|
||||||
|
int endMinutes = EndHour * 60 + EndMinute;
|
||||||
|
|
||||||
|
if(startMinutes < endMinutes)
|
||||||
|
return (currentMinutes >= startMinutes && currentMinutes < endMinutes);
|
||||||
|
else
|
||||||
|
return (currentMinutes >= startMinutes || currentMinutes < endMinutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Manage existing position |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void ManagePosition()
|
||||||
|
{
|
||||||
|
if(!OrderSelect(0, SELECT_BY_POS, MODE_TRADES))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int orderType = OrderType();
|
||||||
|
double orderOpenPrice = OrderOpenPrice();
|
||||||
|
double orderSL = OrderStopLoss();
|
||||||
|
double orderTP = OrderTakeProfit();
|
||||||
|
|
||||||
|
// Check for opposite signal
|
||||||
|
if(CloseOnOpposite)
|
||||||
|
{
|
||||||
|
if(orderType == OP_BUY && CheckSellSignal())
|
||||||
|
{
|
||||||
|
if(!OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrRed))
|
||||||
|
Print("Error closing buy order: ", GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(orderType == OP_SELL && CheckBuySignal())
|
||||||
|
{
|
||||||
|
if(!OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrGreen))
|
||||||
|
Print("Error closing sell order: ", GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentPrice = (orderType == OP_BUY) ? Bid : Ask;
|
||||||
|
double point = Point;
|
||||||
|
double pipValue = point * 10;
|
||||||
|
|
||||||
|
// Breakeven
|
||||||
|
if(EnableBreakeven)
|
||||||
|
{
|
||||||
|
double profit = (orderType == OP_BUY) ?
|
||||||
|
(currentPrice - orderOpenPrice) :
|
||||||
|
(orderOpenPrice - currentPrice);
|
||||||
|
|
||||||
|
if(profit >= BreakevenStart * pipValue)
|
||||||
|
{
|
||||||
|
double newSL = orderOpenPrice + (BreakevenOffset * pipValue *
|
||||||
|
((orderType == OP_BUY) ? 1 : -1));
|
||||||
|
|
||||||
|
if((orderType == OP_BUY && newSL > orderSL) ||
|
||||||
|
(orderType == OP_SELL && (orderSL == 0 || newSL < orderSL)))
|
||||||
|
{
|
||||||
|
if(!OrderModify(OrderTicket(), orderOpenPrice, newSL, orderTP, 0, clrBlue))
|
||||||
|
Print("Error modifying order for breakeven: ", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailing Stop
|
||||||
|
if(EnableTrailing)
|
||||||
|
{
|
||||||
|
double profit = (orderType == OP_BUY) ?
|
||||||
|
(currentPrice - orderOpenPrice) :
|
||||||
|
(orderOpenPrice - currentPrice);
|
||||||
|
|
||||||
|
if(profit >= TrailingStart * pipValue)
|
||||||
|
{
|
||||||
|
double newSL = currentPrice - (TrailingStep * pipValue *
|
||||||
|
((orderType == OP_BUY) ? 1 : -1));
|
||||||
|
|
||||||
|
if((orderType == OP_BUY && newSL > orderSL) ||
|
||||||
|
(orderType == OP_SELL && (orderSL == 0 || newSL < orderSL)))
|
||||||
|
{
|
||||||
|
if(!OrderModify(OrderTicket(), orderOpenPrice, newSL, orderTP, 0, clrBlue))
|
||||||
|
Print("Error modifying order for trailing stop: ", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Display chart information |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void DisplayChartInfo()
|
||||||
|
{
|
||||||
|
string info = "\\n=== " + TradeComment + " ===\\n";
|
||||||
|
info += "Symbol: " + Symbol() + "\\n";
|
||||||
|
info += "Magic: " + IntegerToString(MagicNumber) + "\\n";
|
||||||
|
|
||||||
|
bool hasPosition = false;
|
||||||
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
|
||||||
|
{
|
||||||
|
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
|
||||||
|
{
|
||||||
|
hasPosition = true;
|
||||||
|
info += "Position: " + (OrderType() == OP_BUY ? "BUY" : "SELL") + "\\n";
|
||||||
|
info += "Profit: " + DoubleToStr(OrderProfit(), 2) + "\\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!hasPosition)
|
||||||
|
info += "No Position\\n";
|
||||||
|
|
||||||
|
Comment(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Check Buy Signal |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
bool CheckBuySignal()
|
||||||
|
{
|
||||||
|
return (${buyConditions.join(' &&\n ')});
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Check Sell Signal |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
bool CheckSellSignal()
|
||||||
|
{
|
||||||
|
return (${sellConditions.join(' &&\n ')});
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Open Buy Order |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OpenBuyOrder()
|
||||||
|
{
|
||||||
|
double price = Ask;
|
||||||
|
double sl = EnableHardSL ? price - (atrValue * SL_Multiplier) : 0;
|
||||||
|
double tp = EnableHardTP ? price + (atrValue * TP_Multiplier) : 0;
|
||||||
|
|
||||||
|
sl = (sl > 0) ? NormalizeDouble(sl, Digits) : 0;
|
||||||
|
tp = (tp > 0) ? NormalizeDouble(tp, Digits) : 0;
|
||||||
|
|
||||||
|
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, price, Slippage, sl, tp,
|
||||||
|
TradeComment, MagicNumber, 0, clrGreen);
|
||||||
|
|
||||||
|
if(ticket > 0)
|
||||||
|
{
|
||||||
|
Print("Buy order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Print("Error opening buy order: ", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Open Sell Order |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OpenSellOrder()
|
||||||
|
{
|
||||||
|
double price = Bid;
|
||||||
|
double sl = EnableHardSL ? price + (atrValue * SL_Multiplier) : 0;
|
||||||
|
double tp = EnableHardTP ? price - (atrValue * TP_Multiplier) : 0;
|
||||||
|
|
||||||
|
sl = (sl > 0) ? NormalizeDouble(sl, Digits) : 0;
|
||||||
|
tp = (tp > 0) ? NormalizeDouble(tp, Digits) : 0;
|
||||||
|
|
||||||
|
int ticket = OrderSend(Symbol(), OP_SELL, LotSize, price, Slippage, sl, tp,
|
||||||
|
TradeComment, MagicNumber, 0, clrRed);
|
||||||
|
|
||||||
|
if(ticket > 0)
|
||||||
|
{
|
||||||
|
Print("Sell order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Print("Error opening sell order: ", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.MQ4Converter = MQ4Converter;
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
* MQ4 Code Generator - Generates MetaTrader 4 Expert Advisor code
|
||||||
|
*/
|
||||||
|
class MQ4Generator {
|
||||||
|
constructor(strategy, strategyName) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
this.name = strategyName || this.generateName();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateName() {
|
||||||
|
const pf = this.strategy.metrics.profitFactor.toFixed(2).replace('.', '_');
|
||||||
|
const wr = Math.round(this.strategy.metrics.winRate);
|
||||||
|
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||||
|
return `FxMath_PF${pf}_WR${wr}_${timestamp}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
return `//+------------------------------------------------------------------+
|
||||||
|
//| ${this.name}.mq4 |
|
||||||
|
//| Generated by FxMathQuant Web |
|
||||||
|
//| https://fxmathquant.com |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
#property copyright "FxMathQuant"
|
||||||
|
#property link "https://fxmathquant.com"
|
||||||
|
#property version "1.00"
|
||||||
|
#property strict
|
||||||
|
|
||||||
|
//--- Input Parameters
|
||||||
|
input double LotSize = 0.01; // Lot size
|
||||||
|
input int MagicNumber = ${Math.floor(Math.random() * 9000) + 1000}; // Magic number
|
||||||
|
input int ATR_Period = ${this.strategy.atrPeriod}; // ATR period
|
||||||
|
input double SL_Multiplier = ${this.strategy.slMultiplier.toFixed(2)}; // Stop Loss multiplier
|
||||||
|
input double TP_Multiplier = ${this.strategy.tpMultiplier.toFixed(2)}; // Take Profit multiplier
|
||||||
|
input int Slippage = 3; // Slippage in points
|
||||||
|
|
||||||
|
//--- Global Variables
|
||||||
|
int ticket = 0;
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert initialization function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
int OnInit()
|
||||||
|
{
|
||||||
|
Print("${this.name} initialized");
|
||||||
|
Print("Strategy Performance: PF=${this.strategy.metrics.profitFactor.toFixed(2)}, WR=${this.strategy.metrics.winRate.toFixed(1)}%");
|
||||||
|
return(INIT_SUCCEEDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert deinitialization function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnDeinit(const int reason)
|
||||||
|
{
|
||||||
|
Print("${this.name} stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert tick function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnTick()
|
||||||
|
{
|
||||||
|
// Check if we already have an open position
|
||||||
|
if(OrdersTotal() > 0)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < OrdersTotal(); i++)
|
||||||
|
{
|
||||||
|
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
|
||||||
|
{
|
||||||
|
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
|
||||||
|
return; // Position already open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate ATR
|
||||||
|
double atr = iATR(Symbol(), Period(), ATR_Period, 0);
|
||||||
|
if(atr == 0) return;
|
||||||
|
|
||||||
|
// Check for BUY signal
|
||||||
|
if(CheckBuySignal())
|
||||||
|
{
|
||||||
|
double entry = Ask;
|
||||||
|
double sl = entry - (atr * SL_Multiplier);
|
||||||
|
double tp = entry + (atr * TP_Multiplier);
|
||||||
|
|
||||||
|
// Normalize prices
|
||||||
|
sl = NormalizeDouble(sl, Digits);
|
||||||
|
tp = NormalizeDouble(tp, Digits);
|
||||||
|
|
||||||
|
// Open BUY order
|
||||||
|
ticket = OrderSend(Symbol(), OP_BUY, LotSize, entry, Slippage, sl, tp,
|
||||||
|
"${this.name}", MagicNumber, 0, clrGreen);
|
||||||
|
|
||||||
|
if(ticket > 0)
|
||||||
|
Print("BUY order opened: ", ticket, " @ ", entry, " SL:", sl, " TP:", tp);
|
||||||
|
else
|
||||||
|
Print("Error opening BUY order: ", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Check BUY signal based on strategy rules |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
bool CheckBuySignal()
|
||||||
|
{
|
||||||
|
${this.generateRulesCode()}
|
||||||
|
|
||||||
|
return true; // All rules passed
|
||||||
|
}
|
||||||
|
|
||||||
|
${this.generateHelperFunctions()}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateRulesCode() {
|
||||||
|
return this.strategy.rules.map((rule, index) => {
|
||||||
|
const condition = this.ruleToMQ4(rule);
|
||||||
|
return ` // Rule ${index + 1}
|
||||||
|
if(!(${condition})) return false;`;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleToMQ4(rule) {
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
const left = this.priceToMQ4(rule.left.price, rule.left.shift);
|
||||||
|
const right = this.priceToMQ4(rule.right.price, rule.right.shift);
|
||||||
|
return `${left} ${rule.operator} ${right}`;
|
||||||
|
} else {
|
||||||
|
// Arithmetic rule
|
||||||
|
const left1 = this.priceToMQ4(rule.left.price1, rule.left.shift1);
|
||||||
|
const left2 = this.priceToMQ4(rule.left.price2, rule.left.shift2);
|
||||||
|
const right = this.priceToMQ4(rule.right.price, rule.right.shift);
|
||||||
|
|
||||||
|
return `(${left1} ${rule.left.op} ${left2}) ${rule.operator} (${right} * ${rule.right.multiplier})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
priceToMQ4(priceType, shift) {
|
||||||
|
const type = priceType.charAt(0).toUpperCase() + priceType.slice(1);
|
||||||
|
return `i${type}(Symbol(), Period(), ${shift})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateHelperFunctions() {
|
||||||
|
return `//+------------------------------------------------------------------+
|
||||||
|
//| Helper Functions |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
// Add any additional helper functions here
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
// MQ5 Converter
|
||||||
|
class MQ5Converter {
|
||||||
|
constructor(strategy) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
this.parser = new RuleParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
const { parameters, buy_rules, sell_rules } = this.strategy;
|
||||||
|
const symbol = parameters.symbol || 'EURUSD';
|
||||||
|
const atrPeriod = parameters.atr_period || 14;
|
||||||
|
const slMultiplier = parameters.sl_multiplier || 2.0;
|
||||||
|
const tpMultiplier = parameters.tp_multiplier || 3.0;
|
||||||
|
|
||||||
|
// Generate random 6-digit magic number
|
||||||
|
const magicNumber = Math.floor(100000 + Math.random() * 900000);
|
||||||
|
|
||||||
|
const buyConditions = this.parser.parseRules(buy_rules, 'mq5');
|
||||||
|
const sellConditions = this.parser.parseRules(sell_rules, 'mq5');
|
||||||
|
|
||||||
|
return `//+------------------------------------------------------------------+
|
||||||
|
//| Strategy_${symbol}.mq5 |
|
||||||
|
//| Generated by Strategy Converter |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
#property copyright "FxMath Quant"
|
||||||
|
#property link "https://fxmath.com"
|
||||||
|
#property version "1.00"
|
||||||
|
|
||||||
|
#include <Trade\\Trade.mqh>
|
||||||
|
|
||||||
|
//=== TRADING SETTINGS ===
|
||||||
|
input group "=== Trade Settings ==="
|
||||||
|
input double LotSize = 0.1; // Lot Size
|
||||||
|
input int ATR_Period = ${atrPeriod}; // ATR Period
|
||||||
|
input double SL_Multiplier = ${slMultiplier.toFixed(2)}; // SL ATR Multiplier
|
||||||
|
input double TP_Multiplier = ${tpMultiplier.toFixed(2)}; // TP ATR Multiplier
|
||||||
|
input ulong MagicNumber = ${magicNumber}; // Magic Number
|
||||||
|
input string TradeComment = "FxMath EA"; // Trade Comment
|
||||||
|
input int Slippage = 3; // Slippage
|
||||||
|
|
||||||
|
//=== SL/TP SETTINGS ===
|
||||||
|
input group "=== SL/TP Settings ==="
|
||||||
|
input bool EnableHardSL = true; // Enable Hard Stop Loss
|
||||||
|
input bool EnableHardTP = true; // Enable Hard Take Profit
|
||||||
|
|
||||||
|
//=== TRAILING STOP ===
|
||||||
|
input group "=== Trailing Stop ==="
|
||||||
|
input bool EnableTrailing = false; // Enable Trailing Stop
|
||||||
|
input double TrailingStart = 30; // Trailing Start (pips)
|
||||||
|
input double TrailingStep = 10; // Trailing Step (pips)
|
||||||
|
|
||||||
|
//=== BREAKEVEN ===
|
||||||
|
input group "=== Breakeven ==="
|
||||||
|
input bool EnableBreakeven = false; // Enable Breakeven
|
||||||
|
input double BreakevenStart = 20; // Breakeven Start (pips)
|
||||||
|
input double BreakevenOffset = 2; // Breakeven Offset (pips)
|
||||||
|
|
||||||
|
//=== TIME FILTER ===
|
||||||
|
input group "=== Time Filter ==="
|
||||||
|
input bool EnableTimeFilter = false; // Enable Time Filter
|
||||||
|
input int StartHour = 8; // Start Hour (Server Time)
|
||||||
|
input int StartMinute = 0; // Start Minute
|
||||||
|
input int EndHour = 22; // End Hour (Server Time)
|
||||||
|
input int EndMinute = 0; // End Minute
|
||||||
|
|
||||||
|
//=== SIGNAL SETTINGS ===
|
||||||
|
input group "=== Signal Settings ==="
|
||||||
|
input bool CloseOnOpposite = true; // Close Trade on Opposite Signal
|
||||||
|
|
||||||
|
//=== DISPLAY SETTINGS ===
|
||||||
|
input group "=== Display Settings ==="
|
||||||
|
input bool ShowChartInfo = true; // Show Info on Chart
|
||||||
|
|
||||||
|
// Global Variables
|
||||||
|
CTrade trade;
|
||||||
|
int atrHandle;
|
||||||
|
double atrBuffer[];
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert initialization function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
int OnInit()
|
||||||
|
{
|
||||||
|
// Set trade parameters
|
||||||
|
trade.SetExpertMagicNumber(MagicNumber);
|
||||||
|
trade.SetDeviationInPoints(Slippage);
|
||||||
|
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||||
|
|
||||||
|
// Create ATR indicator handle
|
||||||
|
atrHandle = iATR(_Symbol, PERIOD_CURRENT, ATR_Period);
|
||||||
|
|
||||||
|
if(atrHandle == INVALID_HANDLE)
|
||||||
|
{
|
||||||
|
Print("Error creating ATR indicator");
|
||||||
|
return(INIT_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArraySetAsSeries(atrBuffer, true);
|
||||||
|
|
||||||
|
Print("Strategy EA initialized for ${symbol}");
|
||||||
|
Print("Magic Number: ", MagicNumber);
|
||||||
|
return(INIT_SUCCEEDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert deinitialization function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnDeinit(const int reason)
|
||||||
|
{
|
||||||
|
if(atrHandle != INVALID_HANDLE)
|
||||||
|
IndicatorRelease(atrHandle);
|
||||||
|
|
||||||
|
Print("Strategy EA deinitialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert tick function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnTick()
|
||||||
|
{
|
||||||
|
// Copy ATR values
|
||||||
|
if(CopyBuffer(atrHandle, 0, 0, 2, atrBuffer) < 2)
|
||||||
|
{
|
||||||
|
Print("Error copying ATR buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double atrValue = atrBuffer[1];
|
||||||
|
|
||||||
|
// Check if we have an open position
|
||||||
|
if(PositionSelect(_Symbol))
|
||||||
|
{
|
||||||
|
// Position exists - manage it
|
||||||
|
ManagePosition(atrValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No position - check for entry signals
|
||||||
|
|
||||||
|
// Check time filter
|
||||||
|
if(EnableTimeFilter && !IsTimeAllowed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check Buy Signal
|
||||||
|
if(CheckBuySignal())
|
||||||
|
{
|
||||||
|
OpenBuyOrder(atrValue);
|
||||||
|
}
|
||||||
|
// Check Sell Signal
|
||||||
|
else if(CheckSellSignal())
|
||||||
|
{
|
||||||
|
OpenSellOrder(atrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display chart info
|
||||||
|
if(ShowChartInfo)
|
||||||
|
DisplayChartInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Check if current time is allowed for trading |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
bool IsTimeAllowed()
|
||||||
|
{
|
||||||
|
MqlDateTime dt;
|
||||||
|
TimeToStruct(TimeCurrent(), dt);
|
||||||
|
|
||||||
|
int currentMinutes = dt.hour * 60 + dt.min;
|
||||||
|
int startMinutes = StartHour * 60 + StartMinute;
|
||||||
|
int endMinutes = EndHour * 60 + EndMinute;
|
||||||
|
|
||||||
|
if(startMinutes < endMinutes)
|
||||||
|
return (currentMinutes >= startMinutes && currentMinutes < endMinutes);
|
||||||
|
else
|
||||||
|
return (currentMinutes >= startMinutes || currentMinutes < endMinutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Manage existing position |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void ManagePosition(double atrValue)
|
||||||
|
{
|
||||||
|
if(!PositionSelect(_Symbol))
|
||||||
|
return;
|
||||||
|
|
||||||
|
long posType = PositionGetInteger(POSITION_TYPE);
|
||||||
|
double posOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||||
|
double posSL = PositionGetDouble(POSITION_SL);
|
||||||
|
double posTP = PositionGetDouble(POSITION_TP);
|
||||||
|
|
||||||
|
// Check for opposite signal
|
||||||
|
if(CloseOnOpposite)
|
||||||
|
{
|
||||||
|
if(posType == POSITION_TYPE_BUY && CheckSellSignal())
|
||||||
|
{
|
||||||
|
trade.PositionClose(_Symbol);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(posType == POSITION_TYPE_SELL && CheckBuySignal())
|
||||||
|
{
|
||||||
|
trade.PositionClose(_Symbol);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentPrice = (posType == POSITION_TYPE_BUY) ?
|
||||||
|
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
|
||||||
|
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||||
|
|
||||||
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||||
|
double pipValue = point * 10;
|
||||||
|
|
||||||
|
// Breakeven
|
||||||
|
if(EnableBreakeven)
|
||||||
|
{
|
||||||
|
double profit = (posType == POSITION_TYPE_BUY) ?
|
||||||
|
(currentPrice - posOpenPrice) :
|
||||||
|
(posOpenPrice - currentPrice);
|
||||||
|
|
||||||
|
if(profit >= BreakevenStart * pipValue)
|
||||||
|
{
|
||||||
|
double newSL = posOpenPrice + (BreakevenOffset * pipValue *
|
||||||
|
((posType == POSITION_TYPE_BUY) ? 1 : -1));
|
||||||
|
|
||||||
|
if((posType == POSITION_TYPE_BUY && newSL > posSL) ||
|
||||||
|
(posType == POSITION_TYPE_SELL && (posSL == 0 || newSL < posSL)))
|
||||||
|
{
|
||||||
|
trade.PositionModify(_Symbol, newSL, posTP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailing Stop
|
||||||
|
if(EnableTrailing)
|
||||||
|
{
|
||||||
|
double profit = (posType == POSITION_TYPE_BUY) ?
|
||||||
|
(currentPrice - posOpenPrice) :
|
||||||
|
(posOpenPrice - currentPrice);
|
||||||
|
|
||||||
|
if(profit >= TrailingStart * pipValue)
|
||||||
|
{
|
||||||
|
double newSL = currentPrice - (TrailingStep * pipValue *
|
||||||
|
((posType == POSITION_TYPE_BUY) ? 1 : -1));
|
||||||
|
|
||||||
|
if((posType == POSITION_TYPE_BUY && newSL > posSL) ||
|
||||||
|
(posType == POSITION_TYPE_SELL && (posSL == 0 || newSL < posSL)))
|
||||||
|
{
|
||||||
|
trade.PositionModify(_Symbol, newSL, posTP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Display chart information |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void DisplayChartInfo()
|
||||||
|
{
|
||||||
|
string info = "\\n=== " + TradeComment + " ===\\n";
|
||||||
|
info += "Symbol: " + _Symbol + "\\n";
|
||||||
|
info += "Magic: " + IntegerToString(MagicNumber) + "\\n";
|
||||||
|
|
||||||
|
if(PositionSelect(_Symbol))
|
||||||
|
{
|
||||||
|
info += "Position: " + EnumToString((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)) + "\\n";
|
||||||
|
info += "Profit: " + DoubleToString(PositionGetDouble(POSITION_PROFIT), 2) + "\\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info += "No Position\\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
Comment(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Check Buy Signal |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
bool CheckBuySignal()
|
||||||
|
{
|
||||||
|
return (${buyConditions.join(' &&\n ')});
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Check Sell Signal |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
bool CheckSellSignal()
|
||||||
|
{
|
||||||
|
return (${sellConditions.join(' &&\n ')});
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Open Buy Order |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OpenBuyOrder(double atrValue)
|
||||||
|
{
|
||||||
|
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||||
|
double sl = EnableHardSL ? price - (atrValue * SL_Multiplier) : 0;
|
||||||
|
double tp = EnableHardTP ? price + (atrValue * TP_Multiplier) : 0;
|
||||||
|
|
||||||
|
sl = (sl > 0) ? NormalizeDouble(sl, _Digits) : 0;
|
||||||
|
tp = (tp > 0) ? NormalizeDouble(tp, _Digits) : 0;
|
||||||
|
|
||||||
|
if(trade.Buy(LotSize, _Symbol, price, sl, tp, TradeComment))
|
||||||
|
{
|
||||||
|
Print("Buy order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Print("Error opening buy order: ", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Open Sell Order |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OpenSellOrder(double atrValue)
|
||||||
|
{
|
||||||
|
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||||
|
double sl = EnableHardSL ? price + (atrValue * SL_Multiplier) : 0;
|
||||||
|
double tp = EnableHardTP ? price - (atrValue * TP_Multiplier) : 0;
|
||||||
|
|
||||||
|
sl = (sl > 0) ? NormalizeDouble(sl, _Digits) : 0;
|
||||||
|
tp = (tp > 0) ? NormalizeDouble(tp, _Digits) : 0;
|
||||||
|
|
||||||
|
if(trade.Sell(LotSize, _Symbol, price, sl, tp, TradeComment))
|
||||||
|
{
|
||||||
|
Print("Sell order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Print("Error opening sell order: ", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.MQ5Converter = MQ5Converter;
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
/**
|
||||||
|
* MQ5 Code Generator - Generates MetaTrader 5 Expert Advisor code
|
||||||
|
*/
|
||||||
|
class MQ5Generator {
|
||||||
|
constructor(strategy, strategyName) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
this.name = strategyName || this.generateName();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateName() {
|
||||||
|
const pf = this.strategy.metrics.profitFactor.toFixed(2).replace('.', '_');
|
||||||
|
const wr = Math.round(this.strategy.metrics.winRate);
|
||||||
|
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||||
|
return `FxMath_PF${pf}_WR${wr}_${timestamp}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
return `//+------------------------------------------------------------------+
|
||||||
|
//| ${this.name}.mq5 |
|
||||||
|
//| Generated by FxMathQuant Web |
|
||||||
|
//| https://fxmathquant.com |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
#property copyright "FxMathQuant"
|
||||||
|
#property link "https://fxmathquant.com"
|
||||||
|
#property version "1.00"
|
||||||
|
|
||||||
|
//--- Include libraries
|
||||||
|
#include <Trade\\Trade.mqh>
|
||||||
|
|
||||||
|
//--- Input Parameters
|
||||||
|
input double LotSize = 0.01; // Lot size
|
||||||
|
input int MagicNumber = ${Math.floor(Math.random() * 9000) + 1000}; // Magic number
|
||||||
|
input int ATR_Period = ${this.strategy.atrPeriod}; // ATR period
|
||||||
|
input double SL_Multiplier = ${this.strategy.slMultiplier.toFixed(2)}; // Stop Loss multiplier
|
||||||
|
input double TP_Multiplier = ${this.strategy.tpMultiplier.toFixed(2)}; // Take Profit multiplier
|
||||||
|
input int Slippage = 3; // Slippage in points
|
||||||
|
|
||||||
|
//--- Global Variables
|
||||||
|
CTrade trade;
|
||||||
|
int atrHandle;
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert initialization function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
int OnInit()
|
||||||
|
{
|
||||||
|
// Set magic number
|
||||||
|
trade.SetExpertMagicNumber(MagicNumber);
|
||||||
|
trade.SetDeviationInPoints(Slippage);
|
||||||
|
|
||||||
|
// Create ATR indicator handle
|
||||||
|
atrHandle = iATR(_Symbol, _Period, ATR_Period);
|
||||||
|
if(atrHandle == INVALID_HANDLE)
|
||||||
|
{
|
||||||
|
Print("Error creating ATR indicator");
|
||||||
|
return(INIT_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
Print("${this.name} initialized");
|
||||||
|
Print("Strategy Performance: PF=${this.strategy.metrics.profitFactor.toFixed(2)}, WR=${this.strategy.metrics.winRate.toFixed(1)}%");
|
||||||
|
|
||||||
|
return(INIT_SUCCEEDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert deinitialization function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnDeinit(const int reason)
|
||||||
|
{
|
||||||
|
// Release indicator handle
|
||||||
|
if(atrHandle != INVALID_HANDLE)
|
||||||
|
IndicatorRelease(atrHandle);
|
||||||
|
|
||||||
|
Print("${this.name} stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Expert tick function |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
void OnTick()
|
||||||
|
{
|
||||||
|
// Check if we already have an open position
|
||||||
|
if(PositionSelect(_Symbol))
|
||||||
|
{
|
||||||
|
if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
|
||||||
|
return; // Position already open
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ATR value
|
||||||
|
double atrBuffer[];
|
||||||
|
ArraySetAsSeries(atrBuffer, true);
|
||||||
|
if(CopyBuffer(atrHandle, 0, 0, 1, atrBuffer) <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double atr = atrBuffer[0];
|
||||||
|
if(atr == 0) return;
|
||||||
|
|
||||||
|
// Check for BUY signal
|
||||||
|
if(CheckBuySignal())
|
||||||
|
{
|
||||||
|
double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||||
|
double sl = entry - (atr * SL_Multiplier);
|
||||||
|
double tp = entry + (atr * TP_Multiplier);
|
||||||
|
|
||||||
|
// Normalize prices
|
||||||
|
sl = NormalizeDouble(sl, _Digits);
|
||||||
|
tp = NormalizeDouble(tp, _Digits);
|
||||||
|
|
||||||
|
// Open BUY position
|
||||||
|
if(trade.Buy(LotSize, _Symbol, entry, sl, tp, "${this.name}"))
|
||||||
|
Print("BUY position opened @ ", entry, " SL:", sl, " TP:", tp);
|
||||||
|
else
|
||||||
|
Print("Error opening BUY position: ", trade.ResultRetcode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
//| Check BUY signal based on strategy rules |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
bool CheckBuySignal()
|
||||||
|
{
|
||||||
|
${this.generateRulesCode()}
|
||||||
|
|
||||||
|
return true; // All rules passed
|
||||||
|
}
|
||||||
|
|
||||||
|
${this.generateHelperFunctions()}
|
||||||
|
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateRulesCode() {
|
||||||
|
return this.strategy.rules.map((rule, index) => {
|
||||||
|
const condition = this.ruleToMQ5(rule);
|
||||||
|
return ` // Rule ${index + 1}
|
||||||
|
if(!(${condition})) return false;`;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleToMQ5(rule) {
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
const left = this.priceToMQ5(rule.left.price, rule.left.shift);
|
||||||
|
const right = this.priceToMQ5(rule.right.price, rule.right.shift);
|
||||||
|
return `${left} ${rule.operator} ${right}`;
|
||||||
|
} else {
|
||||||
|
// Arithmetic rule
|
||||||
|
const left1 = this.priceToMQ5(rule.left.price1, rule.left.shift1);
|
||||||
|
const left2 = this.priceToMQ5(rule.left.price2, rule.left.shift2);
|
||||||
|
const right = this.priceToMQ5(rule.right.price, rule.right.shift);
|
||||||
|
|
||||||
|
return `(${left1} ${rule.left.op} ${left2}) ${rule.operator} (${right} * ${rule.right.multiplier})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
priceToMQ5(priceType, shift) {
|
||||||
|
const type = priceType.charAt(0).toUpperCase() + priceType.slice(1).toLowerCase();
|
||||||
|
return `iClose(_Symbol, _Period, ${shift})`.replace('Close', type);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateHelperFunctions() {
|
||||||
|
return `//+------------------------------------------------------------------+
|
||||||
|
//| Get price value at specific shift |
|
||||||
|
//+------------------------------------------------------------------+
|
||||||
|
double iOpen(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
||||||
|
{
|
||||||
|
double buffer[];
|
||||||
|
ArraySetAsSeries(buffer, true);
|
||||||
|
if(CopyOpen(symbol, timeframe, shift, 1, buffer) > 0)
|
||||||
|
return buffer[0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double iHigh(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
||||||
|
{
|
||||||
|
double buffer[];
|
||||||
|
ArraySetAsSeries(buffer, true);
|
||||||
|
if(CopyHigh(symbol, timeframe, shift, 1, buffer) > 0)
|
||||||
|
return buffer[0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double iLow(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
||||||
|
{
|
||||||
|
double buffer[];
|
||||||
|
ArraySetAsSeries(buffer, true);
|
||||||
|
if(CopyLow(symbol, timeframe, shift, 1, buffer) > 0)
|
||||||
|
return buffer[0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double iClose(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
||||||
|
{
|
||||||
|
double buffer[];
|
||||||
|
ArraySetAsSeries(buffer, true);
|
||||||
|
if(CopyClose(symbol, timeframe, shift, 1, buffer) > 0)
|
||||||
|
return buffer[0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Pine Script Converter
|
||||||
|
class PineConverter {
|
||||||
|
constructor(strategy) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
this.parser = new RuleParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
const { parameters, buy_rules, sell_rules } = this.strategy;
|
||||||
|
const symbol = parameters.symbol || 'EURUSD';
|
||||||
|
const atrPeriod = parameters.atr_period || 14;
|
||||||
|
const slMultiplier = parameters.sl_multiplier || 2.0;
|
||||||
|
const tpMultiplier = parameters.tp_multiplier || 3.0;
|
||||||
|
|
||||||
|
const buyConditions = this.parser.parseRules(buy_rules, 'pine');
|
||||||
|
const sellConditions = this.parser.parseRules(sell_rules, 'pine');
|
||||||
|
|
||||||
|
return `//@version=5
|
||||||
|
strategy("Strategy ${symbol}", overlay=true,
|
||||||
|
initial_capital=10000,
|
||||||
|
default_qty_type=strategy.percent_of_equity,
|
||||||
|
default_qty_value=100,
|
||||||
|
commission_type=strategy.commission.percent,
|
||||||
|
commission_value=0.1)
|
||||||
|
|
||||||
|
// Input Parameters
|
||||||
|
lotSize = input.float(0.01, "Lot Size", minval=0.01, step=0.01)
|
||||||
|
atrPeriod = input.int(${atrPeriod}, "ATR Period", minval=1)
|
||||||
|
slMultiplier = input.float(${slMultiplier.toFixed(2)}, "Stop Loss Multiplier", minval=0.1, step=0.1)
|
||||||
|
tpMultiplier = input.float(${tpMultiplier.toFixed(2)}, "Take Profit Multiplier", minval=0.1, step=0.1)
|
||||||
|
|
||||||
|
// Calculate ATR
|
||||||
|
atrValue = ta.atr(atrPeriod)
|
||||||
|
|
||||||
|
// Buy Signal Conditions
|
||||||
|
buySignal = ${buyConditions.join(' and\n ')}
|
||||||
|
|
||||||
|
// Sell Signal Conditions
|
||||||
|
sellSignal = ${sellConditions.join(' and\n ')}
|
||||||
|
|
||||||
|
// Calculate Stop Loss and Take Profit levels
|
||||||
|
longStopLoss = close - (atrValue * slMultiplier)
|
||||||
|
longTakeProfit = close + (atrValue * tpMultiplier)
|
||||||
|
shortStopLoss = close + (atrValue * slMultiplier)
|
||||||
|
shortTakeProfit = close - (atrValue * tpMultiplier)
|
||||||
|
|
||||||
|
// Strategy Entry and Exit
|
||||||
|
if (buySignal)
|
||||||
|
strategy.entry("Long", strategy.long, comment="Buy Signal")
|
||||||
|
strategy.exit("Long Exit", "Long", stop=longStopLoss, limit=longTakeProfit)
|
||||||
|
|
||||||
|
if (sellSignal)
|
||||||
|
strategy.entry("Short", strategy.short, comment="Sell Signal")
|
||||||
|
strategy.exit("Short Exit", "Short", stop=shortStopLoss, limit=shortTakeProfit)
|
||||||
|
|
||||||
|
// Plot Buy and Sell signals
|
||||||
|
plotshape(buySignal, title="Buy Signal", location=location.belowbar,
|
||||||
|
color=color.green, style=shape.triangleup, size=size.small)
|
||||||
|
plotshape(sellSignal, title="Sell Signal", location=location.abovebar,
|
||||||
|
color=color.red, style=shape.triangledown, size=size.small)
|
||||||
|
|
||||||
|
// Plot Stop Loss and Take Profit levels
|
||||||
|
plot(strategy.position_size > 0 ? longStopLoss : na,
|
||||||
|
title="Long SL", color=color.red, style=plot.style_linebr, linewidth=1)
|
||||||
|
plot(strategy.position_size > 0 ? longTakeProfit : na,
|
||||||
|
title="Long TP", color=color.green, style=plot.style_linebr, linewidth=1)
|
||||||
|
plot(strategy.position_size < 0 ? shortStopLoss : na,
|
||||||
|
title="Short SL", color=color.red, style=plot.style_linebr, linewidth=1)
|
||||||
|
plot(strategy.position_size < 0 ? shortTakeProfit : na,
|
||||||
|
title="Short TP", color=color.green, style=plot.style_linebr, linewidth=1)
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.PineConverter = PineConverter;
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
/**
|
||||||
|
* HTML Report Generator - Creates interactive performance reports
|
||||||
|
*/
|
||||||
|
class ReportGenerator {
|
||||||
|
constructor(strategy, strategyName) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
this.name = strategyName || 'Strategy Report';
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
const m = this.strategy.metrics;
|
||||||
|
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>${this.name} - Performance Report</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', -apple-system, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
h1 { font-size: 32px; margin-bottom: 10px; color: #667eea; }
|
||||||
|
.subtitle { color: #a0aec0; margin-bottom: 30px; }
|
||||||
|
.metrics-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
.metric-card {
|
||||||
|
background: #1a1f3a;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #2d3748;
|
||||||
|
}
|
||||||
|
.metric-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #718096;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.metric-value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.positive { color: #48bb78; }
|
||||||
|
.negative { color: #f56565; }
|
||||||
|
.neutral { color: #667eea; }
|
||||||
|
.chart-container {
|
||||||
|
background: #1a1f3a;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border: 1px solid #2d3748;
|
||||||
|
}
|
||||||
|
.rules-section {
|
||||||
|
background: #1a1f3a;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border: 1px solid #2d3748;
|
||||||
|
}
|
||||||
|
.rule {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
background: #0a0e27;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background: #1a1f3a;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #2d3748;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: #0a0e27;
|
||||||
|
color: #a0aec0;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
tr:hover { background: #252b4a; }
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 40px;
|
||||||
|
color: #718096;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>${this.name}</h1>
|
||||||
|
<p class="subtitle">Generated by FxMathQuant Web - AI-Powered Strategy Generator</p>
|
||||||
|
|
||||||
|
<div class="metrics-grid">
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Profit Factor</div>
|
||||||
|
<div class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">
|
||||||
|
${m.profitFactor.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Win Rate</div>
|
||||||
|
<div class="metric-value neutral">${m.winRate.toFixed(1)}%</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Total Trades</div>
|
||||||
|
<div class="metric-value neutral">${m.totalTrades}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Max Drawdown</div>
|
||||||
|
<div class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Total Profit</div>
|
||||||
|
<div class="metric-value ${m.totalProfit >= 0 ? 'positive' : 'negative'}">
|
||||||
|
$${m.totalProfit.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Final Balance</div>
|
||||||
|
<div class="metric-value positive">$${m.finalBalance.toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Avg Win</div>
|
||||||
|
<div class="metric-value positive">$${m.avgWin.toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Avg Loss</div>
|
||||||
|
<div class="metric-value negative">$${Math.abs(m.avgLoss).toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-container">
|
||||||
|
<h2 style="margin-bottom: 20px;">Equity Curve</h2>
|
||||||
|
<canvas id="equityChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rules-section">
|
||||||
|
<h2 style="margin-bottom: 20px;">Strategy Rules</h2>
|
||||||
|
<p style="margin-bottom: 15px; color: #a0aec0;">BUY when ALL of the following conditions are true:</p>
|
||||||
|
${this.generateRulesHTML()}
|
||||||
|
<div style="margin-top: 20px; padding: 15px; background: #0a0e27; border-radius: 6px;">
|
||||||
|
<strong>Parameters:</strong><br>
|
||||||
|
ATR Period: ${this.strategy.atrPeriod} |
|
||||||
|
SL Multiplier: ${this.strategy.slMultiplier.toFixed(2)} |
|
||||||
|
TP Multiplier: ${this.strategy.tpMultiplier.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this.generateTradesTable()}
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2025 FxMathQuant. All rights reserved.</p>
|
||||||
|
<p>This report was generated automatically by AI-powered genetic algorithms.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Equity Curve Chart
|
||||||
|
const ctx = document.getElementById('equityChart').getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: ${JSON.stringify(m.equity.map((_, i) => i))},
|
||||||
|
datasets: [{
|
||||||
|
label: 'Account Balance',
|
||||||
|
data: ${JSON.stringify(m.equity)},
|
||||||
|
borderColor: '#667eea',
|
||||||
|
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: '#1a1f3a',
|
||||||
|
titleColor: '#fff',
|
||||||
|
bodyColor: '#a0aec0',
|
||||||
|
borderColor: '#2d3748',
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: false,
|
||||||
|
grid: { color: '#2d3748' },
|
||||||
|
ticks: { color: '#a0aec0' }
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: { color: '#2d3748' },
|
||||||
|
ticks: { color: '#a0aec0' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateRulesHTML() {
|
||||||
|
return this.strategy.rules.map((rule, index) => {
|
||||||
|
let ruleText = '';
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||||
|
} else {
|
||||||
|
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||||
|
}
|
||||||
|
return `<div class="rule">${index + 1}. ${ruleText}</div>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTradesTable() {
|
||||||
|
const trades = this.strategy.metrics.trades || [];
|
||||||
|
if (trades.length === 0) return '';
|
||||||
|
|
||||||
|
// Show first 50 trades
|
||||||
|
const displayTrades = trades.slice(0, 50);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="background: #1a1f3a; padding: 30px; border-radius: 12px; border: 1px solid #2d3748;">
|
||||||
|
<h2 style="margin-bottom: 20px;">Trade History ${trades.length > 50 ? '(First 50 trades)' : ''}</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Entry</th>
|
||||||
|
<th>Exit</th>
|
||||||
|
<th>Profit</th>
|
||||||
|
<th>Reason</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${displayTrades.map((trade, i) => `
|
||||||
|
<tr>
|
||||||
|
<td>${i + 1}</td>
|
||||||
|
<td>${trade.type}</td>
|
||||||
|
<td>${trade.entry.toFixed(5)}</td>
|
||||||
|
<td>${trade.exit.toFixed(5)}</td>
|
||||||
|
<td class="${trade.profit >= 0 ? 'positive' : 'negative'}">
|
||||||
|
$${trade.profit.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
<td>${trade.reason.replace('_', ' ')}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// Rule Parser - Converts strategy rule objects to platform-specific syntax
|
||||||
|
class RuleParser {
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
parseRules(rules, platform = 'mql') {
|
||||||
|
return rules.map(rule => this.parseRule(rule, platform));
|
||||||
|
}
|
||||||
|
|
||||||
|
parseRule(rule, platform = 'mql') {
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
return this.parseSimpleRule(rule, platform);
|
||||||
|
} else {
|
||||||
|
return this.parseArithmeticRule(rule, platform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseSimpleRule(rule, platform) {
|
||||||
|
const left = this.getPriceReference(rule.left.price, rule.left.shift, platform);
|
||||||
|
const right = this.getPriceReference(rule.right.price, rule.right.shift, platform);
|
||||||
|
const operator = rule.operator;
|
||||||
|
|
||||||
|
return `${left} ${operator} ${right}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseArithmeticRule(rule, platform) {
|
||||||
|
const left1 = this.getPriceReference(rule.left.price1, rule.left.shift1, platform);
|
||||||
|
const left2 = this.getPriceReference(rule.left.price2, rule.left.shift2, platform);
|
||||||
|
const leftOp = rule.left.op;
|
||||||
|
const right = this.getPriceReference(rule.right.price, rule.right.shift, platform);
|
||||||
|
const multiplier = rule.right.multiplier;
|
||||||
|
const operator = rule.operator;
|
||||||
|
|
||||||
|
return `(${left1} ${leftOp} ${left2}) ${operator} (${right} * ${multiplier})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPriceReference(priceType, shift, platform) {
|
||||||
|
const price = priceType.charAt(0).toUpperCase() + priceType.slice(1).toLowerCase();
|
||||||
|
|
||||||
|
if (platform === 'mql') {
|
||||||
|
// MQ4 style
|
||||||
|
return `${price}[${shift}]`;
|
||||||
|
} else if (platform === 'mq5') {
|
||||||
|
// MQ5 style
|
||||||
|
return `i${price}(_Symbol, PERIOD_CURRENT, ${shift})`;
|
||||||
|
} else if (platform === 'pine') {
|
||||||
|
// Pine Script style
|
||||||
|
const priceLower = priceType.toLowerCase();
|
||||||
|
return shift === 0 ? priceLower : `${priceLower}[${shift}]`;
|
||||||
|
} else if (platform === 'csharp') {
|
||||||
|
// cTrader C# style
|
||||||
|
return `MarketSeries.${price}.Last(${shift})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${price}[${shift}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for use in other modules
|
||||||
|
window.RuleParser = RuleParser;
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
/**
|
||||||
|
* View detailed strategy information in a modal
|
||||||
|
*/
|
||||||
|
function viewStrategyDetails(index) {
|
||||||
|
console.log('🔍 viewStrategyDetails called with index:', index);
|
||||||
|
console.log('📊 appData.foundStrategies:', appData?.foundStrategies);
|
||||||
|
|
||||||
|
if (!appData || !appData.foundStrategies) {
|
||||||
|
console.error('❌ appData or foundStrategies is undefined!');
|
||||||
|
alert('Error: Strategy data not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0 || index >= appData.foundStrategies.length) {
|
||||||
|
console.error('❌ Invalid index:', index, 'Length:', appData.foundStrategies.length);
|
||||||
|
alert('Error: Invalid strategy index');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strategy = appData.foundStrategies[index];
|
||||||
|
if (!strategy || !strategy.metrics) {
|
||||||
|
console.error('❌ Invalid strategy at index:', index);
|
||||||
|
alert('Error: Strategy data is corrupted');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = strategy.metrics;
|
||||||
|
|
||||||
|
// Additional validation for required metrics
|
||||||
|
if (!m.profitFactor || !m.winRate || !m.totalTrades) {
|
||||||
|
console.error('❌ Missing required metrics:', m);
|
||||||
|
alert('Error: Strategy metrics are incomplete');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create modal if it doesn't exist
|
||||||
|
let modal = document.getElementById('strategy-modal');
|
||||||
|
if (!modal) {
|
||||||
|
modal = document.createElement('div');
|
||||||
|
modal.id = 'strategy-modal';
|
||||||
|
modal.className = 'modal';
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = `FxMath_${String(index + 1).padStart(3, '0')}_PF${(m.profitFactor || 0).toFixed(2).replace('.', '_')}_WR${Math.round(m.winRate || 0)}`;
|
||||||
|
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>${name}</h2>
|
||||||
|
<button class="modal-close" onclick="closeStrategyModal()">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="metrics-grid-detailed">
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Profit Factor</span>
|
||||||
|
<span class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">${m.profitFactor.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Win Rate</span>
|
||||||
|
<span class="metric-value">${m.winRate.toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Total Trades</span>
|
||||||
|
<span class="metric-value">${m.totalTrades}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">BUY Trades</span>
|
||||||
|
<span class="metric-value" style="color: #48bb78;">${m.buyTrades || 0}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">SELL Trades</span>
|
||||||
|
<span class="metric-value" style="color: #f56565;">${m.sellTrades || 0}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Winning Trades</span>
|
||||||
|
<span class="metric-value positive">${m.winningTrades || 0}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Losing Trades</span>
|
||||||
|
<span class="metric-value negative">${m.losingTrades || 0}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Max Drawdown</span>
|
||||||
|
<span class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Total Profit</span>
|
||||||
|
<span class="metric-value ${m.totalProfit >= 0 ? 'positive' : 'negative'}">$${m.totalProfit.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Avg Win</span>
|
||||||
|
<span class="metric-value positive">$${m.avgWin.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Avg Loss</span>
|
||||||
|
<span class="metric-value negative">$${Math.abs(m.avgLoss).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Largest Win</span>
|
||||||
|
<span class="metric-value positive">$${m.largestWin.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-detailed">
|
||||||
|
<span class="metric-label">Largest Loss</span>
|
||||||
|
<span class="metric-value negative">$${Math.abs(m.largestLoss).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
${m.bestHour && m.worstHour ? `
|
||||||
|
<div class="metric-detailed" style="grid-column: 1 / -1; margin-top: 10px; padding: 10px; background: rgba(66, 153, 225, 0.1); border-radius: 6px;">
|
||||||
|
<span class="metric-label">Best Hour:</span>
|
||||||
|
<span class="metric-value positive">${String(m.bestHour.hour).padStart(2, '0')}:00 ($${m.bestHour.profit.toFixed(2)})</span>
|
||||||
|
<span style="margin: 0 15px;">|</span>
|
||||||
|
<span class="metric-label">Worst Hour:</span>
|
||||||
|
<span class="metric-value negative">${String(m.worstHour.hour).padStart(2, '0')}:00 ($${m.worstHour.profit.toFixed(2)})</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-section">
|
||||||
|
<h3>Equity Curve</h3>
|
||||||
|
<canvas id="equity-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${m.hourlyStats ? `
|
||||||
|
<div class="chart-section">
|
||||||
|
<h3>Hourly Performance (24h)</h3>
|
||||||
|
<canvas id="hourly-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<div class="rules-section">
|
||||||
|
<h3>Strategy Rules</h3>
|
||||||
|
<p style="margin-bottom: 10px; color: #a0aec0;">BUY when ALL of the following conditions are true:</p>
|
||||||
|
<div class="rules-list">
|
||||||
|
${strategy.rules.map((rule, i) => {
|
||||||
|
let ruleText = '';
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||||
|
} else {
|
||||||
|
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||||
|
}
|
||||||
|
return `<div class="rule-item">${i + 1}. ${ruleText}</div>`;
|
||||||
|
}).join('')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="margin: 20px 0 10px 0; color: #a0aec0;">SELL when ALL of the following conditions are true:</p>
|
||||||
|
<div class="rules-list">
|
||||||
|
${strategy.rules.map((rule, i) => {
|
||||||
|
let ruleText = '';
|
||||||
|
// Invert the operator for SELL rules
|
||||||
|
const invertedOp = rule.operator === '>' ? '<=' :
|
||||||
|
rule.operator === '<' ? '>=' :
|
||||||
|
rule.operator === '>=' ? '<' :
|
||||||
|
rule.operator === '<=' ? '>' : rule.operator;
|
||||||
|
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${invertedOp} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||||
|
} else {
|
||||||
|
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${invertedOp} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||||
|
}
|
||||||
|
return `<div class="rule-item">${i + 1}. ${ruleText}</div>`;
|
||||||
|
}).join('')}
|
||||||
|
</div>
|
||||||
|
<div class="parameters-box">
|
||||||
|
<strong>Parameters:</strong> ATR Period: ${strategy.atrPeriod} | SL Multiplier: ${strategy.slMultiplier.toFixed(2)} | TP Multiplier: ${strategy.tpMultiplier.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="trades-section">
|
||||||
|
<h3>Trade Statement</h3>
|
||||||
|
<div class="trades-table-container">
|
||||||
|
<table class="trades-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Entry</th>
|
||||||
|
<th>Exit</th>
|
||||||
|
<th>Profit</th>
|
||||||
|
<th>Reason</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${(m.trades && m.trades.length > 0) ? m.trades.map((t, i) => `
|
||||||
|
<tr>
|
||||||
|
<td>${i + 1}</td>
|
||||||
|
<td><span class="badge">${t.type}</span></td>
|
||||||
|
<td>${t.entry.toFixed(5)}</td>
|
||||||
|
<td>${t.exit.toFixed(5)}</td>
|
||||||
|
<td class="${t.profit >= 0 ? 'positive' : 'negative'}">$${t.profit.toFixed(2)}</td>
|
||||||
|
<td>${t.reason.replace('_', ' ')}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('') : '<tr><td colspan="6" style="text-align: center; color: #a0aec0;">No trades recorded</td></tr>'}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer-actions">
|
||||||
|
<button class="btn-primary" onclick="downloadStrategy(${index}, 'mq4')">Download MQ4</button>
|
||||||
|
<button class="btn-primary" onclick="downloadStrategy(${index}, 'mq5')">Download MQ5</button>
|
||||||
|
<button class="btn-primary" onclick="downloadStrategy(${index}, 'ctrader')">cTrader</button>
|
||||||
|
<button class="btn-primary" onclick="downloadStrategy(${index}, 'pine')">Pine Script</button>
|
||||||
|
<button class="btn-success" onclick="downloadStrategy(${index}, 'report')">HTML Report</button>
|
||||||
|
<button class="btn-secondary" onclick="downloadStrategy(${index}, 'json')">JSON</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
|
||||||
|
// Draw equity chart
|
||||||
|
setTimeout(() => {
|
||||||
|
const ctx = document.getElementById('equity-chart').getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: m.equity.map((_, i) => i),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Account Balance',
|
||||||
|
data: m.equity,
|
||||||
|
borderColor: '#667eea',
|
||||||
|
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true,
|
||||||
|
pointRadius: 0
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: '#1a1f3a',
|
||||||
|
titleColor: '#fff',
|
||||||
|
bodyColor: '#a0aec0',
|
||||||
|
borderColor: '#2d3748',
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: false,
|
||||||
|
grid: { color: '#2d3748' },
|
||||||
|
ticks: { color: '#a0aec0' }
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: { color: '#2d3748' },
|
||||||
|
ticks: { color: '#a0aec0' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render hourly performance chart if data exists
|
||||||
|
if (m.hourlyStats) {
|
||||||
|
const hourlyCanvas = document.getElementById('hourly-chart');
|
||||||
|
if (hourlyCanvas) {
|
||||||
|
new Chart(hourlyCanvas, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: Array.from({ length: 24 }, (_, i) => `${String(i).padStart(2, '0')}:00`),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Profit/Loss',
|
||||||
|
data: m.hourlyStats.map(h => h.profit),
|
||||||
|
backgroundColor: m.hourlyStats.map(h => h.profit >= 0 ? 'rgba(72, 187, 120, 0.6)' : 'rgba(245, 101, 101, 0.6)'),
|
||||||
|
borderColor: m.hourlyStats.map(h => h.profit >= 0 ? 'rgba(72, 187, 120, 1)' : 'rgba(245, 101, 101, 1)'),
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function (context) {
|
||||||
|
const hour = context.dataIndex;
|
||||||
|
const stats = m.hourlyStats[hour];
|
||||||
|
return [
|
||||||
|
`Profit: $${stats.profit.toFixed(2)}`,
|
||||||
|
`Trades: ${stats.trades}`,
|
||||||
|
`Wins: ${stats.wins} | Losses: ${stats.losses}`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: { color: 'rgba(255, 255, 255, 0.1)' },
|
||||||
|
ticks: { color: '#a0aec0' }
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: { display: false },
|
||||||
|
ticks: { color: '#a0aec0', maxRotation: 45, minRotation: 45 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close strategy modal
|
||||||
|
*/
|
||||||
|
function closeStrategyModal() {
|
||||||
|
const modal = document.getElementById('strategy-modal');
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal when clicking outside
|
||||||
|
window.onclick = function (event) {
|
||||||
|
const modal = document.getElementById('strategy-modal');
|
||||||
|
if (event.target === modal) {
|
||||||
|
closeStrategyModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly expose functions to global scope for inline onclick handlers
|
||||||
|
window.viewStrategyDetails = viewStrategyDetails;
|
||||||
|
window.closeStrategyModal = closeStrategyModal;
|
||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
* Strategy Class - Represents a trading strategy with rules and parameters
|
||||||
|
*/
|
||||||
|
class Strategy {
|
||||||
|
constructor() {
|
||||||
|
this.rules = [];
|
||||||
|
this.atrPeriod = 20;
|
||||||
|
this.slMultiplier = 2.0;
|
||||||
|
this.tpMultiplier = 3.0;
|
||||||
|
this.closeAtOpposite = false; // Disable to allow independent BUY/SELL signals
|
||||||
|
this.fitness = 0;
|
||||||
|
this.metrics = {};
|
||||||
|
this.id = this.generateId();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateId() {
|
||||||
|
return 'strategy_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate random trading rules
|
||||||
|
*/
|
||||||
|
generateRandomRules(count, shiftRange) {
|
||||||
|
const minRules = count[0] || 3;
|
||||||
|
const maxRules = count[1] || 8;
|
||||||
|
const numRules = Math.floor(Math.random() * (maxRules - minRules + 1)) + minRules;
|
||||||
|
|
||||||
|
for (let i = 0; i < numRules; i++) {
|
||||||
|
this.rules.push(this.generateRule(shiftRange));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a single random rule
|
||||||
|
*/
|
||||||
|
generateRule(shiftRange) {
|
||||||
|
const priceTypes = ['open', 'high', 'low', 'close'];
|
||||||
|
const operators = ['>', '<', '>=', '<='];
|
||||||
|
|
||||||
|
const minShift = shiftRange[0] || 1;
|
||||||
|
const maxShift = shiftRange[1] || 10;
|
||||||
|
|
||||||
|
// 70% simple rules, 30% arithmetic rules
|
||||||
|
if (Math.random() < 0.7) {
|
||||||
|
// Simple rule: CLOSE[1] > OPEN[2]
|
||||||
|
return {
|
||||||
|
type: 'simple',
|
||||||
|
left: {
|
||||||
|
price: priceTypes[Math.floor(Math.random() * 4)],
|
||||||
|
shift: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift
|
||||||
|
},
|
||||||
|
operator: operators[Math.floor(Math.random() * 4)],
|
||||||
|
right: {
|
||||||
|
price: priceTypes[Math.floor(Math.random() * 4)],
|
||||||
|
shift: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Arithmetic rule: (HIGH[1] + LOW[1]) > (CLOSE[2] * 1.01)
|
||||||
|
const arithmeticOps = ['+', '-', '*'];
|
||||||
|
const multipliers = [0.99, 1.01, 1.02, 0.98];
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'arithmetic',
|
||||||
|
left: {
|
||||||
|
price1: priceTypes[Math.floor(Math.random() * 4)],
|
||||||
|
shift1: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift,
|
||||||
|
op: arithmeticOps[Math.floor(Math.random() * 3)],
|
||||||
|
price2: priceTypes[Math.floor(Math.random() * 4)],
|
||||||
|
shift2: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift
|
||||||
|
},
|
||||||
|
operator: operators[Math.floor(Math.random() * 4)],
|
||||||
|
right: {
|
||||||
|
price: priceTypes[Math.floor(Math.random() * 4)],
|
||||||
|
shift: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift,
|
||||||
|
multiplier: multipliers[Math.floor(Math.random() * 4)]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Randomize ATR and SL/TP parameters
|
||||||
|
*/
|
||||||
|
randomizeParameters() {
|
||||||
|
this.atrPeriod = Math.floor(Math.random() * 31) + 10; // 10-40
|
||||||
|
this.slMultiplier = (Math.random() * 3) + 1; // 1.0-4.0
|
||||||
|
this.tpMultiplier = this.slMultiplier + (Math.random() * 5) + 0.5; // SL + 0.5 to 5.5
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a deep copy of the strategy
|
||||||
|
*/
|
||||||
|
copy() {
|
||||||
|
const newStrategy = new Strategy();
|
||||||
|
newStrategy.rules = JSON.parse(JSON.stringify(this.rules));
|
||||||
|
newStrategy.atrPeriod = this.atrPeriod;
|
||||||
|
newStrategy.slMultiplier = this.slMultiplier;
|
||||||
|
newStrategy.tpMultiplier = this.tpMultiplier;
|
||||||
|
newStrategy.closeAtOpposite = this.closeAtOpposite;
|
||||||
|
newStrategy.metrics = this.metrics ? JSON.parse(JSON.stringify(this.metrics)) : {};
|
||||||
|
newStrategy.fitness = this.fitness || 0;
|
||||||
|
return newStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert rules to human-readable format
|
||||||
|
*/
|
||||||
|
getRulesText() {
|
||||||
|
return this.rules.map((rule, index) => {
|
||||||
|
if (rule.type === 'simple') {
|
||||||
|
return `${index + 1}. ${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||||
|
} else {
|
||||||
|
return `${index + 1}. (${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] * ${rule.right.multiplier})`;
|
||||||
|
}
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export to JSON
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
rules: this.rules,
|
||||||
|
parameters: {
|
||||||
|
atr_period: this.atrPeriod,
|
||||||
|
sl_multiplier: this.slMultiplier,
|
||||||
|
tp_multiplier: this.tpMultiplier,
|
||||||
|
close_at_opposite: this.closeAtOpposite
|
||||||
|
},
|
||||||
|
performance: this.metrics,
|
||||||
|
fitness: this.fitness
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import from JSON
|
||||||
|
*/
|
||||||
|
static fromJSON(json) {
|
||||||
|
const strategy = new Strategy();
|
||||||
|
strategy.id = json.id;
|
||||||
|
strategy.rules = json.rules;
|
||||||
|
strategy.atrPeriod = json.parameters.atr_period;
|
||||||
|
strategy.slMultiplier = json.parameters.sl_multiplier;
|
||||||
|
strategy.tpMultiplier = json.parameters.tp_multiplier;
|
||||||
|
strategy.closeAtOpposite = json.parameters.close_at_opposite || false;
|
||||||
|
strategy.metrics = json.performance || {};
|
||||||
|
strategy.fitness = json.fitness || 0;
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// UI Controller - Simple helper functions for UI updates
|
||||||
|
// (Most UI logic is in main.js)
|
||||||
+240
@@ -0,0 +1,240 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FxMathQuant - License Activation</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 50px 40px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 450px;
|
||||||
|
animation: slideIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo h1 {
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo p {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-activate {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-activate:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-activate:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-activate:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.show {
|
||||||
|
display: block;
|
||||||
|
animation: fadeIn 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background: #fee;
|
||||||
|
color: #c33;
|
||||||
|
border: 1px solid #fcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: #efe;
|
||||||
|
color: #3c3;
|
||||||
|
border: 1px solid #cfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background: #eef;
|
||||||
|
color: #33c;
|
||||||
|
border: 1px solid #ccf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: white;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.license-format {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="logo">
|
||||||
|
<h1>🔐 FxMathQuant</h1>
|
||||||
|
<p>Strategy Finder & Optimizer</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="alertBox" class="alert"></div>
|
||||||
|
|
||||||
|
<form id="licenseForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="licenseKey">License Key</label>
|
||||||
|
<input type="text" id="licenseKey" name="licenseKey"
|
||||||
|
placeholder="XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX" required autocomplete="off" maxlength="39">
|
||||||
|
<div class="license-format">Format: 32 characters with dashes</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn-activate" id="activateBtn">
|
||||||
|
Activate License
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="help-text">
|
||||||
|
Need a license? <a href="mailto:fxmathsolution@gmail.com">Contact Support</a>
|
||||||
|
</div>
|
||||||
|
<div class="help-text" style="margin-top: 10px; font-size: 12px;">
|
||||||
|
📧 <a href="mailto:fxmathsolution@gmail.com">fxmathsolution@gmail.com</a> |
|
||||||
|
💬 <a href="https://t.me/FxMath" target="_blank">Telegram</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/login.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
+419
@@ -0,0 +1,419 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FxMathQuant User Manual</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 3px solid #667eea;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #764ba2;
|
||||||
|
font-size: 1.8em;
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-left: 4px solid #764ba2;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #555;
|
||||||
|
font-size: 1.3em;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin-left: 30px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #2d2d2d;
|
||||||
|
color: #f8f8f2;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background: #e3f2fd;
|
||||||
|
border-left: 4px solid #2196f3;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background: #fff3e0;
|
||||||
|
border-left: 4px solid #ff9800;
|
||||||
|
color: #e65100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: #e8f5e9;
|
||||||
|
border-left: 4px solid #4caf50;
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: #5568d3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc {
|
||||||
|
background: #f9f9f9;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-box {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-box a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>📚 FxMathQuant User Manual</h1>
|
||||||
|
<p><strong>AI-Powered Trading Strategy Generator</strong></p>
|
||||||
|
|
||||||
|
<div class="toc">
|
||||||
|
<h3>📋 Table of Contents</h3>
|
||||||
|
<ol>
|
||||||
|
<li><a href="#getting-started">Getting Started</a></li>
|
||||||
|
<li><a href="#license">License Activation</a></li>
|
||||||
|
<li><a href="#export">Exporting Data from MT4/MT5</a></li>
|
||||||
|
<li><a href="#upload">Uploading Data</a></li>
|
||||||
|
<li><a href="#config">Configuring Strategy Generation</a></li>
|
||||||
|
<li><a href="#generate">Generating Strategies</a></li>
|
||||||
|
<li><a href="#results">Viewing Results</a></li>
|
||||||
|
<li><a href="#download">Downloading Strategies</a></li>
|
||||||
|
<li><a href="#troubleshoot">Troubleshooting</a></li>
|
||||||
|
<li><a href="#support">Contact Support</a></li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="getting-started">🚀 Getting Started</h2>
|
||||||
|
<h3>What is FxMathQuant?</h3>
|
||||||
|
<p>FxMathQuant is an AI-powered trading strategy generator that uses genetic algorithms to discover profitable
|
||||||
|
trading strategies from your historical price data.</p>
|
||||||
|
|
||||||
|
<h3>System Requirements</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Web Browser</strong>: Chrome, Firefox, Safari, or Edge (latest version)</li>
|
||||||
|
<li><strong>MetaTrader</strong>: MT4 or MT5 (for data export)</li>
|
||||||
|
<li><strong>License Key</strong>: Required for access</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="license">🔑 License Activation</h2>
|
||||||
|
<h3>Step 1: Obtain License Key</h3>
|
||||||
|
<p>Purchase a license from:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Email</strong>: <a href="mailto:fxmathsolution@gmail.com">fxmathsolution@gmail.com</a></li>
|
||||||
|
<li><strong>Telegram</strong>: <a href="https://t.me/FxMath" target="_blank">https://t.me/FxMath</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Step 2: Activate License</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Open the application in your web browser</li>
|
||||||
|
<li>Enter your license key in the format: <code>XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX</code></li>
|
||||||
|
<li>Click "Activate License"</li>
|
||||||
|
<li>Wait for validation (requires internet connection)</li>
|
||||||
|
<li>You'll be redirected to the main application</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<strong>Session Duration:</strong> Your session remains active until you logout or clear browser data. There
|
||||||
|
is no automatic logout.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="export">📊 Exporting Data from MT4/MT5</h2>
|
||||||
|
<h3>Download Data Provider EA</h3>
|
||||||
|
<p>Download the EA from the application:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>MT4</strong>: <code>FxMathQuant_DataExporter_MT4.ex4</code></li>
|
||||||
|
<li><strong>MT5</strong>: <code>FxMathQuant_DataExporter_MT5.ex5</code></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Export Historical Data</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Open a chart (e.g., XAUUSD H1)</li>
|
||||||
|
<li>Drag the EA onto the chart</li>
|
||||||
|
<li>Configure settings:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Bars to Export</strong>: 10,000 (recommended)</li>
|
||||||
|
<li><strong>Remove Suffix</strong>: true</li>
|
||||||
|
<li><strong>Export Folder</strong>: FxMathQuant</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Click "Export to CSV" button</li>
|
||||||
|
<li>Wait for completion message</li>
|
||||||
|
<li>Find your file: <code>MQL4/Files/FxMathQuant/XAUUSD_H1.csv</code></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Recommended Data</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Timeframe</th>
|
||||||
|
<th>Bars</th>
|
||||||
|
<th>Use Case</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>M15</td>
|
||||||
|
<td>10,000</td>
|
||||||
|
<td>Scalping strategies</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>H1</td>
|
||||||
|
<td>10,000</td>
|
||||||
|
<td>Intraday trading</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>H4</td>
|
||||||
|
<td>5,000</td>
|
||||||
|
<td>Swing trading</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>D1</td>
|
||||||
|
<td>3,000</td>
|
||||||
|
<td>Position trading</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="upload">📤 Uploading Data</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Click "Browse" or drag-and-drop your CSV file</li>
|
||||||
|
<li>Select the file exported from MT4/MT5</li>
|
||||||
|
<li>Wait for parsing (10,000 bars ≈ 2-3 seconds)</li>
|
||||||
|
<li>Confirmation: "Data loaded successfully: X bars"</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2 id="config">⚙️ Configuring Strategy Generation</h2>
|
||||||
|
<h3>Genetic Algorithm Settings</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Parameter</th>
|
||||||
|
<th>Default</th>
|
||||||
|
<th>Range</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Population Size</td>
|
||||||
|
<td>100</td>
|
||||||
|
<td>50-500</td>
|
||||||
|
<td>Number of strategies per generation</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Generations</td>
|
||||||
|
<td>50</td>
|
||||||
|
<td>10-200</td>
|
||||||
|
<td>Number of evolution cycles</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Mutation Rate</td>
|
||||||
|
<td>0.1</td>
|
||||||
|
<td>0.01-0.5</td>
|
||||||
|
<td>Probability of random changes</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Crossover Rate</td>
|
||||||
|
<td>0.7</td>
|
||||||
|
<td>0.3-0.9</td>
|
||||||
|
<td>Probability of combining strategies</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="generate">🎯 Generating Strategies</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Review your settings</li>
|
||||||
|
<li>Click "Start Generation"</li>
|
||||||
|
<li>Monitor progress (current generation, strategies found, best fitness)</li>
|
||||||
|
<li>Wait for completion (30 seconds to 5 minutes)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2 id="results">📈 Viewing Results</h2>
|
||||||
|
<p>Each strategy shows:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Profit Factor</strong>: Ratio of gross profit to gross loss</li>
|
||||||
|
<li><strong>Win Rate</strong>: Percentage of winning trades</li>
|
||||||
|
<li><strong>Total Trades</strong>: Number of trades executed</li>
|
||||||
|
<li><strong>Net Profit</strong>: Total profit in currency</li>
|
||||||
|
<li><strong>Max Drawdown</strong>: Largest equity drop</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Click "View Details" to see:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Performance Metrics</li>
|
||||||
|
<li>BUY/SELL Rules</li>
|
||||||
|
<li>Equity Curve Chart</li>
|
||||||
|
<li>Hourly Performance</li>
|
||||||
|
<li>Complete Trade Statement</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="download">💾 Downloading Strategies</h2>
|
||||||
|
<h3>Available Formats</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>MetaTrader 4 (.mq4)</strong> - For MT4 platform</li>
|
||||||
|
<li><strong>MetaTrader 5 (.mq5)</strong> - For MT5 platform</li>
|
||||||
|
<li><strong>cTrader (.cs)</strong> - For cTrader platform</li>
|
||||||
|
<li><strong>TradingView (Pine Script)</strong> - For TradingView</li>
|
||||||
|
<li><strong>HTML Report</strong> - Comprehensive performance report</li>
|
||||||
|
<li><strong>JSON Data</strong> - Raw strategy data</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="troubleshoot">🐛 Troubleshooting</h2>
|
||||||
|
|
||||||
|
<h3>CSV Upload Issues</h3>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Error:</strong> "CSV must contain: time, open, high, low, close columns"<br>
|
||||||
|
<strong>Solution:</strong> Ensure CSV has required columns. Column names can be lowercase or capitalized.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>License Issues</h3>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Error:</strong> "Invalid license key"<br>
|
||||||
|
<strong>Solution:</strong> Check key format (32 characters with dashes). Copy-paste from email.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Generation Issues</h3>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>No strategies found</strong><br>
|
||||||
|
<strong>Solution:</strong> Relax criteria (lower Min Profit Factor, Win Rate). Increase population size and
|
||||||
|
generations.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="support">📞 Contact Support</h2>
|
||||||
|
|
||||||
|
<div class="contact-box">
|
||||||
|
<h3>Get Help</h3>
|
||||||
|
<p><strong>📧 Email:</strong> <a href="mailto:fxmathsolution@gmail.com">fxmathsolution@gmail.com</a></p>
|
||||||
|
<p><strong>💬 Telegram:</strong> <a href="https://t.me/FxMath" target="_blank">https://t.me/FxMath</a></p>
|
||||||
|
<p style="margin-top: 15px; font-size: 0.9em;">Response time: 24-48 hours via email, faster on Telegram</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info" style="margin-top: 30px;">
|
||||||
|
<strong>⚠️ Disclaimer:</strong> Past performance does not guarantee future results. Always test strategies
|
||||||
|
on demo account first. Use proper risk management.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="text-align: center; margin-top: 30px; color: #999; font-size: 0.9em;">
|
||||||
|
© 2025 FxMath Solution. All rights reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user