
    6Jia              	          U d Z ddlZddlZddlZddlZddlZddlmZmZ ddl	m
Z
 ddlZddlZddlmZ ddlmZmZmZ ddlmZ  e        dZd	Zd
ZdZdZe G d d             Ze G d d             Ze G d d             ZdedededefdZ dede!eef   fdZ"dCdedede!eef   fdZ# G d d      Z$ e$d       e$d      d Z%e&ee$f   e'd!<   g Z(e)e   e'd"<   da*ejV                  dz  e'd#<   dejV                  fd$Z,d% Z-d& Z.d' Z/ e       Z0e0jc                  d(e)      d*        Z2e0jg                  d+      d,efd-       Z4e0jk                  d.      d/        Z6d0Z7e8d1k(  r e9d2d3         e9d4        e9d5        e9d6        e9d7        e9d8        e9d9        e9d:        e9d3         e9d;        e9d<        e9d=        e9d>        e9d3 d2        ejt                  e0d?d@dAB       yy)Du   
Turbo Dashboard — Green-on-black terminal-style trading interface.
BTC 5MIN + 15MIN prediction markets on Monad with Pyth oracle.

Run: python backend/dashboard.py
Open: http://localhost:8080
    N)	dataclassfield)Path)load_dotenv)FastAPI	WebSocketWebSocketDisconnect)HTMLResponsez3https://hermes.pyth.network/v2/updates/price/latestB0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43i@B          ?c                   "    e Zd ZU eed<   eed<   y)LevelpricesizeN)__name__
__module____qualname__int__annotations__float     backend/dashboard.pyr   r   (   s    J
Kr   r   c                   R    e Zd ZU  ee      Zee   ed<    ee      Zee   ed<   y)	OrderBookdefault_factorybidsasksN)	r   r   r   r   listr   r   r   r    r   r   r   r   r   -   s(    d3D$u+3d3D$u+3r   r   c                       e Zd ZU dZeed<   dZeed<   dZe	ed<   dZ
eed<   dZeed	<   d
Zeed<    ee      Zeed<    ee      Zeed<   dZe	ed<   dZe	ed<   y)MarketState 	market_id   interval        strike_pricer   
start_timeend_timeFresolvedr   yes_bookno_bookyes_posno_posN)r   r   r   r%   strr   r'   r   r)   r   r*   r+   r,   boolr   r   r-   r.   r/   r0   r   r   r   r#   r#   2   sp    IsHcL%JHcHd	:Hi:y9GY9GUFEr   r#   r'   boundary_tsstrikereturnc           	          d|  d| dt        |dz         }dt        j                  |j                               j	                         d d z   S )Nz
TURBO-BTC-zm--g    .A0x   )r   hashlibsha256encode	hexdigest)r'   r3   r4   raws       r   generate_market_idr?   @   sN    xj;-qVc\1B0C
DC'...88:3B???r   interval_minutesc                 h    t        t        j                               }| dz  }||z  |z  }||z   }||fS )z2Get current market window [start, end] timestamps.<   )r   time)r@   nowinterval_secsstartends        r   get_current_boundaryrH   E   s?    
diik
C$r)MM!]2E
-
C#:r   	mid_pricespread_centsc                     ddl t        | t        z        }t        |z
  }t        |t        z  dz        dt        dt        ffd} ||       ||      fS )z6Generate mock YES and NO orderbooks around a midpoint.r   N   midr5   c                 p   g }g }t        t              D ]  }| z
  |dz  z
  }| z   |dz  z   }|dkD  r6|j                  t        |t	        j                  dd      d                   |t        k  s^|j                  t        |t	        j                  dd      d                    t        ||      S )Ni  r      
   r   r   r   r    )range	OB_LEVELSappendr   rounduniformPRICE_SCALEr   )rM   r   r    ibpaphalf_spreadrandoms         r   	make_bookz%generate_mock_book.<locals>.make_bookU   s    y! 	SA{"QX-B{"QX-BAvEv~~a7La1PQRKEv~~a7La1PQR	S d..r   )r]   r   rX   r   )rI   rJ   yes_midno_midr^   r\   r]   s        @@r   generate_mock_bookra   N   s]    )k)*G7"Fl[0367K
