o
    Kj1                     @   s   d 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Z	dZ
dZe Zdad$d
dZd$ddZd%ddZd&ddZdd Zdd ZdZdd Zdd Zdd Zdd  Zd!d" Zed#krge  dS dS )'z
Dashboard estatico del wrapper de arbitraje.

Cada 60s consulta SQLite y regenera dashboard.html con:
  - Resumen general
  - Distribucion por categoria
  - Top 10 oportunidades activas
  - Posiciones paper abiertas/cerradas
  - PnL post-mortem
    N)datetime	timedelta)Pathz*/root/arbitrage_data/data/opportunities.dbz-/root/arbitrage_data/dashboard/dashboard.html<   T c                 C   s   |   }||| | S )z=Helper para queries que devuelven filas como lista de tuples.)cursorexecutefetchall)connsqlparamsr   r   r   5/root/arbitrage_data/dashboard/dashboard_generator.pyq   s   r   c                 C   s   t | ||}|r|d S dS )zHelper para una unica fila.r   N)r   )r
   r   r   rowsr   r   r   q_one    s   r      c              	   C   p   | du rdS zt | } W n ttfy   t|  Y S w | dkr"dn| dk r(dnd}d| d	| d
| ddS )z"Format pct con signo y color HTML.N   —g{Gz?#16a34ag{Gz#dc2626#6b7280<span style="color:">+.fz%</span>float
ValueError	TypeErrorstrvdecimalscolorr   r   r   fmt_pct&      r$      c              	   C   r   )zFormat USD con color.Nr   gMbP?r   gMbPr   r   r   r   r   r   </span>r   r    r   r   r   fmt_usd2   r%   r(   c                 C   sT   | du rdS t | } | dk r| ddS | d }|dk r"|ddS |d dd	S )
z"Format duracion en formato humano.Nr   r   .0fm   z.1fhd)r   )minuteshoursr   r   r   fmt_dur>   s   r0   c                  C   s~   t  t } |  }|dk rt|d  dS |dk r-t|d  dt|d d  dS t|d  dt|d d  dS )z.Format uptime desde el arranque del generador.i  r   r*   iQ r,   r-   )r   utcnow
START_TIMEtotal_secondsint)delta	total_secr   r   r   
fmt_uptimeK   s   $$r7   ue  <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="60">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Polymarket Arb Dashboard</title>
<style>
* {{ box-sizing: border-box; }}
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
       margin: 0; padding: 20px; background: #f3f4f6; color: #111827;
       font-size: 14px; line-height: 1.4; }}
