""" Monitor visuale INTRADAY 2H per la strategia RETTANGOLO (breakout + retest). - Range: high/low candela daily precedente (rettangolo blu) - Candele: 2H intraday (ultimi 7 giorni) - Segnale live: candela 2H corrente, se c'è breakout precedente nelle ultime 10 candele e la corrente tocca il livello rotto con pattern di inversione -> entry - Auto-refresh: rigenera ogni 60s """ import sys import time import json import urllib.request import urllib.parse from datetime import datetime, timezone from pathlib import Path import plotly.graph_objects as go from plotly.subplots import make_subplots from rettangolo_strategy import compute_signal, scan_signals OUT_HTML = Path(r"G:\AI TRADING ENGINE\live_deploy\bt_rettangolo\monitor.html") ASSETS = ["SOLUSDT", "BTCUSDT", "ETHUSDT", "WIFUSDT"] def fetch_klines(symbol, interval, days): end_ms = int(time.time() * 1000) start_ms = end_ms - days * 24 * 3600 * 1000 params = {"category": "linear", "symbol": symbol, "interval": interval, "start": start_ms, "end": end_ms, "limit": 200} qs = urllib.parse.urlencode(params) url = f"https://api.bybit.com/v5/market/kline?{qs}" with urllib.request.urlopen(url, timeout=15) as r: data = json.loads(r.read().decode("utf-8")) rows = data.get("result", {}).get("list", []) rows.sort(key=lambda x: int(x[0])) klines = [] for r in rows: klines.append({ "ts": int(r[0]), "date": datetime.fromtimestamp(int(r[0])/1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M"), "open": float(r[1]), "high": float(r[2]), "low": float(r[3]), "close": float(r[4]), "volume": float(r[5]) }) return klines def build_figure(symbol, intraday_klines, daily_klines): if len(intraday_klines) < 5 or len(daily_klines) < 2: return None # Filtra: solo candele 2H del giorno precedente (ieri) e del giorno in osservazione (oggi) # giorno precedente = candela daily[-2], giorno in osservazione = candela daily[-1] cur_day = daily_klines[-1] prev_day = daily_klines[-2] cur_day_start = cur_day["ts"] prev_day_start = prev_day["ts"] filtered = [k for k in intraday_klines if prev_day_start <= k["ts"] <= cur_day_start + 24*3600*1000] if len(filtered) < 3: filtered = intraday_klines # fallback se filtro troppo aggressivo prev_daily = prev_day # RANGE dal giorno precedente (daily[-2]): high/low di ieri rng_top = prev_daily["high"] rng_bot = prev_daily["low"] rng_mid = (rng_top + rng_bot) / 2 rng_pct = (rng_top - rng_bot) / rng_bot * 100 # candele 2H (solo ieri + oggi) dates = [k["date"] for k in filtered] opens = [k["open"] for k in filtered] highs = [k["high"] for k in filtered] lows = [k["low"] for k in filtered] closes = [k["close"] for k in filtered] # split: ieri (linea continua) vs oggi (linea tratteggiata) today_start = cur_day["ts"] idx_today = next((i for i, k in enumerate(filtered) if k["ts"] >= today_start), len(filtered)) yesterday = filtered[:idx_today] today = filtered[idx_today:] # segnale live sig = compute_signal(prev_daily, filtered, len(filtered) - 1) sig_text = "NESSUN SETUP - in attesa breakout + retest" sig_color = "gray" if sig: sig_text = ">>> " + sig["signal"] + " ENTRY @ " + format(sig["entry"], ".4f") + " | SL=" + format(sig["sl"], ".4f") + " | TP=" + format(sig["tp"], ".4f") + " <<<" sig_color = "#4caf50" if sig["signal"] == "LONG" else "#ef5350" historical = scan_signals(prev_daily, filtered) historical = historical[-5:] # figura fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, row_heights=[0.75, 0.25]) fig.add_trace(go.Candlestick( x=[k["date"] for k in yesterday], open=[k["open"] for k in yesterday], high=[k["high"] for k in yesterday], low=[k["low"] for k in yesterday], close=[k["close"] for k in yesterday], name=symbol + " 2H IERI", increasing_line_color="#26a69a", decreasing_line_color="#ef5350" ), row=1, col=1) fig.add_trace(go.Candlestick( x=[k["date"] for k in today], open=[k["open"] for k in today], high=[k["high"] for k in today], low=[k["low"] for k in today], close=[k["close"] for k in today], name=symbol + " 2H OGGI (in formazione)", increasing_line_color="#80cbc4", decreasing_line_color="#ef9a9a", opacity=0.5 ), row=1, col=1) # rettangolo tratteggiato del range daily precedente fig.add_shape( type="rect", x0=dates[0], x1=dates[-1], y0=rng_bot, y1=rng_top, line=dict(color="rgba(33,150,243,0.4)", width=1, dash="dot"), fillcolor="rgba(33,150,243,0.06)", name="Range " + prev_daily["date"][:10] ) # LINEE TRATTEGGIATE TOP/BOTTOM: prosecuzione del min/max del giorno precedente, estese su tutto il chart fig.add_shape( type="line", x0=dates[0], x1=dates[-1], y0=rng_top, y1=rng_top, line=dict(color="rgba(33,150,243,1.0)", width=2.5, dash="dash"), name="TOP " + format(rng_top, ".4f") ) fig.add_shape( type="line", x0=dates[0], x1=dates[-1], y0=rng_bot, y1=rng_bot, line=dict(color="rgba(33,150,243,1.0)", width=2.5, dash="dash"), name="BOTTOM " + format(rng_bot, ".4f") ) # linea MID (target TP) fig.add_shape( type="line", x0=dates[0], x1=dates[-1], y0=rng_mid, y1=rng_mid, line=dict(color="rgba(255,152,0,1.0)", width=2, dash="dot"), name="MID " + format(rng_mid, ".4f") ) for k in filtered: if k["close"] > rng_top: fig.add_trace(go.Scatter( x=[k["date"]], y=[k["close"]], mode="markers", marker=dict(symbol="triangle-up", size=8, color="#4caf50", opacity=0.6), showlegend=False, hoverinfo="skip" ), row=1, col=1) elif k["close"] < rng_bot: fig.add_trace(go.Scatter( x=[k["date"]], y=[k["close"]], mode="markers", marker=dict(symbol="triangle-down", size=8, color="#ef5350", opacity=0.6), showlegend=False, hoverinfo="skip" ), row=1, col=1) # etichette prezzi lato destro fig.add_annotation( xref="paper", x=0.985, y=rng_top, text="BUY " + format(rng_top, ".4f"), showarrow=False, xanchor="left", yanchor="middle", font=dict(color="#ef5350", size=14, family="monospace"), bgcolor="rgba(239,83,80,0.15)", bordercolor="#ef5350", borderwidth=1, borderpad=4 ) fig.add_annotation( xref="paper", x=0.985, y=rng_bot, text="SELL " + format(rng_bot, ".4f"), showarrow=False, xanchor="left", yanchor="middle", font=dict(color="#4caf50", size=14, family="monospace"), bgcolor="rgba(76,175,80,0.15)", bordercolor="#4caf50", borderwidth=1, borderpad=4 ) fig.add_annotation( xref="paper", x=0.985, y=rng_mid, text="TP " + format(rng_mid, ".4f"), showarrow=False, xanchor="left", yanchor="middle", font=dict(color="#ff9800", size=14, family="monospace"), bgcolor="rgba(255,152,0,0.15)", bordercolor="#ff9800", borderwidth=1, borderpad=4 ) for s in historical: color = "#4caf50" if s["signal"] == "LONG" else "#ef5350" symbol_arrow = "triangle-up" if s["signal"] == "LONG" else "triangle-down" fig.add_trace(go.Scatter( x=[s["date"]], y=[s["entry"]], mode="markers", marker=dict(symbol=symbol_arrow, size=12, color=color, line=dict(width=2, color="white")), showlegend=False, hoverinfo="text", hovertext=s["signal"] + " @ " + format(s["entry"], ".4f") ), row=1, col=1) colors = ["#26a69a" if closes[i] >= opens[i] else "#ef5350" for i in range(len(closes))] fig.add_trace(go.Bar(x=dates, y=[k["volume"] for k in filtered], marker_color=colors, name="Volume", showlegend=False), row=2, col=1) fig.update_layout( title="" + symbol + " • Range " + prev_daily["date"][:10] + ": " + format(rng_bot, ".4f") + " — " + format(rng_top, ".4f") + " (" + format(rng_pct, ".2f") + "%)
" "" + sig_text + "", template="plotly_dark", height=700, width=1500, xaxis=dict(dtick=7200000, tickformat="%H:%M\n%b %d"), xaxis2=dict(dtick=7200000, tickformat="%H:%M\n%b %d"), xaxis_rangeslider_visible=False, xaxis2_rangeslider_visible=False, legend=dict(orientation="h", y=1.08, x=0.5, xanchor="center"), ) return fig def build_html(per_symbol_data, generated_at): figs = {} for sym, data in per_symbol_data.items(): fig = build_figure(sym, data["intraday"], data["daily"]) if fig: figs[sym] = fig if not figs: return "

Nessun dato disponibile

" tabs_html = "" divs_html = "" for i, (sym, fig) in enumerate(figs.items()): active = " active" if i == 0 else "" tabs_html += '\n' div_inner = fig.to_html(full_html=False, include_plotlyjs="cdn", div_id="plot_" + sym) divs_html += '
\n' + div_inner + '\n
\n' css = """ body { background: #1a1a1a; color: #fff; font-family: monospace; margin: 20px; } h1 { color: #2196f3; } .tab { overflow: hidden; border-bottom: 1px solid #555; } .tab button { background: #222; color: #fff; border: none; padding: 12px 20px; cursor: pointer; font-size: 14px; font-weight: bold; } .tab button:hover { background: #333; } .tab button.active { background: #2196f3; color: #fff; } .tabcontent { display: none; padding: 10px 0; } .tabcontent.active { display: block; } .footer { margin-top: 30px; color: #888; font-size: 12px; text-align: center; } .refresh { color: #4caf50; } """ js = """ function openTab(evt, sym) { var i, tabcontent, tablinks; tabcontent = document.getElementsByClassName("tabcontent"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].className = tabcontent[i].className.replace(" active", ""); } tablinks = document.getElementsByClassName("tablinks"); for (i = 0; i < tablinks.length; i++) { tablinks[i].className = tablinks[i].className.replace(" active", ""); } document.getElementById(sym).className += " active"; evt.currentTarget.className += " active"; var plotDiv = document.querySelector('#' + sym + ' .plotly'); if (plotDiv && window.Plotly) { window.Plotly.Plots.resize(plotDiv); } } """ html = """ Rettangolo Monitor 2H - """ + generated_at + """

📊 Rettangolo Monitor INTRADAY 2H — Strategia Charter (Breakout + Retest)

Generato: """ + generated_at + """ | Auto-refresh: ogni 60s

Range blu = high/low candela daily precedente • Linea arancione = MID (target TP)

▲ verde / ▼ rosso sui bordi = breakout (candela 2H chiusa oltre range) • Etichette sui tocchi = segnali di retest confermati

LOGICA: breakout candela 2H (close oltre range) → candela successiva tocca livello rotto + Doji/Hammer → entry live

""" + tabs_html + """
""" + divs_html + """ """ return html def run_once(): generated_at = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M:%S %Z") print("[" + generated_at + "] rigenero monitor 2H...") per_symbol_data = {} for sym in ASSETS: try: intraday = fetch_klines(sym, "120", days=3) daily = fetch_klines(sym, "D", days=10) if intraday and daily: per_symbol_data[sym] = {"intraday": intraday, "daily": daily} except Exception as e: print(" err " + sym + ": " + str(e)) html = build_html(per_symbol_data, generated_at) OUT_HTML.parent.mkdir(parents=True, exist_ok=True) # forza no-cache con query string + meta html = html.replace( '', '\n\n' ) OUT_HTML.write_text(html, encoding="utf-8") print(" -> " + str(OUT_HTML) + " (" + str(len(html)) + " chars)") if __name__ == "__main__": if "--once" in sys.argv: run_once() else: print("Loop infinito, rigenero ogni 60s. Ctrl+C per uscire.") while True: try: run_once() except Exception as e: print("ERR: " + str(e)) time.sleep(60)