/s 
/y 
/ Wy000r   c                   X    e Zd ZdZdefdZdefdZd Zd Z	defd	Z
defd
ZdefdZy)TurboMarketManagerz'Manages one BTC market (5min or 15min).r'   c                     || _         t        |      | _        d| _        d| _        g | _        d| _        d| _        d| _        y )N)r'   r(   r   Ttk   )	r'   r#   mkt	btc_pricepyth_mid	log_linespausedstrategyrJ   )selfr'   s     r   __init__zTurboMarketManager.__init__k   s@     1 #"$&!"r   msgc                     t        j                  d      }d| d| j                   d| }| j                  j	                  |       t        | j                        dkD  r| j                  dd  | _        t        |d       y )	Nz%H:%M:%S[z] [BTC/zm]    iTflush)rC   strftimer'   rj   rU   lenprint)rm   ro   tslines       r   _logzTurboMarketManager._logu   sk    ]]:&2$gdmm_Cu5d#t~~#!^^CD1DNd$r   c                    t        | j                        \  }}t        | j                  || j                  xs d      }| j                  j
                  |k7  r| j                  dkD  r| j                  j
                  }t        || j                  | j                  ||      | _        |r | j                  d| j                  d       y| j                  d| j                  d       yyy)z*Create or rotate to current market window.ip r   )r%   r'   r)   r*   r+   u   MARKET ROTATION → Strike: $z,.2fu   NEW MARKET → Strike: $N)rH   r'   r?   rh   rg   r%   r#   rz   )rm   rF   rG   expected_idolds        r   check_market_rotationz(TurboMarketManager.check_market_rotation}   s    )$--8
s(t~~?VQVW88,!1C(($$C"%!^^ DH 		9$..9NOP		4T^^D4IJK 2D,r   c           	         | j                   dk  s| j                  j                  sy| j                  j                  dkD  r\| j                   | j                  j                  z
  | j                  j                  z  }t	        dt        dd|dz  z               | _        nd| _        t        | j                  | j                        \  | j                  _	        | j                  _
        y)z?Update orderbooks (mock for now, real when contracts deployed).r   Ng?gffffff?r   r   )rh   rg   r%   r)   maxminri   ra   rJ   r-   r.   )rm   diff_pcts     r   update_bookszTurboMarketManager.update_books   s    >>Qdhh&8&888  1$)>)>>$((BWBWWHc$hl0B&CDDMDM.@PTPaPa.b+488+r   r5   c                     | j                   j                  dk(  ryt        d| j                   j                  t        t	        j                               z
        S )Nr   )rg   r+   r   r   rC   )rm   s    r   time_remainingz!TurboMarketManager.time_remaining   s>    88!1dhh''#diik*::;;r   c                 T   dt         dt        fd}| j                         }| j                  | j                  j
                  r| j                  j
                  d d dz   nd| j                  j                  | j                  | j                  |dz   d|dz  d	| || j                  j                         || j                  j                        | j                  j                  | j                  j                  | j                  | j                  | j                  | j                   d
