trtr

Trading simulator and techanalysis gym
git clone https://git.ea.contact/trtr
Log | Files | Refs

commit e30f39b127c1f0aca4ddfc842c2e86e6cc73cca5
parent eefb14484d957f6b48c6ca6cb9e831c292b23af7
Author: ea <ea@ea.contact>
Date:   Sun, 24 May 2026 20:05:37 +0000

Techa: add play till close

Diffstat:
Mfrontend/index.html | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+), 0 deletions(-)

diff --git a/frontend/index.html b/frontend/index.html @@ -470,6 +470,9 @@ } .gym-btn:hover { background: #2a2a4a; color: #a0a0c0; } .gym-btn.danger:hover { background: #3a1a1a; color: #ef5350; border-color: #5a2020; } + .gym-btn.go { border-color: #1a4a3a; color: #26a69a; } + .gym-btn.go:hover:not(:disabled) { background: #0f2e26; color: #4fd4c0; border-color: #267a6a; } + .gym-btn:disabled, .gym-btn.go:disabled { opacity: 0.35; cursor: not-allowed; } #text-input-overlay { position: fixed; @@ -633,6 +636,7 @@ </span> </div> <div id="prediction-info">No prediction set</div> + <button class="gym-btn go" id="ff-to-position-btn" disabled>&#9654;&#9654; Play to close</button> <button class="gym-btn danger" id="clear-prediction-btn">Cancel prediction</button> </div> @@ -1114,6 +1118,7 @@ // ── Gym Mode ────────────────────────────────────────────────────────────── let gymMode = false; + let gymFastForwarding = false; let gymActiveTool = null; // null = no tool, chart pans/zooms freely const gymDrawings = []; let gymPrediction = null; // { logTarget, entryPrice, direction: 'long'|'short' } @@ -1237,9 +1242,11 @@ function updatePredictionInfo() { const el = document.getElementById('prediction-info'); + const ffBtn = document.getElementById('ff-to-position-btn'); if (!gymPrediction) { el.textContent = 'No prediction set'; el.classList.remove('has-pred'); + if (ffBtn) ffBtn.disabled = true; return; } const { logTarget, entryPrice, direction } = gymPrediction; @@ -1251,6 +1258,7 @@ `<span style="color:${col};font-weight:700">${dir}</span><br>` + `Entry:&nbsp; $${fmt(entryPrice)}<br>` + `Closes: ${barsLeft === 0 ? 'next bar' : '+' + barsLeft + ' bars'}`; + if (ffBtn) ffBtn.disabled = gymFastForwarding || barsLeft === 0; } function updateGymScore() { @@ -1506,6 +1514,51 @@ updatePredictionInfo(); }); + async function gymFastForwardToClose() { + if (!gymPrediction || gymFastForwarding) return; + gymFastForwarding = true; + document.getElementById('ff-to-position-btn').disabled = true; + document.getElementById('next-btn').disabled = true; + + while (gymPrediction && gymLastLogical < gymPrediction.logTarget) { + const res = await fetch('/api/next', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId }), + }); + + if (res.status === 409) { + document.getElementById('next-btn').textContent = 'End of data'; + document.getElementById('next-btn').disabled = true; + break; + } + + const data = await res.json(); + const c = data.candle; + candleSeries.update({ time: c.ts, open: c.open, high: c.high, low: c.low, close: c.close }); + chart.timeScale().scrollToRealTime(); + gymLastLogical++; + currentPrice = c.close; + updatePortfolio(data.usd, data.btc, data.pnl, c.close); + syncCurrentBreakEven(); + + if (gymPrediction && gymLastLogical >= gymPrediction.logTarget) { + resolveGymPrediction(c.close); + break; + } else { + updatePredictionInfo(); + } + + await new Promise(r => setTimeout(r, 120)); + } + + gymFastForwarding = false; + document.getElementById('next-btn').disabled = false; + updatePredictionInfo(); + } + + document.getElementById('ff-to-position-btn').addEventListener('click', gymFastForwardToClose); + document.getElementById('clear-drawings-btn').addEventListener('click', () => { gymDrawings.length = 0; });