h1 {{ font-size: 22px; margin: 0 0 8px 0; }}
h2 {{ font-size: 16px; margin: 24px 0 10px 0; padding-bottom: 6px;
      border-bottom: 1px solid #e5e7eb; color: #374151; }}
.subtitle {{ color: #6b7280; font-size: 12px; margin-bottom: 20px; }}
.summary-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
                 gap: 12px; margin-bottom: 20px; }}
.metric {{ background: white; padding: 12px; border-radius: 6px;
           box-shadow: 0 1px 2px rgba(0,0,0,.05); }}
.metric .label {{ color: #6b7280; font-size: 11px; text-transform: uppercase;
                  letter-spacing: 0.5px; }}
.metric .value {{ font-size: 20px; font-weight: 600; margin-top: 4px; }}
table {{ width: 100%; border-collapse: collapse; background: white;
         border-radius: 6px; overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,.05); }}
th, td {{ padding: 8px 12px; text-align: left; border-bottom: 1px solid #f3f4f6;
          font-size: 12px; }}
th {{ background: #f9fafb; color: #6b7280; font-weight: 500; text-transform: uppercase;
      font-size: 10px; letter-spacing: 0.5px; }}
tr:hover {{ background: #f9fafb; }}
.num {{ text-align: right; font-variant-numeric: tabular-nums; }}
.muted {{ color: #9ca3af; }}
.tag {{ display: inline-block; padding: 2px 6px; border-radius: 3px;
        font-size: 10px; background: #e5e7eb; color: #374151; }}
.tag-finance {{ background: #dbeafe; color: #1e40af; }}
.tag-sports {{ background: #d1fae5; color: #065f46; }}
.tag-politics {{ background: #fef3c7; color: #92400e; }}
.tag-culture {{ background: #fce7f3; color: #9d174d; }}
.tag-tech {{ background: #ede9fe; color: #5b21b6; }}
@media (max-width: 640px) {{
  body {{ padding: 10px; font-size: 13px; }}
  .summary-grid {{ grid-template-columns: repeat(2, 1fr); }}
  table {{ font-size: 11px; }}
  th, td {{ padding: 6px 8px; }}
}}
</style>
</head>
<body>
<h1>Polymarket Arbitrage Dashboard</h1>
<p class="subtitle">Generated {generated_at} UTC · Dashboard uptime {uptime} · Auto-refresh 60s</p>

<h2>Summary</h2>
<div class="summary-grid">
{summary_metrics}
</div>

<h2>Distribution by Category (Active Opportunities)</h2>
{categories_table}

<h2>Top 10 Active Opportunities (by edge after fees)</h2>
{top_opps_table}

<h2>Paper Trader — Open Positions</h2>
{open_positions_table}

<h2>Paper Trader — Last 20 Closed Positions</h2>
{closed_positions_table}

<h2>PnL Post-mortem (Vanished opportunities)</h2>
{pnl_outcome_table}

</body>
</html>
c                 C   s   d|  d| dS )Nz'<div class="metric"><div class="label">z</div><div class="value">z</div></div>r   )labelvaluer   r   r   render_metric   s   r:   c                 C   s(   | sdS d|    }d| d|  dS )Nu   <span class="tag">—</span>ztag-z<span class="tag r   r'   )lower)catclsr   r   r   render_category_tag   s   r>   c           .      C   sJ  t | dd }t | dd }t | dd }t | dd }t | dd }t | d}|r2|d r2|d nd}t | d}|rC|d d	d
 nd}	td|td| td| td| td| tdt|d td|	 }
t| d}d}|D ]\}}}}}|dt| d| d| d| d|dd7 }qt|d7 }t| d}d}|D ]1\}}}}}}|rdnd }|pd d	d! }|d| d"t| d|d#d|dd| d"| d7 }q||rdnd$7 }t| d%}d&}|D ].\}}}}}}} |d| d"|pd d	d'  d"t| d"|d	d
  d|d#d| dd7 }q||rdnd(7 }t| d)}!d*}"|!D ]0\	}}}}#}$}%}&}'} |"d| d"|p8d d	d'  d"t| d"|% dt|& dt|' d7 }"q$|"|!r[dnd+7 }"t| d,}(d-})|(D ]!\}*}}+},}-|)d|* d| dt|+d. dt|, d|- d/7 })qg|)|(rdnd07 })tjt	
 d1t |
||||"|)d2S )3z)Construye el HTML completo del dashboard.zSELECT COUNT(*) FROM scansr   z8SELECT COUNT(*) FROM opportunities WHERE status='active'z:SELECT COUNT(*) FROM opportunities WHERE status='vanished'z8SELECT COUNT(*) FROM paper_positions WHERE status='open'z:SELECT COUNT(*) FROM paper_positions WHERE status='closed'z>SELECT SUM(pnl_usd) FROM paper_positions WHERE status='closed'z4SELECT scan_time FROM scans ORDER BY id DESC LIMIT 1N   r   zTotal scanszActive oppszVanished oppsz
Paper openzPaper closedzPaper PnL (USD)r   z	Last scanu_  
        SELECT COALESCE(category, '—') as cat, COUNT(*) as n,
               ROUND(AVG(last_edge_after_fees_pct), 2) as avg_edge,
               ROUND(MAX(last_edge_after_fees_pct), 2) as max_edge,
               ROUND(AVG(last_min_liquidity), 0) as avg_liq
        FROM opportunities WHERE status='active'
        GROUP BY cat ORDER BY n DESC
    z<table><tr><th>Category</th><th class="num">Count</th><th class="num">Avg edge</th><th class="num">Max edge</th><th class="num">Avg liq USD</th></tr>z<tr><td>z</td><td class="num">z%</td><td class="num">z%</td><td class="num">$r)   z
