Add files via upload

This commit is contained in:
FxPouya
2025-12-24 13:07:51 +03:30
committed by GitHub
commit ad50cc66cb
28 changed files with 22747 additions and 0 deletions
+216
View File
@@ -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!
+264
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+465
View File
@@ -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
View File
@@ -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>&copy; 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>
+464
View File
@@ -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;
}
}
+314
View File
@@ -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
View File
@@ -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));
}
}
+493
View File
@@ -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;
+162
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+328
View File
@@ -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;
+152
View File
@@ -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
`;
}
}
+341
View File
@@ -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;
+202
View File
@@ -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;
}
`;
}
}
+75
View File
@@ -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;
+272
View File
@@ -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>&copy; 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>`;
}
}
+59
View File
@@ -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;
+329
View File
@@ -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()">&times;</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
View File
@@ -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;
}
}
+2
View File
@@ -0,0 +1,2 @@
// UI Controller - Simple helper functions for UI updates
// (Most UI logic is in main.js)
+240
View File
@@ -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
View File
@@ -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>