Demolifted commited on
Commit
e3f3bc4
·
1 Parent(s): 4f3b44c

Add interactive demo trade chart with draggable lines

Browse files
Files changed (1) hide show
  1. crypto_ui/terminal.html +122 -3
crypto_ui/terminal.html CHANGED
@@ -6,6 +6,7 @@
6
  <title>Kronos — Demo Terminal</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
 
9
  <style>
10
  /* ===== design tokens (matches tracker.html) ===== */
11
  :root {
@@ -270,6 +271,7 @@
270
  <div class="main">
271
  <!-- LEFT: Positions -->
272
  <div class="col-left">
 
273
  <div class="pos-grid" id="posGrid"></div>
274
  <div id="empty">
275
  <div class="big">No demo positions</div>
@@ -281,7 +283,10 @@
281
  <div class="col-right">
282
  <!-- New Trade Form -->
283
  <div class="panel-card">
284
- <div class="panel-title">Execute Trade</div>
 
 
 
285
  <form id="tradeForm" autocomplete="off" onsubmit="return executeTrade(event)">
286
  <div class="form-group">
287
  <label>Symbol</label>
@@ -390,9 +395,9 @@ function setDir(dir) {
390
  calcRR();
391
  }
392
 
393
- /* ===== live R:R calculation ===== */
394
  ['fEntry','fStop','fTP1'].forEach(id => {
395
- document.getElementById(id).addEventListener('input', calcRR);
396
  });
397
 
398
  function calcRR() {
@@ -683,6 +688,120 @@ function renderLog() {
683
  list.scrollTop = list.scrollHeight;
684
  }
685
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
  /* ===== init ===== */
687
  (async function init() {
688
  await poll();
 
6
  <title>Kronos — Demo Terminal</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
+ <script src="https://unpkg.com/lightweight-charts@4.2.3/dist/lightweight-charts.standalone.production.js"></script>
10
  <style>
11
  /* ===== design tokens (matches tracker.html) ===== */
12
  :root {
 
271
  <div class="main">
272
  <!-- LEFT: Positions -->
273
  <div class="col-left">
274
+ <div id="chartWrap" style="height:350px;width:100%;margin-bottom:18px;border:1px solid var(--border);border-radius:14px;overflow:hidden;background:var(--panel);display:none;position:relative"></div>
275
  <div class="pos-grid" id="posGrid"></div>
276
  <div id="empty">
277
  <div class="big">No demo positions</div>
 
283
  <div class="col-right">
284
  <!-- New Trade Form -->
285
  <div class="panel-card">
286
+ <div class="panel-title" style="display:flex;justify-content:space-between;align-items:center">
287
+ Execute Trade
288
+ <button type="button" class="btn-dim" style="margin:0;border-color:var(--accent);color:var(--text);background:rgba(139,92,246,.1)" onclick="loadDemoTrade()">✨ Load Demo Trade</button>
289
+ </div>
290
  <form id="tradeForm" autocomplete="off" onsubmit="return executeTrade(event)">
291
  <div class="form-group">
292
  <label>Symbol</label>
 
395
  calcRR();
396
  }
397
 
398
+ /* ===== live R:R calculation & chart sync ===== */
399
  ['fEntry','fStop','fTP1'].forEach(id => {
400
+ document.getElementById(id).addEventListener('input', () => { calcRR(); syncChartLines(); });
401
  });
402
 
403
  function calcRR() {
 
688
  list.scrollTop = list.scrollHeight;
689
  }
690
 
691
+ /* ===== Interactive Chart & Demo Setup ===== */
692
+ let chart, candleSeries;
693
+ let entryLine, stopLine, tpLine;
694
+ let draggingLine = null; // 'entry', 'stop', or 'tp'
695
+ let lastPrice = 0;
696
+
697
+ function initChart() {
698
+ if (chart) return;
699
+ $('#chartWrap').style.display = 'block';
700
+ chart = LightweightCharts.createChart($('#chartWrap'), {
701
+ layout: { background: { color: '#10121a' }, textColor: '#767d96' },
702
+ grid: { vertLines: { color: 'rgba(27,30,42,0.5)' }, horzLines: { color: 'rgba(27,30,42,0.5)' } },
703
+ crosshair: { mode: 0 },
704
+ rightPriceScale: { borderColor: '#1b1e2a' },
705
+ timeScale: { borderColor: '#1b1e2a', timeVisible: true }
706
+ });
707
+
708
+ candleSeries = chart.addCandlestickSeries({
709
+ upColor: '#16c784', downColor: '#ea3943', borderVisible: false,
710
+ wickUpColor: '#16c784', wickDownColor: '#ea3943'
711
+ });
712
+
713
+ // Setup drag interaction using subscribeCrosshairMove and mousedown/up
714
+ const container = $('#chartWrap');
715
+ let isMousedown = false;
716
+
717
+ container.addEventListener('mousedown', () => { isMousedown = true; });
718
+ container.addEventListener('mouseup', () => { isMousedown = false; draggingLine = null; calcRR(); });
719
+ container.addEventListener('mouseleave', () => { isMousedown = false; draggingLine = null; calcRR(); });
720
+
721
+ chart.subscribeCrosshairMove(param => {
722
+ if (!param.point || !param.price) return;
723
+ const p = param.price;
724
+
725
+ // Determine which line to drag on mousedown
726
+ if (isMousedown && !draggingLine) {
727
+ const ePrice = parseFloat($('#fEntry').value) || 0;
728
+ const sPrice = parseFloat($('#fStop').value) || 0;
729
+ const tPrice = parseFloat($('#fTP1').value) || 0;
730
+
731
+ // Calculate distances in pixels to make it easier to grab
732
+ const yClick = param.point.y;
733
+ const yEntry = candleSeries.priceToCoordinate(ePrice);
734
+ const yStop = candleSeries.priceToCoordinate(sPrice);
735
+ const yTP = candleSeries.priceToCoordinate(tPrice);
736
+
737
+ let closest = null;
738
+ let minD = 20; // 20px grab radius
739
+
740
+ if (yEntry && Math.abs(yClick - yEntry) < minD) { closest = 'entry'; minD = Math.abs(yClick - yEntry); }
741
+ if (yStop && Math.abs(yClick - yStop) < minD) { closest = 'stop'; minD = Math.abs(yClick - yStop); }
742
+ if (yTP && Math.abs(yClick - yTP) < minD) { closest = 'tp'; minD = Math.abs(yClick - yTP); }
743
+
744
+ draggingLine = closest;
745
+ }
746
+
747
+ if (draggingLine) {
748
+ if (draggingLine === 'entry') { $('#fEntry').value = Number(p).toFixed(2); entryLine.applyOptions({ price: p }); }
749
+ if (draggingLine === 'stop') { $('#fStop').value = Number(p).toFixed(2); stopLine.applyOptions({ price: p }); }
750
+ if (draggingLine === 'tp') { $('#fTP1').value = Number(p).toFixed(2); tpLine.applyOptions({ price: p }); }
751
+ // Don't calcRR on every tick to save perf, do it on mouseup
752
+ }
753
+ });
754
+ }
755
+
756
+ function syncChartLines() {
757
+ if (!chart || !candleSeries) return;
758
+ const ePrice = parseFloat($('#fEntry').value) || 0;
759
+ const sPrice = parseFloat($('#fStop').value) || 0;
760
+ const tPrice = parseFloat($('#fTP1').value) || 0;
761
+
762
+ if (entryLine) candleSeries.removePriceLine(entryLine);
763
+ if (stopLine) candleSeries.removePriceLine(stopLine);
764
+ if (tpLine) candleSeries.removePriceLine(tpLine);
765
+
766
+ if (ePrice) entryLine = candleSeries.createPriceLine({ price: ePrice, color: '#e8eaf2', lineWidth: 2, lineStyle: 0, title: 'Entry (drag)' });
767
+ if (sPrice) stopLine = candleSeries.createPriceLine({ price: sPrice, color: '#ea3943', lineWidth: 2, lineStyle: 0, title: 'Stop' });
768
+ if (tPrice) tpLine = candleSeries.createPriceLine({ price: tPrice, color: '#16c784', lineWidth: 2, lineStyle: 0, title: 'TP1' });
769
+ }
770
+
771
+ async function loadDemoTrade() {
772
+ $('#fSym').value = 'BTCUSDT';
773
+ $('#fProvider').value = 'binance';
774
+
775
+ // Fetch current price to populate realistic demo targets
776
+ initChart();
777
+ try {
778
+ const r = await fetch('/api/klines?provider=binance&symbol=BTCUSDT&interval=15m');
779
+ const data = await r.json();
780
+ if (data.klines && data.klines.length) {
781
+ candleSeries.setData(data.klines.map(k => ({
782
+ time: k.time / 1000, open: k.open, high: k.high, low: k.low, close: k.close
783
+ })));
784
+ lastPrice = data.klines[data.klines.length - 1].close;
785
+
786
+ // Populate form (Long by default)
787
+ setDir('long');
788
+ $('#fEntry').value = lastPrice.toFixed(2);
789
+ $('#fStop').value = (lastPrice * 0.985).toFixed(2); // -1.5%
790
+ $('#fTP1').value = (lastPrice * 1.035).toFixed(2); // +3.5% (approx 2.3 R:R)
791
+ $('#fLev').value = '5';
792
+ $('#fQty').value = '0.5';
793
+
794
+ calcRR();
795
+ syncChartLines();
796
+
797
+ // Auto-scale chart to fit the trade setup
798
+ setTimeout(() => { chart.timeScale().fitContent(); }, 100);
799
+ }
800
+ } catch (err) {
801
+ console.error('Failed to load chart data:', err);
802
+ }
803
+ }
804
+
805
  /* ===== init ===== */
806
  (async function init() {
807
  await poll();