feat(ui): strategy config form and backend endpoint
This commit is contained in:
28
app.py
28
app.py
@@ -5,7 +5,7 @@ Provides mock metrics for PnL, drawdown, and win rate.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import Flask, render_template, jsonify, request
|
from flask import Flask, render_template, jsonify, request
|
||||||
import random
|
import os
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from data.connectors import create_moomoo_client, Interval
|
from data.connectors import create_moomoo_client, Interval
|
||||||
|
|
||||||
@@ -14,6 +14,8 @@ app = Flask(__name__)
|
|||||||
# Initialize MoomooClient in mock mode (using our LLM scenario generator)
|
# Initialize MoomooClient in mock mode (using our LLM scenario generator)
|
||||||
client = create_moomoo_client(mock_mode=True)
|
client = create_moomoo_client(mock_mode=True)
|
||||||
|
|
||||||
|
STRATEGY_CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'data', 'strategy_configs.json')
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def dashboard():
|
def dashboard():
|
||||||
"""Serve the dashboard HTML page."""
|
"""Serve the dashboard HTML page."""
|
||||||
@@ -91,6 +93,30 @@ def market_data(symbol):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/strategy-config', methods=['POST'])
|
||||||
|
def save_strategy_config():
|
||||||
|
"""Save strategy configuration to a JSON file (simple local persistence)."""
|
||||||
|
try:
|
||||||
|
data = request.get_json(force=True) or {}
|
||||||
|
os.makedirs(os.path.join(os.path.dirname(__file__), 'data'), exist_ok=True)
|
||||||
|
configs = []
|
||||||
|
if os.path.exists(STRATEGY_CONFIG_PATH):
|
||||||
|
import json as _json
|
||||||
|
with open(STRATEGY_CONFIG_PATH, 'r') as f:
|
||||||
|
try:
|
||||||
|
configs = _json.load(f)
|
||||||
|
except Exception:
|
||||||
|
configs = []
|
||||||
|
# Append with timestamp
|
||||||
|
data['savedAt'] = datetime.now().isoformat()
|
||||||
|
configs.append(data)
|
||||||
|
import json
|
||||||
|
with open(STRATEGY_CONFIG_PATH, 'w') as f:
|
||||||
|
json.dump(configs, f, indent=2)
|
||||||
|
return jsonify({'status': 'ok', 'count': len(configs)})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'status': 'error', 'error': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/health')
|
@app.route('/health')
|
||||||
def health():
|
def health():
|
||||||
"""Health check endpoint."""
|
"""Health check endpoint."""
|
||||||
|
|||||||
@@ -121,13 +121,50 @@ function setupEventListeners() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Form submissions
|
// Strategy config form submission (real backend call)
|
||||||
|
const strategyForm = document.getElementById('strategy-config-form');
|
||||||
|
if (strategyForm) {
|
||||||
|
strategyForm.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const payload = {
|
||||||
|
name: document.getElementById('strategy-name').value,
|
||||||
|
type: document.getElementById('strategy-type').value,
|
||||||
|
initialCapital: Number(document.getElementById('initial-capital').value),
|
||||||
|
entryLookback: Number(document.getElementById('entry-lookback').value),
|
||||||
|
exitLookback: Number(document.getElementById('exit-lookback').value),
|
||||||
|
riskPerTrade: Number(document.getElementById('risk-per-trade').value),
|
||||||
|
maxPositions: Number(document.getElementById('max-positions').value),
|
||||||
|
notes: document.getElementById('strategy-notes').value,
|
||||||
|
};
|
||||||
|
fetch('/api/strategy-config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'ok') {
|
||||||
|
showToast('Strategy configuration saved', 'success');
|
||||||
|
} else {
|
||||||
|
showToast('Failed to save configuration', 'danger');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error saving strategy config', err);
|
||||||
|
showToast('Error saving configuration', 'danger');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic form submissions (fallback)
|
||||||
const forms = document.querySelectorAll('form');
|
const forms = document.querySelectorAll('form');
|
||||||
forms.forEach(form => {
|
forms.forEach(form => {
|
||||||
form.addEventListener('submit', function(e) {
|
if (form.id !== 'strategy-config-form') {
|
||||||
e.preventDefault();
|
form.addEventListener('submit', function(e) {
|
||||||
showToast('Settings saved successfully', 'success');
|
e.preventDefault();
|
||||||
});
|
showToast('Settings saved successfully', 'success');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle buttons
|
// Toggle buttons
|
||||||
|
|||||||
@@ -560,29 +560,53 @@
|
|||||||
<h6 class="mb-0">Create New Strategy</h6>
|
<h6 class="mb-0">Create New Strategy</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form>
|
<form id="strategy-config-form">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Strategy Name</label>
|
<label class="form-label">Strategy Name</label>
|
||||||
<input type="text" class="form-control bg-dark border-secondary-dark text-light" placeholder="e.g., Momentum Breakout">
|
<input id="strategy-name" type="text" class="form-control bg-dark border-secondary-dark text-light" placeholder="e.g., Momentum Breakout" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Strategy Type</label>
|
<label class="form-label">Strategy Type</label>
|
||||||
<select class="form-select bg-dark border-secondary-dark text-light">
|
<select id="strategy-type" class="form-select bg-dark border-secondary-dark text-light">
|
||||||
<option>Trend Following</option>
|
<option value="trend">Trend Following</option>
|
||||||
<option>Mean Reversion</option>
|
<option value="mean-reversion">Mean Reversion</option>
|
||||||
<option>Arbitrage</option>
|
<option value="arbitrage">Arbitrage</option>
|
||||||
<option>Machine Learning</option>
|
<option value="ml">Machine Learning</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Initial Capital</label>
|
<label class="form-label">Initial Capital</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text bg-dark border-secondary-dark text-light">$</span>
|
<span class="input-group-text bg-dark border-secondary-dark text-light">$</span>
|
||||||
<input type="number" class="form-control bg-dark border-secondary-dark text-light" value="100000">
|
<input id="initial-capital" type="number" class="form-control bg-dark border-secondary-dark text-light" value="100000" min="1000" step="1000" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6 mb-3 mb-md-0">
|
||||||
|
<label class="form-label">Entry Lookback (bars)</label>
|
||||||
|
<input id="entry-lookback" type="number" class="form-control bg-dark border-secondary-dark text-light" value="20" min="1">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Exit Lookback (bars)</label>
|
||||||
|
<input id="exit-lookback" type="number" class="form-control bg-dark border-secondary-dark text-light" value="50" min="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6 mb-3 mb-md-0">
|
||||||
|
<label class="form-label">Risk per Trade (%)</label>
|
||||||
|
<input id="risk-per-trade" type="number" class="form-control bg-dark border-secondary-dark text-light" value="1" min="0.1" max="5" step="0.1">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Max Positions</label>
|
||||||
|
<input id="max-positions" type="number" class="form-control bg-dark border-secondary-dark text-light" value="5" min="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Notes</label>
|
||||||
|
<textarea id="strategy-notes" class="form-control bg-dark border-secondary-dark text-light" rows="2" placeholder="Optional notes about this configuration"></textarea>
|
||||||
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-primary">Create & Backtest</button>
|
<button type="submit" class="btn btn-primary">Save Configuration</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user