d  dS )Nbookr5   c                     | j                   d t         D cg c]  }|j                  |j                  d c}| j                  d t         D cg c]  }|j                  |j                  d c}dS c c}w c c}w )NrQ   rR   )r   rT   r   r   r    )r   ls     r   book_to_listz0TurboMarketManager.to_dict.<locals>.book_to_list   s`    EIYYzPYEZ[177AFF;[EIYYzPYEZ[177AFF;[ [[s    A2 A7r9   z...r$   rB   :02di)r'   r%   r4   rh   ri   r   seconds_remainingr-   r.   r/   r0   rk   rl   rJ   logs)r   dictr   r'   rg   r%   r)   rh   ri   r-   r.   r/   r0   rk   rl   rJ   rj   )rm   r   trs      r   to_dictzTurboMarketManager.to_dict   s    	y 	T 	   "<@HH<N<N++CR058TVhh++!#r
!BGC=9!#$TXX%6%67#DHH$4$45xx''hhookk --NN23'
 	
r   cmdc                   K   |dk(  r7| j                    | _         | j                  | j                   rd       y d       y |dk(  r| j                  d       y |dv rDddd	d
d}| j                   r| j                  ||    d       y | j                  d||    d       y |dk(  rd| _        | j                  d       y |dk(  rd| _        | j                  d       y |j                  d      rJ	 t	        |j                  d      d         }d|cxk  rdk  r n y || _        | j                  d| d       y y y # t        t        f$ r Y y w xY ww)NRPAUSEDRESUMEDNzCancelled all orders)7890zSELL YESzBUY YESzBUY NOzSELL NOu    — paused, press R firstu   → z	 @ marketTKre   u   Strategy → TKPOLYpolytrueu   Strategy → PolyTruezSPREAD:r   rO      u   Spread → c)	rk   rz   rl   
startswithr   splitrJ   
ValueError
IndexError)rm   r   actionsvals       r   handle_commandz!TurboMarketManager.handle_command   s<    #:"kk/DKII$++h=9=CZII,-((&YXIVG{{		WS\N*DEF		Di89D[ DMII'(F]&DMII-.^^I&#))C.+,>r>(+D%IIC523 " ' 
+ s6   C)E,*D6 ED6 3E6EEEEN)r   r   r   __doc__r   rn   r1   rz   r~   r   r   r   r   r   r   r   r   rc   rc   h   sM    1# #   L&c< <

 
4 r   rc   r&   )r   r&   managers
ws_clients_http_clientc                  n   K   t         t         j                  rt        j                  d      a t         S w)Ng      @)timeout)r   	is_closedhttpxAsyncClientr   r   r   get_httpr      s*     |55((5s   35c                    K   	 	 t                d{   } | j                  t        dt        i       d{   }|j	                          |j                         }|j                  d      rB|d   d   d   }t        |d         d|d	   z  z  }t        j                         D ]	  }||_	         t        j                  d       d{    7 7 # t        $ r}t        d
| d       Y d}~@d}~ww xY w7 /w)z*Fetch BTC price from Pyth every 2 seconds.TNzids[])paramsparsedr   r   rP   expoz[PYTH] Error: rs      )r   getPYTH_HERMES_URLPYTH_BTC_FEEDraise_for_statusjsonr   r   valuesrh   	Exceptionrw   asynciosleep)httprespdatapr   mgres          r   
price_loopr      s     
	4!#D/7M:RSSD!!#99;Dxx!N1%g.AgJ26?;#??, *C$)CM* mmA #S  	4N1#&d33	4s[   C9C C$C CA7C /C9C7C9C C 	C4C/*C9/C44C9c                     K   	 t         j                         D ]"  } | j                          | j                          $ t	        j
                  t               d{    [7 w)z(Check market rotations and update books.N)r   r   r~   r   r   r   REFRESH_INTERVAL)r   s    r   market_loopr     sV     
??$ 	C%%'	 mm,---	  	.s   AA!AA!c                    K   	 t         rt        j                         D  ci c]  \  } }t        |       |j	                         ! }} }t        j                  |      }g }t         D ]  }	 |j                  |       d{     |D ]  }t         j                  |        t        j                  d       d{    c c}} w 7 F# t        $ r |j                  |       Y }w xY w7 -w)z$Push state to all WebSocket clients.Ng      ?)r   r   itemsr1   r   r   dumps	send_textr   rU   remover   r   )kvstatepayloaddeadwss         r   broadcast_loopr   
  s     
5=^^5EFTQSVQYY[(FEFjj'GD  $$,,w///$
  &!!"%&mmD!!! F
 0  $KKO$ 	"sR   C($B>#C()C=C>C6C(8C&9C(CC# C("C##C(/)response_classc                     K   t         S wN)DASHBOARD_HTMLr   r   r   rootr   "  s     s   	z/wsr   c                 b  K   | j                          d {    t        j                  |        	 	 | j                          d {   }	 t	        j
                  |      }t        |j                  dd            }|j                  dd      }|t        v r"|r t        |   j                  |       d {    7 7 t7 	# t        j                  t        f$ r Y "w xY w# t        $ r Y nw xY w	 | t        v rt        j                  |        y y # | t        v rt        j                  |        w w xY ww)Nr'   r&   r   r$   )acceptr   rU   receive_textr   loadsr   r   r   r   JSONDecodeErrorr   r	   r   )r   r   ro   r'   r   s        r   websocket_endpointr   '  s    
))+b"**Djj&swwz267ggeR(x'C"8,;;C@@@   + A((*5  b! 2b! s   D/B8D/C B:C A'B> 2B<3B> 7C 8D/:C <B> >CC CC 	C)&D (C))D -D/ D,,D/startupc                     K   t        j                  t                      t        j                  t                      t        j                  t	                      y wr   )r   create_taskr   r   r   r   r   r   start_bgr   =  s7     
%&()s   AAu1  <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TURBO — BTC Prediction Markets</title>
<style>
  :root {
    --bg: #000000;
    --panel: #0a0a0a;
    --border: #003300;
    --green: #00ff41;
    --dim: #00aa2a;
    --dark-green: #004400;
    --red: #ff0040;
    --yellow: #ccff00;
    --text: #00ff41;
    --glow: 0 0 8px rgba(0,255,65,0.3);
    --selected: #00ff41;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: var(--bg);
    color: var(--text);
    font-family: 'Courier New', 'Consolas', monospace;
    font-size: 13px;
    overflow-x: hidden;
    min-height: 100vh;
  }

  /* Header */
  .header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 16px;
    border-bottom: 1px solid var(--border);
    background: var(--panel);
  }
  .logo {
    font-size: 20px;
    font-weight: 900;
    color: var(--green);
    text-shadow: var(--glow);
    letter-spacing: 3px;
  }
  .btc-price {
    font-size: 16px;
    font-weight: 700;
    color: var(--green);
    text-shadow: var(--glow);
  }

  /* Grid: two panels side by side */
  .grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2px;
    padding: 2px;
    height: calc(100vh - 80px);
  }

  /* Market panel */
  .panel {
    background: var(--panel);
    border: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  .panel.selected {
    border-color: var(--green);
    box-shadow: var(--glow);
  }

  /* Panel header */
  .panel-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 12px;
    border-bottom: 1px solid var(--border);
    background: #050505;
  }
  .panel-title {
    font-size: 18px;
    font-weight: 900;
    color: var(--green);
    text-shadow: var(--glow);
    letter-spacing: 2px;
  }
  .panel-info {
    font-size: 12px;
    color: var(--dim);
  }
  .panel-info .countdown {
    color: var(--green);
    font-weight: 700;
    font-size: 14px;
  }
  .panel-info .strike { color: var(--dim); }
  .status-tag {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 3px;
    font-size: 11px;
    font-weight: 700;
  }
  .status-tag.paused { background: var(--dark-green); color: var(--yellow); }
  .status-tag.running { background: #002200; color: var(--green); }

  /* Orderbooks container */
  .books {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    flex: 1;
    overflow: hidden;
  }

  /* Single orderbook (YES or NO) */
  .book {
    display: flex;
    flex-direction: column;
    padding: 4px 8px;
    overflow: hidden;
  }
  .book-header {
    display: flex;
    justify-content: space-between;
    padding: 4px 0;
    border-bottom: 1px solid var(--border);
    font-weight: 700;
    font-size: 14px;
  }
  .book-header.yes { color: var(--green); }
  .book-header.no { color: var(--red); }
  .book-header .pos { font-size: 12px; font-weight: 400; }

  /* Column headers */
  .col-headers {
    display: flex;
    justify-content: space-between;
    padding: 2px 0;
    font-size: 10px;
    color: var(--dark-green);
    text-transform: uppercase;
  }

  /* Orderbook levels */
  .levels { flex: 1; display: flex; flex-direction: column; }
  .asks { justify-content: flex-end; }

  .level {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 4px 0;
    position: relative;
    font-size: 13px;
    height: 28px;
  }
  .level .bar {
    position: absolute;
    top: 0;
    right: 0;
    height: 100%;
    opacity: 0.12;
  }
  .level.ask .bar { background: var(--red); }
  .level.bid .bar { background: var(--green); }
  .level.edge { background: rgba(204,255,0,0.08); }
  .level .price { font-weight: 600; z-index: 1; }
  .level.ask .price { color: var(--red); }
  .level.bid .price { color: var(--green); }
  .level .size { color: var(--dim); z-index: 1; text-align: right; }

  /* Spread line */
  .spread-line {
    text-align: center;
    padding: 4px 0;
    font-size: 11px;
    color: var(--dark-green);
    border-top: 1px dashed var(--border);
    border-bottom: 1px dashed var(--border);
  }

  /* Controls */
  .controls {
    display: flex;
    gap: 4px;
    padding: 6px 8px;
    border-top: 1px solid var(--border);
    align-items: center;
    flex-wrap: wrap;
  }
  .ctrl-btn {
    background: var(--bg);
    color: var(--green);
    border: 1px solid var(--border);
    padding: 4px 10px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
    border-radius: 3px;
  }
  .ctrl-btn:hover { border-color: var(--green); box-shadow: var(--glow); }
  .ctrl-btn.active { background: var(--dark-green); color: var(--green); border-color: var(--green); }
  .ctrl-btn.sell { color: var(--red); border-color: #330000; }
  .ctrl-btn.sell:hover { border-color: var(--red); }
  .ctrl-btn.warn { color: var(--yellow); border-color: #333300; }

  .spread-input {
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--green);
    font-family: inherit;
    font-size: 12px;
    padding: 4px 6px;
    width: 40px;
    text-align: center;
    border-radius: 3px;
  }

  /* Log area */
  .log-area {
    border-top: 1px solid var(--border);
    padding: 4px 8px;
    min-height: 100px;
    max-height: 150px;
    overflow-y: auto;
    font-size: 11px;
    background: #020202;
  }
  .log-line { padding: 1px 0; color: var(--dim); white-space: nowrap; }

  /* Bottom bar */
  .bottom-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 6px 16px;
    background: var(--panel);
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: center;
    gap: 16px;
    font-size: 11px;
    color: var(--dark-green);
  }
  .bottom-bar kbd {
    background: var(--bg);
    border: 1px solid var(--border);
    padding: 1px 5px;
    border-radius: 2px;
    color: var(--dim);
    font-family: inherit;
  }

  /* Scanline effect */
  body::after {
    content: '';
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: repeating-linear-gradient(
      0deg,
      transparent,
      transparent 2px,
      rgba(0,255,65,0.015) 2px,
      rgba(0,255,65,0.015) 4px
    );
    pointer-events: none;
    z-index: 9999;
  }
</style>
</head>
<body>

<div class="header">
  <span class="logo">TURBO</span>
  <span class="btc-price" id="btc-price">BTC $--,---.--</span>
</div>

<div class="grid" id="grid"></div>

<div class="bottom-bar">
  <span><kbd>Tab</kbd> switch panel</span>
  <span><kbd>7</kbd> Sell Y</span>
  <span><kbd>8</kbd> Buy Y</span>
  <span><kbd>9</kbd> Buy N</span>
  <span><kbd>0</kbd> Sell N</span>
  <span><kbd>R</kbd> Pause</span>
  <span><kbd>N</kbd> Cancel</span>
</div>

<script>
let state = {};
let selected = 5;  // 5 or 15
const intervals = [5, 15];

const ws = new WebSocket(`ws://${location.host}/ws`);
ws.onmessage = (e) => { state = JSON.parse(e.data); render(); };
ws.onclose = () => setTimeout(() => location.reload(), 2000);

