mirror of
https://github.com/BrentNeale1/fx-quant.git
synced 2026-08-06 07:07:45 +00:00
b1f3a919bf
New strategy (pivot_retest_engulfing) that enters long/short trades at pivot level retests confirmed by SMA 50 alignment and engulfing candle patterns. Uses ATR-based stop loss with two take-profit levels — at TP1 half the position closes and SL moves to breakeven, at TP2 the rest closes. - data_engine: add detect_engulfing() for bullish/bearish pattern detection - backtester: add generate_signals_pivot_retest(), run_backtest_dual_tp(), update signal dispatcher and metrics for dual-TP trade format - order_executor: support signal=-1 (SHORT), attach SL/TP levels - config: switch to pivot_retest_engulfing with default params - chart_trades: new mplfinance script to visualize entries on candlesticks - README: rewrite with full setup guide, project structure, strategy docs - requirements.txt: make portable (remove conda file:// paths), add mplfinance - .env.example: add template for secrets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
133 lines
3.8 KiB
HTML
133 lines
3.8 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Chart - fx-quant{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row mb-3">
|
|
<div class="col">
|
|
<h4>Price Chart with Pivot Points</h4>
|
|
</div>
|
|
<div class="col-auto">
|
|
<form method="get" action="/chart" class="d-flex gap-2">
|
|
<select name="instrument" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
{% for inst in instruments %}
|
|
<option value="{{ inst }}" {% if inst == instrument %}selected{% endif %}>{{ inst }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<select name="granularity" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
{% for g in granularities %}
|
|
<option value="{{ g }}" {% if g == granularity %}selected{% endif %}>{{ g }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{% if error_msg %}
|
|
<div class="alert alert-warning">{{ error_msg }}</div>
|
|
{% endif %}
|
|
|
|
<div class="card">
|
|
<div class="card-body p-2">
|
|
<div id="chart" style="width:100%; height:600px;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>
|
|
<script>
|
|
(function() {
|
|
var ohlc = {{ ohlc_json | safe }};
|
|
var pivots = {{ pivot_json | safe }};
|
|
var smas = {{ sma_json | safe }};
|
|
|
|
if (!ohlc || ohlc.length === 0) {
|
|
document.getElementById('chart').innerHTML =
|
|
'<p class="text-muted text-center mt-5">No candle data available for {{ instrument }} / {{ granularity }}.</p>';
|
|
return;
|
|
}
|
|
|
|
var times = ohlc.map(function(r) { return r.time; });
|
|
var opens = ohlc.map(function(r) { return r.open; });
|
|
var highs = ohlc.map(function(r) { return r.high; });
|
|
var lows = ohlc.map(function(r) { return r.low; });
|
|
var closes = ohlc.map(function(r) { return r.close; });
|
|
|
|
var traces = [{
|
|
x: times, open: opens, high: highs, low: lows, close: closes,
|
|
type: 'candlestick',
|
|
increasing: {line: {color: '#198754'}},
|
|
decreasing: {line: {color: '#dc3545'}},
|
|
name: '{{ instrument }}'
|
|
}];
|
|
|
|
// SMA overlay lines
|
|
var smaColors = {
|
|
'sma_3': '#ff9800',
|
|
'sma_20': '#9c27b0',
|
|
'sma_21': '#673ab7',
|
|
'sma_50': '#00bcd4',
|
|
'sma_100': '#e91e63'
|
|
};
|
|
Object.keys(smas).forEach(function(key) {
|
|
traces.push({
|
|
x: times,
|
|
y: smas[key],
|
|
type: 'scatter',
|
|
mode: 'lines',
|
|
name: key.toUpperCase().replace('_', ' '),
|
|
line: {color: smaColors[key] || '#999', width: 1.5},
|
|
connectgaps: false
|
|
});
|
|
});
|
|
|
|
// Pivot level lines
|
|
var levelDefs = [
|
|
{key: 'r3', color: '#b71c1c', label: 'R3'},
|
|
{key: 'r2', color: '#e53935', label: 'R2'},
|
|
{key: 'r1', color: '#ef9a9a', label: 'R1'},
|
|
{key: 'pivot', color: '#1565c0', label: 'Pivot'},
|
|
{key: 's1', color: '#a5d6a7', label: 'S1'},
|
|
{key: 's2', color: '#43a047', label: 'S2'},
|
|
{key: 's3', color: '#1b5e20', label: 'S3'}
|
|
];
|
|
|
|
var shapes = [];
|
|
var annotations = [];
|
|
|
|
levelDefs.forEach(function(lv) {
|
|
var val = pivots[lv.key];
|
|
if (val == null) return;
|
|
shapes.push({
|
|
type: 'line',
|
|
x0: times[0], x1: times[times.length - 1],
|
|
y0: val, y1: val,
|
|
line: {color: lv.color, width: 1.5, dash: lv.key === 'pivot' ? 'solid' : 'dash'}
|
|
});
|
|
annotations.push({
|
|
x: times[times.length - 1], y: val,
|
|
xanchor: 'left', yanchor: 'middle',
|
|
text: ' ' + lv.label + ' ' + val.toFixed(5),
|
|
showarrow: false,
|
|
font: {size: 11, color: lv.color}
|
|
});
|
|
});
|
|
|
|
var layout = {
|
|
title: '{{ instrument }} {{ granularity }}',
|
|
xaxis: {
|
|
type: 'category',
|
|
rangeslider: {visible: false},
|
|
nticks: 12
|
|
},
|
|
yaxis: {title: 'Price'},
|
|
shapes: shapes,
|
|
annotations: annotations,
|
|
margin: {l: 60, r: 100, t: 40, b: 40},
|
|
paper_bgcolor: '#fff',
|
|
plot_bgcolor: '#f8f9fa'
|
|
};
|
|
|
|
Plotly.newPlot('chart', traces, layout, {responsive: true});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|