</td></tr>z</table>z
        SELECT description, category, last_edge_after_fees_pct, last_min_liquidity,
               scans_count, high_fee_zone
        FROM opportunities WHERE status='active'
        ORDER BY last_edge_after_fees_pct DESC LIMIT 10
    z<table><tr><th>Description</th><th>Cat</th><th class="num">Edge</th><th class="num">Liq</th><th class="num">Scans</th><th>HFZ</th></tr>u   ⚠️ A   z	</td><td>z.2fz-<p class="muted">No active opportunities.</p>z
        SELECT position_id, description, category, entry_time, entry_edge_pct,
               entry_total_cost, entry_size_usd
        FROM paper_positions WHERE status='open' ORDER BY entry_time DESC LIMIT 20
    z<table><tr><th>ID</th><th>Description</th><th>Cat</th><th>Entry time</th><th class="num">Entry edge</th><th class="num">Size USD</th></tr>7   z'<p class="muted">No open positions.</p>z
        SELECT position_id, description, category, entry_time, exit_time,
               exit_reason, pnl_usd, pnl_pct, entry_size_usd
        FROM paper_positions WHERE status='closed' ORDER BY exit_time DESC LIMIT 20
    z<table><tr><th>ID</th><th>Description</th><th>Cat</th><th>Reason</th><th class="num">PnL USD</th><th class="num">PnL %</th></tr>z-<p class="muted">No closed positions yet.</p>a  
        SELECT outcome, COUNT(*) as n,
               ROUND(AVG(pnl_pct_on_cost), 3) as avg_pnl,
               ROUND(AVG(duration_minutes), 1) as avg_dur,
               ROUND(AVG(initial_edge_pct), 2) as avg_init_edge
        FROM pnl_simulations GROUP BY outcome ORDER BY n DESC
    z<table><tr><th>Outcome</th><th class="num">Count</th><th class="num">Avg PnL%</th><th class="num">Avg duration</th><th class="num">Avg initial edge</th></tr>   z%</td></tr>z4<p class="muted">No vanished opps processed yet.</p>z%Y-%m-%d %H:%M:%S)generated_atuptimesummary_metricscategories_tabletop_opps_tableopen_positions_tableclosed_positions_tablepnl_outcome_table)r   r:   r(   r   r>   r$   r0   HTML_TEMPLATEformatr   r1   strftimer7   ).r
   n_scansn_active
n_vanishedn_paper_openn_paper_closedpaper_pnl_row	paper_pnl	last_scanlast_scan_strmetricscat_rowscat_htmlr<   navg_emax_eavg_ltop_rowstop_htmldescedgeliqscanshfzhfz_txt
desc_short	open_rows	open_htmlpidentry_tcostsizeclosed_rowsclosed_htmlentextreasonpnl_upnl_ppnl_rowspnl_htmloutcomeavg_pnlavg_duravg_initr   r   r   
build_html   sx   


0
8
L
L
6r{   c                 C   s   t d|  dd dad S )Nz[shutdown] Senal TflushF)print_running)signumframer   r   r   shutdown  s   r   c               
   C   sv  t  t jt t  t jt ttjjddd tdt dd d} t	r| d7 } zEt
t}t|}|  td }t|d}|| W d	   n1 sPw   Y  t|t td
|  dt   ddd W n& ty } ztd
|  d| dd dd	l}|  W Y d	}~nd	}~ww d}|tk rt	rttdt|  |d7 }|tk rt	st	s%tddd d	S )z2Loop infinito: regenera HTML cada REFRESH_SECONDS.T)parentsexist_okz&Dashboard generator iniciado. Salida: r|   r      z.tmpwNz[cycle z] z dashboard regeneratedz	] ERROR:    zDashboard generator detenido)signalSIGTERMr   SIGINTr   	HTML_PATHparentmkdirr~   r   sqlite3connectDB_PATHr{   closeopenwritereplacer   r1   	isoformat	Exception	traceback	print_excREFRESH_SECONDStimesleepmin)cycler
   htmltmp_pathr   er   sleptr   r   r   main  s@   

r   __main__)r   )r   )r&   )__doc__r   r   r   sysr   r   pathlibr   r   r   r   r1   r2   r   r   r   r$   r(   r0   r7   rL   r:   r>   r{   r   r   __name__r   r   r   r   <module>   s6    




Lb#