function send(interval, cmd) {
  if (ws.readyState === WebSocket.OPEN)
    ws.send(JSON.stringify({interval, cmd}));
}

function fmtPrice(p) { return '$' + (p / 1000000).toFixed(3); }

function renderBook(book, type) {
  // type = 'yes' or 'no'
  const maxSz = Math.max(...[...book.bids, ...book.asks].map(l => l.size), 1);
  let html = '';

  // Asks (reversed — highest at top)
  const asks = [...book.asks].reverse();
  html += '<div class="levels asks">';
  for (const a of asks) {
    const pct = (a.size / maxSz * 100) | 0;
    html += `<div class="level ask">
      <div class="bar" style="width:${pct}%"></div>
      <span class="price">${fmtPrice(a.price)}</span>
      <span class="size">${a.size.toFixed(1)}</span>
    </div>`;
  }
  html += '</div>';

  // Spread
  const bestBid = book.bids[0]?.price || 0;
  const bestAsk = book.asks[0]?.price || 0;
  const spread = bestAsk > 0 && bestBid > 0 ? ((bestAsk - bestBid) / 10000).toFixed(1) : '--';
  html += `<div class="spread-line">${spread}c spread</div>`;

  // Bids
  html += '<div class="levels bids">';
  for (const b of book.bids) {
    const pct = (b.size / maxSz * 100) | 0;
    html += `<div class="level bid">
      <div class="bar" style="width:${pct}%"></div>
      <span class="price">${fmtPrice(b.price)}</span>
      <span class="size">${b.size.toFixed(1)}</span>
    </div>`;
  }
  html += '</div>';

  return html;
}

function render() {
  const grid = document.getElementById('grid');
  let html = '';

  for (const intv of intervals) {
    const d = state[String(intv)];
    if (!d) continue;
    const sel = intv === selected;
    const statusClass = d.paused ? 'paused' : 'running';
    const statusText = d.paused ? 'PAUSED' : (d.strategy === 'tk' ? 'TK' : 'POLYTRUE');

    html += `<div class="panel${sel ? ' selected' : ''}" onclick="selected=${intv};render()">`;

    // Header
    html += `<div class="panel-header">
      <div>
        <span class="panel-title">BTC ${intv}MIN</span>
        <span class="status-tag ${statusClass}">${statusText}</span>
      </div>
      <div class="panel-info">
        <span class="strike">Strike: $${d.strike ? d.strike.toLocaleString(undefined,{maximumFractionDigits:2}) : '--'}</span>
        &nbsp;
        <span class="countdown">${d.time_remaining}</span>
      </div>
    </div>`;

    // Orderbooks
    html += '<div class="books">';

    // YES book
    html += '<div class="book">';
    html += `<div class="book-header yes">
      <span>YES $${(d.pyth_mid || 0).toFixed(3)}</span>
      <span class="pos" style="color:${d.yes_pos > 0 ? 'var(--green)' : 'var(--dim)'}">pos:${(d.yes_pos||0).toFixed(1)}</span>
    </div>`;
    html += '<div class="col-headers"><span>PRICE</span><span>SIZE</span></div>';
    html += renderBook(d.yes_book, 'yes');
    html += '</div>';

    // NO book
    html += '<div class="book">';
    html += `<div class="book-header no">
      <span>NO $${(1 - (d.pyth_mid || 0)).toFixed(3)}</span>
      <span class="pos" style="color:${d.no_pos > 0 ? 'var(--red)' : 'var(--dim)'}">pos:${(d.no_pos||0).toFixed(1)}</span>
    </div>`;
    html += '<div class="col-headers"><span>PRICE</span><span>SIZE</span></div>';
    html += renderBook(d.no_book, 'no');
    html += '</div>';

    html += '</div>';  // books

    // Controls
    const isTK = d.strategy === 'tk';
    html += `<div class="controls">
      <button class="ctrl-btn${isTK?' active':''}" onclick="event.stopPropagation();send(${intv},'TK')">TK</button>
      <button class="ctrl-btn${!isTK?' active':''}" onclick="event.stopPropagation();send(${intv},'POLY')">Poly</button>
      <button class="ctrl-btn sell" onclick="event.stopPropagation();send(${intv},'7')">7 Sell Y</button>
      <button class="ctrl-btn" onclick="event.stopPropagation();send(${intv},'8')">8 Buy Y</button>
      <button class="ctrl-btn" onclick="event.stopPropagation();send(${intv},'9')">9 Buy N</button>
      <button class="ctrl-btn sell" onclick="event.stopPropagation();send(${intv},'0')">0 Sell N</button>
      <button class="ctrl-btn warn" onclick="event.stopPropagation();send(${intv},'R')">R</button>
      <button class="ctrl-btn warn" onclick="event.stopPropagation();send(${intv},'N')">N</button>
      <label style="font-size:11px;color:var(--dark-green)">Spread:</label>
      <input class="spread-input" value="${d.spread_cents}" onchange="event.stopPropagation();send(${intv},'SPREAD:'+this.value)" />
      <span style="font-size:11px;color:var(--dark-green)">c</span>
    </div>`;

    // Log
    html += '<div class="log-area">';
    html += (d.logs || []).map(l => `<div class="log-line">${l}</div>`).join('');
    html += '</div>';

    html += '</div>';  // panel
  }

  grid.innerHTML = html;

  // BTC price
  const btcEl = document.getElementById('btc-price');
  const anyMgr = state['15'] || state['5'];
  if (anyMgr && anyMgr.btc_price > 0) {
    btcEl.textContent = 'BTC $' + anyMgr.btc_price.toLocaleString(undefined, {maximumFractionDigits: 2});
  }

  // Auto-scroll logs
  document.querySelectorAll('.log-area').forEach(el => el.scrollTop = el.scrollHeight);
}

// Keyboard
document.addEventListener('keydown', (e) => {
  if (e.target.tagName === 'INPUT') return;
  if (e.key === 'Tab') {
    e.preventDefault();
    const idx = intervals.indexOf(selected);
    selected = intervals[(idx + 1) % intervals.length];
    render();
    return;
  }
  const map = {
    '7':'7','8':'8','9':'9','0':'0',
    'r':'R','R':'R','n':'N','N':'N',
  };
  if (map[e.key]) send(selected, map[e.key]);
});
</script>
</body>
</html>
__main__
z7=======================================================uu     ████████╗██╗   ██╗██████╗ ██████╗  ██████╗ u}     ╚══██╔══╝██║   ██║██╔══██╗██╔══██╗██╔═══██╗uk        ██║   ██║   ██║██████╔╝██████╔╝██║   ██║uk        ██║   ██║   ██║██╔══██╗██╔══██╗██║   ██║us        ██║   ╚██████╔╝██║  ██║██████╔╝╚██████╔╝ui        ╚═╝    ╚═════╝ ╚═╝  ╚═╝╚═════╝  ╚═════╝ z!  BTC Prediction Markets on Monadz"  Markets:    BTC 5MIN + BTC 15MINz  Oracle:     Pyth Networkz  Chain:      Monadz!  Dashboard:  http://0.0.0.0:8080z0.0.0.0i  warning)hostport	log_level)rf   );r   r   r   osrC   r:   dataclassesr   r   pathlibr   r   uvicorndotenvr   fastapir   r   r	   fastapi.responsesr
   r   r   rX   rT   r   r   r   r#   r   r   r1   r?   tuplerH   ra   rc   r   r   r   r   r!   r   r   r   r   r   r   appr   r   	websocketr   on_eventr   r   r   rw   runr   r   r   <module>r      s     	   (     ; ; * 
 HT	     4 4 4 
 
 
@ @3 @ @# @
3 5c? 1% 1s 15T]I]C^ 14m mj !2+$s&&
'  !
DO  )-e$& -)) $."* i \* + u" " "* i* *Kb z	Bvh-	  B  D	  J  L	wy	wy	  B	uw	-/	VH	.0	&(	!	-/	VHB-GKK)$)D! r   