arbproxy / decimal-bot.html
Arbitrage01's picture
Upload decimal-bot.html
6223280 verified
Raw History Blame Contribute Delete
110 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>IPL Arb v4</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0
}
:root {
--g: #00d47e;
--g2: #00ff99;
--r: #ff3a5e;
--y: #ffd44a;
--p: #9b95ff;
--o: #ff8a60;
--mum: #f5a623;
--kol: #9b59ff;
--bk: #6db3ff;
--lk: #ff7099;
--bg: #07090f;
--s1: #090e1c;
--s2: #0d1322;
--bd: #1a2540;
--bd2: #243050;
--txt: #b8cae8;
--txt2: #4a5a72;
--head: #e8f0ff
}
body {
background: var(--bg);
color: var(--txt);
font-family: monospace;
font-size: 12px;
min-height: 100vh
}
/* ── HEADER ── */
.hdr {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 14px;
background: #0a0f1e;
border-bottom: 1px solid var(--bd);
flex-wrap: wrap;
gap: 6px;
position: sticky;
top: 0;
z-index: 200;
backdrop-filter: blur(8px)
}
.logo {
font-family: sans-serif;
font-size: 18px;
font-weight: 900;
letter-spacing: 2px
}
.logo .lp {
color: var(--p)
}
.logo .lo {
color: var(--o)
}
.logo small {
display: block;
font-family: monospace;
font-size: 9px;
color: var(--txt2);
letter-spacing: 3px
}
.hr {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: wrap
}
.live {
display: flex;
align-items: center;
gap: 4px;
font-size: 10px;
color: var(--g);
letter-spacing: 1px
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--g);
animation: blink 1.2s infinite
}
@keyframes blink {
50% {
opacity: .15
}
}
.hbtn {
background: var(--s2);
border: 1px solid var(--bd2);
color: var(--txt2);
padding: 4px 10px;
border-radius: 3px;
cursor: pointer;
font-family: monospace;
font-size: 10px;
letter-spacing: 1px;
transition: .15s
}
.hbtn:hover {
border-color: var(--p);
color: var(--p)
}
.nav-link {
display: inline-flex;
align-items: center;
justify-content: center;
}
#ncall {
color: var(--p)
}
/* ── SETTINGS ── */
.stw {
background: var(--s1);
border-bottom: 1px solid var(--bd)
}
.sthd {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 14px;
cursor: pointer;
user-select: none
}
.sthl {
font-family: sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 2px;
color: var(--head)
}
.stsub {
font-size: 10px;
color: var(--txt2);
margin-top: 1px
}
.stsub b {
color: var(--y);
font-weight: 400
}
.stbd {
display: none;
padding: 0 14px 14px;
border-top: 1px solid var(--bd)
}
.stbd.open {
display: block
}
.sg {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: flex-end;
margin-top: 10px
}
.f {
display: flex;
flex-direction: column;
gap: 2px
}
.f label {
font-size: 9px;
letter-spacing: 1px;
color: var(--txt2);
text-transform: uppercase
}
.f input,
.f select {
background: var(--s2);
border: 1px solid var(--bd);
color: var(--head);
padding: 5px 8px;
font-family: monospace;
font-size: 11px;
border-radius: 3px;
width: 130px;
transition: .15s
}
.f.w input {
width: 210px
}
.f input:focus,
.f select:focus {
outline: none;
border-color: var(--p)
}
.sdiv {
width: 100%;
height: 1px;
background: var(--bd);
margin: 5px 0
}
.slbl {
font-size: 9px;
letter-spacing: 2px;
color: var(--txt2);
width: 100%
}
.abtn {
padding: 6px 14px;
border-radius: 3px;
border: none;
cursor: pointer;
font-family: monospace;
font-size: 11px;
font-weight: 700;
letter-spacing: 1px
}
.abp {
background: #6c63ff;
color: #fff
}
.abb {
background: #ff6030;
color: #fff
}
.abtn:hover {
opacity: .85
}
/* ── MATCH BAR ── */
.mbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 7px 14px;
background: var(--s2);
border-bottom: 1px solid var(--bd);
flex-wrap: wrap;
gap: 6px
}
.mt {
font-family: sans-serif;
font-size: 16px;
font-weight: 900;
color: var(--head);
letter-spacing: 1px
}
.pills {
display: flex;
gap: 6px;
flex-wrap: wrap;
align-items: center
}
.pill {
padding: 3px 12px;
border-radius: 20px;
font-family: sans-serif;
font-size: 13px;
font-weight: 700
}
.pm {
background: rgba(245, 166, 35, .12);
color: var(--mum);
border: 1px solid rgba(245, 166, 35, .3)
}
.pk {
background: rgba(155, 89, 255, .12);
color: var(--kol);
border: 1px solid rgba(155, 89, 255, .3)
}
.mr {
font-size: 10px;
color: var(--txt2);
display: flex;
gap: 10px;
flex-wrap: wrap
}
.mr span {
color: var(--y)
}
/* ── TABS ── */
.tabs {
display: flex;
background: var(--s1);
border-bottom: 1px solid var(--bd)
}
.tab {
padding: 8px 14px;
font-size: 11px;
font-weight: 700;
letter-spacing: 1px;
color: var(--txt2);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: .15s
}
.tab.on {
color: var(--p);
border-bottom-color: #6c63ff
}
.tc {
display: none
}
.tc.on {
display: block
}
/* ── ORDER BOOKS ── */
.obg {
display: grid;
grid-template-columns: 1fr 1fr;
border-bottom: 1px solid var(--bd)
}
@media(max-width:620px) {
.obg {
grid-template-columns: 1fr
}
}
.obp {
border-right: 1px solid var(--bd)
}
.obp:last-child {
border-right: none
}
.obh {
display: flex;
align-items: center;
justify-content: space-between;
padding: 7px 12px;
border-bottom: 1px solid var(--bd);
background: rgba(255, 255, 255, .01)
}
.obt {
font-family: sans-serif;
font-size: 13px;
font-weight: 700;
letter-spacing: 2px
}
.obt.p {
color: var(--p)
}
.obt.b {
color: var(--o)
}
.bdg {
font-size: 9px;
padding: 2px 7px;
border-radius: 10px;
letter-spacing: 1px
}
.bdg-p {
background: rgba(108, 99, 255, .15);
color: var(--p);
border: 1px solid rgba(108, 99, 255, .25)
}
.bdg-b {
background: rgba(255, 96, 48, .15);
color: var(--o);
border: 1px solid rgba(255, 96, 48, .25)
}
.bdg-ok {
background: rgba(0, 212, 126, .12);
color: var(--g);
border: 1px solid rgba(0, 212, 126, .25)
}
.bdg-w {
background: rgba(255, 212, 74, .12);
color: var(--y);
border: 1px solid rgba(255, 212, 74, .25)
}
.pmeta {
display: flex;
justify-content: space-between;
padding: 5px 12px;
border-bottom: 1px solid var(--bd);
color: var(--txt2);
font-size: 10px;
flex-wrap: wrap;
gap: 4px
}
.pmeta span {
color: var(--head)
}
table {
width: 100%;
border-collapse: collapse
}
th {
padding: 4px 8px;
font-size: 9px;
letter-spacing: 1px;
color: var(--txt2);
text-align: right;
border-bottom: 1px solid var(--bd);
font-weight: 400
}
th:first-child {
text-align: left
}
td {
padding: 5px 8px;
text-align: right;
border-bottom: 1px solid rgba(26, 37, 64, .4);
font-size: 11px
}
td:first-child {
text-align: left
}
tr.ask td {
color: var(--lk)
}
tr.bid td {
color: var(--g)
}
tr.ask td.pr {
color: var(--r);
font-weight: 700
}
tr.bid td.pr {
color: var(--g2);
font-weight: 700
}
tr.spr td {
text-align: center !important;
color: var(--y);
font-size: 9px;
background: rgba(255, 212, 74, .03);
padding: 3px;
border-bottom: 1px solid var(--bd) !important
}
tr:hover td {
background: rgba(255, 255, 255, .015)
}
/* BHAV */
.bhdr {
display: grid;
grid-template-columns: repeat(6, 1fr);
border-bottom: 1px solid var(--bd)
}
.bh {
text-align: center;
font-size: 8px;
padding: 3px 0;
letter-spacing: 1px
}
.bh.bk {
color: var(--bk);
background: rgba(46, 143, 255, .05)
}
.bh.lk {
color: var(--r);
background: rgba(255, 58, 110, .05)
}
.brow {
border-bottom: 1px solid var(--bd)
}
.brh {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 12px;
background: rgba(255, 255, 255, .01)
}
.btn {
font-family: sans-serif;
font-size: 13px;
font-weight: 700
}
.btn.m {
color: var(--mum)
}
.btn.k {
color: var(--kol)
}
.bcells {
display: grid;
grid-template-columns: repeat(6, 1fr)
}
.bc {
padding: 6px 3px;
text-align: center;
border-right: 1px solid var(--bd)
}
.bc:last-child {
border-right: none
}
.bc.bk {
background: rgba(46, 143, 255, .05)
}
.bc.lk {
background: rgba(255, 58, 110, .05)
}
.bc.bbk {
background: rgba(46, 143, 255, .22) !important
}
.bc.blk {
background: rgba(255, 58, 110, .24) !important
}
.bo {
font-size: 12px;
font-weight: 700
}
.bc.bk .bo {
color: var(--bk)
}
.bc.lk .bo {
color: var(--lk)
}
.bc.bbk .bo,
.bc.blk .bo {
color: #fff
}
.bl {
font-size: 9px;
color: var(--txt2);
margin-top: 1px
}
/* ── CALCULATOR ── */
.cw {
padding: 12px 14px
}
.ctit {
font-family: sans-serif;
font-size: 16px;
font-weight: 900;
letter-spacing: 2px;
color: var(--head);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px
}
.ctit::after {
content: '';
flex: 1;
height: 1px;
background: var(--bd)
}
.ci {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
align-items: flex-end
}
/* DIRECTION SELECTOR */
.dir-wrap {
margin-bottom: 10px
}
.dir-lbl {
font-size: 9px;
letter-spacing: 2px;
color: var(--txt2);
margin-bottom: 5px
}
.dir-btns {
display: flex;
gap: 6px;
flex-wrap: wrap
}
.dir-btn {
padding: 5px 14px;
border-radius: 4px;
border: 1px solid var(--bd);
background: var(--s2);
color: var(--txt2);
font-family: monospace;
font-size: 11px;
font-weight: 700;
cursor: pointer;
letter-spacing: 1px;
transition: .15s
}
.dir-btn:hover {
border-color: var(--head);
color: var(--head)
}
.dir-btn.on.both {
border-color: var(--g);
color: var(--g);
background: rgba(0, 212, 126, .08)
}
.dir-btn.on.mum {
border-color: var(--mum);
color: var(--mum);
background: rgba(245, 166, 35, .08)
}
.dir-btn.on.kol {
border-color: var(--kol);
color: var(--kol);
background: rgba(155, 89, 255, .08)
}
/* SLIPPAGE */
.slrow {
display: flex;
align-items: center;
gap: 5px;
flex-wrap: wrap;
margin-bottom: 10px
}
.sllbl {
font-size: 10px;
color: var(--txt2);
white-space: nowrap
}
.slbtn {
padding: 3px 8px;
border-radius: 3px;
border: 1px solid var(--bd);
background: var(--s2);
color: var(--txt2);
font-family: monospace;
font-size: 10px;
cursor: pointer;
transition: .15s
}
.slbtn:hover,
.slbtn.on {
border-color: var(--y);
color: var(--y);
background: rgba(255, 212, 74, .07)
}
/* DEPLOY BAR */
.dbar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px
}
.dbg {
flex: 1;
height: 4px;
background: var(--bd);
border-radius: 2px;
overflow: hidden
}
.dbf {
height: 100%;
border-radius: 2px;
background: linear-gradient(90deg, var(--p), var(--o));
transition: .4s
}
/* RESULTS */
.rt {
width: 100%;
border-collapse: collapse;
font-size: 11px
}
.rt th {
padding: 5px 8px;
font-size: 9px;
letter-spacing: 1px;
color: var(--txt2);
text-align: right;
border-bottom: 1px solid var(--bd);
font-weight: 400;
background: var(--s2);
white-space: nowrap
}
.rt th:first-child {
text-align: left
}
.rt td {
padding: 6px 8px;
text-align: right;
border-bottom: 1px solid rgba(26, 37, 64, .4);
white-space: nowrap
}
.rt td:first-child {
text-align: left;
color: var(--txt2);
font-size: 10px
}
.rt tr.mx {
background: rgba(0, 212, 126, .06);
border-left: 2px solid var(--g)
}
.rt tr.mx td:first-child {
color: var(--g);
font-weight: 700
}
.rt tr:hover td {
background: rgba(255, 255, 255, .015)
}
.pos {
color: var(--g2) !important;
font-weight: 700
}
.neg {
color: var(--r) !important;
font-weight: 700
}
.hl {
color: var(--head);
font-weight: 700
}
.ow {
overflow-x: auto
}
/* SUMMARY CARDS */
.smg {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 6px;
margin-top: 12px
}
.smc {
background: var(--s2);
border-radius: 4px;
padding: 8px 10px;
border: 1px solid var(--bd)
}
.sml {
font-size: 9px;
letter-spacing: 1px;
color: var(--txt2);
margin-bottom: 3px
}
.smv {
font-family: sans-serif;
font-size: 22px;
font-weight: 900;
color: var(--head);
line-height: 1
}
.sms {
font-size: 9px;
color: var(--txt2);
margin-top: 2px
}
/* ── CHART ── */
.chwrap {
padding: 12px 14px
}
.ch-hdr {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
flex-wrap: wrap;
gap: 6px
}
.ch-leg {
display: flex;
gap: 12px
}
.ch-li {
display: flex;
align-items: center;
gap: 5px;
font-size: 10px
}
.ch-dot {
width: 8px;
height: 8px;
border-radius: 50%
}
canvas {
max-height: 220px
}
/* ── SCREEN FLASH ── */
.flash-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 212, 126, .08);
z-index: 400;
pointer-events: none;
animation: flashFade .8s ease-out forwards
}
@keyframes flashFade {
0% {
opacity: 1
}
100% {
opacity: 0
}
}
/* ── MISC ── */
.nodata {
padding: 20px;
text-align: center;
color: var(--txt2)
}
footer {
padding: 8px 14px;
background: var(--s1);
border-top: 1px solid var(--bd);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 4px;
font-size: 9px;
color: var(--txt2);
letter-spacing: 1px
}
@media(max-width:500px) {
.rt th,
.rt td {
padding: 4px 5px;
font-size: 10px
}
.f input,
.f select {
width: 110px
}
.ci .f input {
width: 110px
}
}
.calc-panels {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 20px;
}
@media(max-width: 600px) {
.calc-panels {
grid-template-columns: 1fr;
}
}
.calc-panel {
background: #0f121b;
border: 1px solid var(--bd);
border-radius: 6px;
padding: 12px 16px;
}
.cp-tit {
font-family: monospace;
font-size: 10px;
color: #7b859a;
letter-spacing: 1px;
margin-bottom: 12px;
text-transform: uppercase;
display: flex;
justify-content: space-between;
}
.cp-cols {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.cp-col {
display: flex;
flex-direction: column;
}
.cp-lbl {
font-size: 9px;
color: #5d6b82;
margin-bottom: 5px;
letter-spacing: 1px;
}
.cp-input {
background: #090b10;
border: 1px solid var(--bd);
color: var(--head);
font-family: monospace;
font-size: 14px;
font-weight: 700;
padding: 6px 10px;
border-radius: 4px;
outline: none;
transition: 0.15s;
width: 100%;
box-sizing: border-box;
}
.cp-input:focus {
border-color: var(--p);
}
.cp-sub {
margin-top: 6px;
font-size: 9px;
font-weight: 700;
min-height: 12px;
}
.cp-up {
color: var(--g);
}
.cp-dn {
color: var(--r);
}
.arb-toggle {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.arb-toggle-lbl {
font-size: 10px;
color: #7b859a;
letter-spacing: 1px;
text-transform: uppercase;
}
.arb-btn {
background: var(--bg);
border: 1px solid var(--bd);
color: var(--txt2);
padding: 5px 12px;
font-family: monospace;
font-size: 10px;
font-weight: 700;
border-radius: 4px;
cursor: pointer;
letter-spacing: 1px;
transition: 0.15s;
}
.arb-btn.on.m {
border-color: var(--mum);
color: var(--mum);
background: rgba(245, 166, 35, 0.08);
}
.arb-btn.on.k {
border-color: var(--kol);
color: var(--kol);
background: rgba(155, 89, 255, 0.08);
}
.w-alloc {
background: #0f121b;
border: 1px solid var(--bd);
border-radius: 6px;
padding: 12px;
margin-bottom: 15px;
border-color: rgba(255, 255, 255, 0.05);
}
.wa-tit {
font-size: 10px;
color: #7b859a;
letter-spacing: 1px;
margin-bottom: 8px;
text-transform: uppercase;
}
.wa-bar-wrap {
height: 10px;
background: var(--bg);
border-radius: 4px;
display: flex;
overflow: hidden;
margin-bottom: 8px;
}
.wa-bhav {
background: #ff8a60;
transition: 0.4s;
}
.wa-poly {
background: #6c63ff;
transition: 0.4s;
}
.wa-free {
background: #243050;
transition: 0.4s;
}
.wa-leg {
display: flex;
justify-content: space-between;
font-size: 9px;
color: var(--txt2);
}
.wa-leg-left {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.wa-item {
display: flex;
align-items: center;
gap: 4px;
}
.wa-dot {
width: 8px;
height: 8px;
border-radius: 2px;
}
.wa-item.b .wa-dot {
background: #ff8a60;
}
.wa-item.p .wa-dot {
background: #6c63ff;
}
.wa-item.f .wa-dot {
background: #243050;
}
.wa-item span {
font-weight: 700;
}
.wa-item.b span {
color: #ff8a60;
}
.wa-item.p span {
color: #9b95ff;
}
.wa-roi {
color: var(--y);
font-weight: 700;
font-size: 10px;
}
.rth {
background: #0f121b !important;
}
.rth-g {
font-size: 9px;
text-align: center !important;
padding: 6px !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
letter-spacing: 1px;
color: #7b859a;
}
.rth-gw {
background: #0f121b;
color: var(--g) !important;
}
/* ── ARB LIVE BOX ── */
.arb-box {
display: flex;
align-items: stretch;
background: var(--s1);
border: 1px solid var(--bd);
border-radius: 6px;
margin: 8px 14px;
overflow: hidden;
min-height: 80px;
}
.arb-box-left {
flex: 1;
padding: 10px 14px;
border-right: 1px solid var(--bd);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
text-align: center;
}
.arb-box-mid {
width: 150px;
min-width: 130px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8px 6px;
background: var(--s2);
}
.arb-box-right {
flex: 1;
padding: 10px 14px;
border-left: 1px solid var(--bd);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
text-align: center;
}
.arb-box-label {
font-size: 9px;
letter-spacing: 2px;
color: var(--txt2);
text-transform: uppercase;
}
.arb-box-team {
font-size: 11px;
color: var(--head);
font-weight: 700;
letter-spacing: 1px;
}
.arb-box-big {
font-size: 28px;
font-weight: 900;
font-family: sans-serif;
line-height: 1;
color: var(--head);
}
.arb-box-sub {
font-size: 11px;
color: var(--txt2);
margin-top: 2px;
}
.arb-box-meta {
margin-top: 4px;
font-size: 10px;
color: var(--txt2);
text-align: center;
line-height: 1.35;
}
.arb-alt-list {
margin-top: 10px;
display: grid;
gap: 6px;
}
.arb-alt-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
padding: 6px 8px;
border: 1px solid var(--bd2);
border-radius: 6px;
background: rgba(26, 37, 64, .22);
font-size: 10px;
color: var(--txt2);
}
.arb-alt-item b {
color: var(--txt);
font-size: 11px;
}
.arb-alt-pos {
color: var(--g);
}
.arb-alt-neg {
color: var(--r);
}
.arb-pct {
font-size: 20px;
font-weight: 900;
font-family: sans-serif;
line-height: 1;
max-width: 100%;
text-align: center;
}
.arb-pct.arb-yes {
color: var(--g);
}
.arb-pct.arb-no {
color: var(--r);
}
.arb-pct-lbl {
font-size: 9px;
letter-spacing: 2px;
margin-top: 4px;
color: var(--txt2);
}
.arb-pct-lbl.arb-yes {
color: var(--g);
}
.arb-pct-lbl.arb-no {
color: var(--r);
}
.arb-box-cents {
font-size: 22px;
font-weight: 900;
color: var(--p);
font-family: sans-serif;
}
/* ── POLY TEAM TABS ── */
.poly-tab {
flex: 1;
padding: 4px 8px;
border-radius: 3px;
border: 1px solid var(--bd);
background: var(--s2);
color: var(--txt2);
font-family: monospace;
font-size: 10px;
font-weight: 700;
letter-spacing: 1px;
cursor: pointer;
transition: .15s;
text-transform: uppercase;
}
.poly-tab:hover {
border-color: var(--p);
color: var(--p);
}
.poly-tab.on {
border-color: var(--p);
color: var(--p);
background: rgba(108, 99, 255, .12);
}
</style>
</head>
<body>
<!-- FLASH OVERLAY -->
<div class="flash-overlay" id="flashOv"></div>
<!-- HEADER -->
<div class="hdr">
<div class="logo"><span class="lp">POLY</span>Γ—<span class="lo">BHAV</span><small>IPL ARB v4</small></div>
<div class="hr">
<span class="live"><span class="dot"></span>LIVE</span>
<span style="font-size:10px;color:var(--txt2)">CALLS:<span id="ncall">0</span></span>
<!-- Navigation Buttons -->
<div style="display:flex; gap:6px; margin-right:10px; border-right:1px solid var(--bd); padding-right:10px; position: relative; z-index: 300;">
<a href="/login" target="_blank" class="hbtn nav-link" style="text-decoration:none; color:var(--y); border-color:var(--y)">πŸ”‘ LOGIN</a>
<a href="/save-session" target="_blank" class="hbtn nav-link" style="text-decoration:none; color:var(--g); border-color:var(--g)">πŸ’Ύ SAVE SESSION</a>
<a href="/debug" target="_blank" class="hbtn nav-link" style="text-decoration:none; color:var(--p); border-color:var(--p)">πŸͺ² DEBUG</a>
</div>
<button class="hbtn" onclick="toggleStg()">βš™ SETTINGS</button>
<button class="hbtn" onclick="refreshAll()">⟳ REFRESH</button>
</div>
</div>
<!-- SETTINGS -->
<div class="stw">
<div class="sthd" onclick="toggleStg()">
<div>
<div class="sthl">βš™ SETTINGS</div>
<div class="stsub">Match:<b id="sbm">MI vs KKR</b> Β· Capital:<b id="sbc">β‚Ή1,00,000</b> Β· INR:<b
id="sbi">β‚Ή84</b> Β· Fallback Poll:<b id="sbr">5s</b> Β· MinGap:<b id="sbg">2.0%</b> Β· Alert
Hold:<b id="sah">6s</b></div>
</div>
<span id="sarr" style="font-size:10px;color:var(--txt2)">β–²</span>
</div>
<div class="stbd open" id="stbd">
<div class="sg">
<div class="slbl">MATCH</div>
<div class="f"><label>Match Name</label><input id="sMatch" value="MI vs KKR" oninput="onMN()" /></div>
<div class="f"><label id="sT1Lbl">Team 1 (MUM side)</label><input id="sT1" value="Mumbai Indians" oninput="onT()" />
</div>
<div class="f"><label id="sT2Lbl">Team 2 (KOL side)</label><input id="sT2" value="Kolkata Knight Riders"
oninput="onT()" /></div>
<div class="sdiv"></div>
<div class="slbl">DATA SOURCES</div>
<div class="f w"><label>Polymarket URL / Condition ID</label><input id="sPolyUrl"
placeholder="https://polymarket.com/event/... or token ID" /></div>
<div class="f w"><label>Rebel777 Match URL <span style="color:var(--g);font-size:9px">●
LIVE</span></label><input id="sRebel777Url" placeholder="https://rebel777.co/sport/game/..."
oninput="onRebel777UrlChange()" /></div>
<div class="f"><label>Fallback Poll</label>
<select id="sRef" onchange="onRC()">
<option value="2">2s</option>
<option value="3">3s</option>
<option value="5" selected>5s</option>
<option value="10">10s</option>
<option value="15">15s</option>
<option value="0">Off</option>
</select>
</div>
<div>
<button class="abtn abp" onclick="fetchPoly()" style="margin-right:6px">⬑ LOAD POLY</button>
<button class="abtn" onclick="fetchRebel777()"
style="background:#1a7a4a;color:#fff;margin-right:6px">⚑ START REBEL777</button>
<button class="abtn" onclick="stopRebel777()"
style="background:#3a1a1a;color:#ff6060;border:1px solid #5a2a2a;margin-right:6px">β– 
STOP</button>
<div style="font-size:9px;color:var(--txt2);margin-top:4px">Session calls: <span id="ncall2"
style="color:var(--p)">0</span></div>
</div>
<div class="sdiv"></div>
<div class="slbl">CAPITAL & RATES</div>
<div class="f"><label>Capital (β‚Ή)</label><input type="number" id="sCap" value="100000" step="5000"
oninput="onCap()" /></div>
<div class="f"><label>Fixed Rebel Stake (β‚Ή)</label><input type="number" id="sRebelStake"
value="20000" step="1000" oninput="onRebelStake()" /></div>
<div class="f"><label>Bhav Win Step (β‚Ή)</label><input type="number" id="sStep" value="5000" step="1000"
oninput="onStep()" /></div>
<div class="f"><label>USD β†’ INR (β‚Ή)</label><input type="number" id="sInr" value="84" step="0.5"
oninput="onInr()" /></div>
<div class="sdiv"></div>
<div class="slbl">ARB ALERTS</div>
<div class="f"><label>Min Gap Threshold</label><input type="number" id="sMinGap" value="2.0" step="0.5"
oninput="onMG()" /></div>
<div class="f"><label>Alert Hold (s)</label><input type="number" id="sAlertHold" value="6" min="1"
step="1" oninput="onAH()" /></div>
<div class="f w"><label>Telegram Bot Token</label><input id="sTgToken"
placeholder="123456:ABC-DEF..." /></div>
<div class="f"><label>Telegram Chat ID</label><input id="sTgChat" placeholder="-1001234567890" /></div>
<div class="f"><label>Sound Alert</label>
<select id="sSnd">
<option value="1">On</option>
<option value="0">Off</option>
</select>
</div>
</div>
</div>
</div>
<!-- MATCH BAR -->
<div class="mbar">
<div class="pills">
<div class="mt" id="mTitle">MI vs KKR</div>
<div class="pill pm"><span id="mumLbl">MUM</span> <span id="mumO">β€”</span></div>
<div class="pill pk"><span id="kolLbl">KOL</span> <span id="kolO">β€”</span></div>
</div>
<div class="mr">1 USD=<span id="inrD">β‚Ή84</span> Β· Updated:<span id="tsD">β€”</span></div>
</div>
<!-- LIVE ARB BOX -->
<div class="arb-box" id="arbBox">
<div class="arb-box-left">
<div class="arb-box-label">POLYMARKET</div>
<div class="arb-box-team" id="abPolyTeam">β€”</div>
<div class="arb-box-big" id="abPolyShares">β€”</div>
<div class="arb-box-sub" id="abPolySharesLbl">net shares after fee</div>
<div class="arb-box-meta" id="abPolyGross">gross β€” Β· fee β€”</div>
<div class="arb-box-cents" id="abPolyCents">β€”</div>
<div class="arb-box-sub">best odds (Β’)</div>
</div>
<div class="arb-box-mid">
<div class="arb-pct arb-no" id="abPct">β€”</div>
<div class="arb-pct-lbl arb-no" id="abPctLbl">NO ARB</div>
</div>
<div class="arb-box-right">
<div class="arb-box-label">REBEL777 LAY</div>
<div class="arb-box-team" id="abRebelTeam">β€”</div>
<div class="arb-box-big" id="abRebelOdds">β€”</div>
<div class="arb-box-sub">best lay odds</div>
<div style="font-size:11px;color:var(--txt2);margin-top:4px">Stake: <span
style="color:#0f0;font-size:13px;font-weight:700" id="abRebelLayStake">β‚Ή20,000</span></div>
<div style="font-size:11px;color:var(--txt2);margin-top:2px">Liability: <span
style="color:var(--y);font-size:13px;font-weight:700" id="abRebelStake">β€”</span></div>
<div class="arb-alt-list">
<div class="arb-alt-item" id="abLayAlt1">L1 β€”</div>
<div class="arb-alt-item" id="abLayAlt2">L2 β€”</div>
</div>
</div>
</div>
<!-- TABS -->
<div class="tabs">
<div class="tab on" onclick="swTab(this,'ob')">ORDER BOOKS</div>
<div class="tab" onclick="swTab(this,'calc')">ARB CALCULATOR</div>
<div class="tab" onclick="swTab(this,'chart')">ODDS CHART</div>
</div>
<!-- ORDER BOOKS TAB -->
<div class="tc on" id="tc-ob">
<div class="obg">
<div class="obp">
<div class="obh" style="flex-direction:column;align-items:stretch;gap:6px;padding-bottom:8px">
<div style="display:flex;align-items:center;justify-content:space-between">
<span class="obt p">POLYMARKET</span>
<span class="bdg bdg-p" id="pSt">NOT LOADED</span>
</div>
<div style="display:flex;gap:6px" id="polyTeamTabs">
<button class="poly-tab on" id="polyTab0" onclick="switchPolyTab(0, true)">β€”</button>
<button class="poly-tab" id="polyTab1" onclick="switchPolyTab(1, true)">β€”</button>
</div>
</div>
<div class="pmeta">LAST:<span id="pLast">β€”</span> SPREAD:<span id="pSprd">β€”</span> VOL:<span
id="pVol">β€”</span></div>
<table>
<thead>
<tr>
<th>SIDE</th>
<th>ODDS (Β’)</th>
<th>SHARES</th>
<th>$ TOTAL</th>
</tr>
</thead>
<tbody id="pBook">
<tr>
<td colspan="4">
<div class="nodata">Load Polymarket URL</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="obp">
<div class="obh"><span class="obt b">REBEL777 RATES</span><span class="bdg bdg-b" id="bSt">NOT
LOADED</span>
</div>
<div class="bhdr">
<div class="bh bk">BACK</div>
<div class="bh bk">BACK</div>
<div class="bh bk">BACKβ˜…</div>
<div class="bh lk">LAYβ˜…</div>
<div class="bh lk">LAY</div>
<div class="bh lk">LAY</div>
</div>
<div id="bBook">
<div class="nodata">Paste Rebel777 match URL β†’ START</div>
</div>
</div>
</div>
</div>
<!-- CALCULATOR TAB -->
<div class="tc" id="tc-calc">
<div class="cw">
<div class="ctit">ARB CALCULATOR</div>
<!-- RATE INPUTS -->
<!-- RATE INPUTS -->
<div class="calc-panels">
<div class="calc-panel">
<div class="cp-tit">POLYMARKET ASK <span
style="text-transform:none;opacity:0.6;font-weight:400">(cheapest to buy)</span></div>
<div class="cp-cols">
<div class="cp-col">
<label class="cp-lbl"><span id="cpL1">MUM</span> YES ASK</label>
<input type="number" id="cP1" class="cp-input" value="2.18" step="0.01"
oninput="onCalcManualInput()" />
<div class="cp-sub" id="cpS1"></div>
</div>
<div class="cp-col">
<label class="cp-lbl"><span id="cpL2">KOL</span> YES ASK</label>
<input type="number" id="cP2" class="cp-input" value="1.82" step="0.01"
oninput="onCalcManualInput()" />
<div class="cp-sub" id="cpS2"></div>
</div>
</div>
</div>
<div class="calc-panel">
<div class="cp-tit">BHAV / BETFAIR LAY odds</div>
<div class="cp-cols">
<div class="cp-col">
<label class="cp-lbl"><span id="cbL1">MUM</span> LAY</label>
<input type="number" id="cB1" class="cp-input" value="2.00" step="0.01"
oninput="onCalcManualInput()" />
<div class="cp-sub" id="cbS1"></div>
</div>
<div class="cp-col">
<label class="cp-lbl"><span id="cbL2">KOL</span> LAY</label>
<input type="number" id="cB2" class="cp-input" value="2.00" step="0.01"
oninput="onCalcManualInput()" />
<div class="cp-sub" id="cbS2"></div>
</div>
</div>
</div>
</div>
<!-- WALLET ALLOCATION -->
<div class="w-alloc">
<div class="wa-tit" style="display:flex;justify-content:space-between">
<span>WALLET ALLOCATION <span style="text-transform:none;opacity:0.6">(best row ⭐)</span></span>
<span class="wa-roi" id="waRoi">Guaranteed ROI: +0.00%</span>
</div>
<div class="wa-bar-wrap">
<div class="wa-bhav" id="waBhav" style="width:0%"></div>
<div class="wa-poly" id="waPoly" style="width:0%"></div>
<div class="wa-free" id="waFree" style="width:100%"></div>
</div>
<div class="wa-leg">
<div class="wa-leg-left">
<div class="wa-item b">
<div class="wa-dot"></div><span id="waBhavT">Bhav: β‚Ή0 (0.0%)</span>
</div>
<div class="wa-item p">
<div class="wa-dot"></div><span id="waPolyT">Poly: β‚Ή0 = $0 (0.0%)</span>
</div>
<div class="wa-item f">
<div class="wa-dot"></div><span id="waFreeT">Free: β‚Ή100,000 (100.0%)</span>
</div>
</div>
</div>
</div>
<!-- RESULTS TABLE -->
<div class="ow">
<table class="rt">
<thead>
<tr>
<th colspan="6" class="rth-g rth-gw" id="btnBoth" onclick="setMode('both')"
style="cursor:pointer; transition:0.2s; user-select:none">BOTH OUTCOMES</th>
<th colspan="1" class="rth-g" id="btnM1" onclick="setMode('m1')"
style="cursor:pointer; border-left:1px solid #1a2540; transition:0.2s; user-select:none">
MUM WINS
</th>
<th colspan="3" class="rth-g" id="btnM2" onclick="setMode('m2')"
style="cursor:pointer; border-left:1px solid #1a2540; transition:0.2s; user-select:none">
KOL WINS
</th>
</tr>
<tr>
<th class="rth">BHAV RETURN IF LAY WINS (β‚Ή)</th>
<th class="rth">BHAV LIABILITY (β‚Ή)</th>
<th class="rth">POLY BUY ($ / Β’)</th>
<th class="rth">POLY SHARES (GROSS β†’ NET)</th>
<th class="rth">TOTAL DEPLOYED (β‚Ή)</th>
<th class="rth">UNUSED CAPITAL (β‚Ή)</th>
<th class="rth" id="thWin">IF MUM WINS</th>
<th class="rth" id="thLose">IF MUM LOSES</th>
<th class="rth" id="thMinP">GUARANTEED PROFIT</th>
<th class="rth">ROI ON DEPLOYED %</th>
</tr>
</thead>
<tbody id="calcTb">
<tr>
<td colspan="10">
<div class="nodata">Loading calculator...</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- CHART TAB -->
<div class="tc" id="tc-chart">
<div class="chwrap">
<div class="ch-hdr">
<div style="font-family:sans-serif;font-size:14px;font-weight:700;color:var(--head)">ODDS HISTORY β€”
Since Page Load</div>
<div class="ch-leg">
<div class="ch-li">
<div class="ch-dot" style="background:var(--p)"></div><span style="color:var(--txt2)">Poly
YES</span>
</div>
<div class="ch-li">
<div class="ch-dot" style="background:var(--lk)"></div><span style="color:var(--txt2)">Bhav
LAY</span>
</div>
<div class="ch-li">
<div class="ch-dot" style="background:var(--g)"></div><span style="color:var(--txt2)">Gap
%</span>
</div>
</div>
</div>
<canvas id="oddsChart"></canvas>
<div style="margin-top:8px;font-size:10px;color:var(--txt2);text-align:center" id="chartInfo">No data yet β€”
load rates to start tracking</div>
</div>
</div>
<footer>
<span>POLYΓ—BHAV ARB v4</span>
<span>⚠ OBSERVATION ONLY β€” NOT FINANCIAL ADVICE</span>
<span id="ts">β€”</span>
</footer>
<script>
// ── STATE ──
let sl = 0, ncalls = 0, dir = 'both', rtmr = null;
let polyPrice = 2.00, bhavLay = 1.82;
let _polyPrices = [null, null]; // decimal odds for [team1, team2] on Polymarket
let chartData = { labels: [], poly: [], bhav: [], gap: [] };
let myChart = null;
let lastGap = 0;
let manualCalcOverrideUntil = 0;
const MANUAL_CALC_HOLD_MS = 10000;
let arbHoldTimer = null;
let arbHoldStartedAt = 0;
let arbHoldKey = '';
let arbHoldMs = 6000;
let arbHoldCandidate = null;
let arbAlertSentKey = '';
const $ = id => document.getElementById(id);
const fmt = n => Math.round(n).toLocaleString('en-IN');
const fu = n => parseFloat(n).toFixed(2);
const inrR = () => parseFloat($('sInr').value) || 84;
const minGap = () => parseFloat($('sMinGap').value) || 2.0;
const alertHoldSeconds = () => Math.max(1, parseInt($('sAlertHold')?.value || '6', 10) || 6);
const t1 = () => $('sT1').value || 'Mumbai Indians';
const t2 = () => $('sT2').value || 'Kolkata Knight Riders';
const t1s = () => t1().split(' ')[0].toUpperCase();
const t2s = () => t2().split(' ')[0].toUpperCase();
function teamNameAt(idx) {
const fallback = idx === 0 ? t1() : t2();
const polyName = _polyTeamNames?.[idx];
return polyName && polyName !== 'β€”' ? polyName : fallback;
}
function teamShortLabel(name) {
const clean = (name || '').trim();
if (!clean) return 'β€”';
const parts = clean.split(/\s+/).filter(Boolean);
if (parts.length >= 2) return parts.slice(0, 3).map(p => p[0]).join('').toUpperCase();
return clean.slice(0, 3).toUpperCase();
}
function syncTeamLabels() {
const team1 = teamNameAt(0);
const team2 = teamNameAt(1);
const short1 = teamShortLabel(team1);
const short2 = teamShortLabel(team2);
if ($('mumLbl')) $('mumLbl').textContent = short1;
if ($('kolLbl')) $('kolLbl').textContent = short2;
if ($('sT1Lbl')) $('sT1Lbl').textContent = `Team 1 (${short1} side)`;
if ($('sT2Lbl')) $('sT2Lbl').textContent = `Team 2 (${short2} side)`;
if ($('cpL1')) $('cpL1').textContent = short1;
if ($('cpL2')) $('cpL2').textContent = short2;
if ($('cbL1')) $('cbL1').textContent = short1;
if ($('cbL2')) $('cbL2').textContent = short2;
if ($('btnM1')) $('btnM1').textContent = `${team1.toUpperCase()} WINS`;
if ($('btnM2')) $('btnM2').textContent = `${team2.toUpperCase()} WINS`;
}
function getPolyFeeRate() {
return 0.03;
}
function getPolyBuyFeeFromOdds(p, grossUsd) {
if (!p || p <= 1 || !grossUsd || grossUsd <= 0) return { cents: 0, feeRate: getPolyFeeRate(), feeUsd: 0, feeShares: 0 };
const priceUsd = 1 / p;
const feeRate = getPolyFeeRate();
const feeUsd = grossUsd * feeRate * Math.max(0, 1 - priceUsd);
const feeShares = priceUsd > 0 ? feeUsd / priceUsd : 0;
return {
cents: Math.round(priceUsd * 100),
feeRate,
feeUsd,
feeShares
};
}
function calcArbRows(p, b, cap, inr, mode = 'both') {
const rows = [];
const fixedLayStake = parseFloat($('sRebelStake').value) || 20000;
if (!p || !b || !cap || !fixedLayStake || p <= 1 || b <= 1) return rows;
const priceUsd = 1 / p;
const feeModel = getPolyBuyFeeFromOdds(p, 1);
const polyCents = feeModel.cents;
const feeRate = feeModel.feeRate;
const netMultiplier = 1 - feeRate * Math.max(0, 1 - priceUsd);
const effectivePolyOdds = p * netMultiplier;
if (effectivePolyOdds <= 1) return rows;
const w = fixedLayStake;
const bs = w * (b - 1);
let poly_inr, profW, profL;
if (mode === 'W') {
poly_inr = w;
} else if (mode === 'L') {
poly_inr = bs / (effectivePolyOdds - 1);
} else if (mode === 'both') {
poly_inr = (w * b) / effectivePolyOdds;
} else if (typeof mode === 'number') {
// For slider: interpolate between L (min) and W (max)
const min_poly = bs / (effectivePolyOdds - 1);
const max_poly = w;
// Mode is 0..1 where 1 represents W (Team wins = max profit) and 0 represents L
poly_inr = min_poly + mode * (max_poly - min_poly);
}
const poly_usd = poly_inr / inr;
const grossShares = poly_usd * p;
const feeBreakup = getPolyBuyFeeFromOdds(p, poly_usd);
const feeUsd = feeBreakup.feeUsd;
const feeShares = feeBreakup.feeShares;
const netShares = Math.max(0, grossShares - feeShares);
const winPayoutInr = netShares * inr;
profW = winPayoutInr - poly_inr - bs;
profL = w - poly_inr;
const dep = bs + poly_inr;
if (dep > cap) return rows;
const left = cap - dep;
const minProf = Math.min(profW, profL);
const tgtProf = mode === 'both' ? minProf : Math.max(profW, profL);
const roi = dep > 0 ? (tgtProf / dep) * 100 : 0;
rows.push({
w,
bs,
pu: poly_usd,
ps: poly_inr,
dep,
left,
profW,
profL,
min: minProf,
tgtProf,
roi,
sh: Math.round(netShares),
grossSh: grossShares,
netSh: netShares,
feeUsd,
feeSh: feeShares,
feeRate,
cents: polyCents
});
return rows;
}
function fmtAltLay(level, p, cap, inr) {
if (!level || !p || p <= 1) return 'β€”';
const row = calcArbRows(p, level, cap, inr, 'both')[0];
if (!row) return `${level.toFixed(2)} Β· over cap`;
const roiCls = row.roi > 0 ? 'arb-alt-pos' : 'arb-alt-neg';
const roiTxt = `${row.roi > 0 ? '+' : ''}${row.roi.toFixed(1)}%`;
const profTxt = `${row.min > 0 ? '+' : ''}β‚Ή${fmt(Math.abs(row.min))}`;
return `<span><b>${level.toFixed(2)}</b> Β· ${fu(row.netSh)} net Β· ${profTxt}</span><span class="${roiCls}">${roiTxt}</span>`;
}
function isManualCalcActive() {
return Date.now() < manualCalcOverrideUntil;
}
function getCalcPairsFromInputs() {
return {
polyPrices: [
parseFloat($('cP1')?.value || 0) || null,
parseFloat($('cP2')?.value || 0) || null
],
rebelLays: [
parseFloat($('cB1')?.value || 0) || null,
parseFloat($('cB2')?.value || 0) || null
]
};
}
function pickBestArbPairFromValues(polyPrices, rebelLays, fallbackIdx = _polyActiveTab) {
const cap = parseFloat($('sCap').value) || 100000;
const inr = inrR();
let best = null;
polyPrices.forEach((p, i) => {
const b = rebelLays[i];
if (!p || !b || p <= 1 || b <= 1) return;
const rows = calcArbRows(p, b, cap, inr, 'both');
if (!rows.length) return;
const positiveRows = rows.filter(r => r.tgtProf > 0);
const bestRow = positiveRows.length ? positiveRows[positiveRows.length - 1] : rows[rows.length - 1];
if (!best || bestRow.roi > best.row.roi) {
best = { idx: i, p, b, row: bestRow, rows };
}
});
if (!best) {
const p = polyPrices[fallbackIdx];
const b = rebelLays[fallbackIdx];
if (p > 1 && b > 1) {
const rows = calcArbRows(p, b, cap, inr, 'both');
if (rows.length) best = { idx: fallbackIdx, p, b, row: rows[rows.length - 1], rows };
}
}
return best;
}
function pickBestArbPair() {
const rebelLays = [window._r0Lay || null, window._r1Lay || null];
return pickBestArbPairFromValues(_polyPrices, rebelLays, _polyActiveTab);
}
// ── CLOCK ──
setInterval(() => { const t = new Date().toLocaleTimeString('en-IN'); $('ts').textContent = t; $('tsD').textContent = t; }, 1000);
// ── SETTINGS TOGGLE ──
function toggleStg() {
const b = $('stbd'), open = b.classList.contains('open');
b.classList.toggle('open', !open);
$('sarr').textContent = open ? 'β–²' : 'β–Ό';
}
// ── SETTINGS HANDLERS ──
function onMN() { const v = $('sMatch').value; $('mTitle').textContent = v; $('sbm').textContent = v; }
let calcMode = 'both';
function setMode(m) {
calcMode = m;
$('btnBoth').className = 'rth-g' + (m === 'both' ? ' rth-gw' : '');
$('btnM1').className = 'rth-g' + (m === 'm1' ? ' rth-gw' : '');
$('btnM2').className = 'rth-g' + (m === 'm2' ? ' rth-gw' : '');
recalc();
}
function updateSubtexts() {
if (!$('cP1')) return;
const p1 = parseFloat($('cP1').value), b1 = parseFloat($('cB1').value);
const p2 = parseFloat($('cP2').value), b2 = parseFloat($('cB2').value);
const u = (cp, cb, p, b) => {
const g = p - b;
if (g > 0) {
$(cp).innerHTML = `<span class="cp-up">β–² +${g.toFixed(3)} vs Bhav LAY</span>`;
$(cb).innerHTML = `<span class="cp-up">LAY &lt; Poly βœ“</span>`;
} else {
$(cp).innerHTML = `<span class="cp-dn">β–Ό ${g.toFixed(3)}</span>`;
$(cb).innerHTML = `<span class="cp-dn">LAY &gt; Poly βœ—</span>`;
}
};
if (p1 && b1) u('cpS1', 'cbS1', p1, b1);
if (p2 && b2) u('cpS2', 'cbS2', p2, b2);
}
function updateTopPolyBadges() {
if ($('mumO')) $('mumO').textContent = _polyPrices[0] ? _polyPrices[0].toFixed(3) : 'β€”';
if ($('kolO')) $('kolO').textContent = _polyPrices[1] ? _polyPrices[1].toFixed(3) : 'β€”';
syncTeamLabels();
}
function onCalcManualInput() {
manualCalcOverrideUntil = Date.now() + MANUAL_CALC_HOLD_MS;
recalc();
}
function onT() {
syncTeamLabels();
updateTopPolyBadges();
recalc();
}
function onCap() { const v = $('sCap').value; $('sbc').textContent = 'β‚Ή' + fmt(parseFloat(v)); recalc(); }
function onStep() { recalc(); }
function onInr() { const v = $('sInr').value; $('inrD').textContent = 'β‚Ή' + v; $('sbi').textContent = 'β‚Ή' + v; recalc(); }
function onMG() { $('sbg').textContent = $('sMinGap').value + '%'; recalc(); }
function onAH() { arbHoldMs = alertHoldSeconds() * 1000; if ($('sah')) $('sah').textContent = alertHoldSeconds() + 's'; }
function onRebelStake() {
const v = parseFloat($('sRebelStake').value) || 20000;
if ($('abRebelLayStake')) $('abRebelLayStake').textContent = 'β‚Ή' + v.toLocaleString('en-IN');
recalc();
}
// ── LIVE ARB BOX ──
// Called whenever poly or rebel odds update
function updateArbBox() {
const manualActive = isManualCalcActive();
const best = pickBestArbPair();
const bestTabIdx = best?.idx ?? _polyActiveTab;
const p = best?.p ?? null;
const b = best?.b ?? null;
const row = best?.row ?? null;
const arbPct = row ? row.roi : null;
const cap = parseFloat($('sCap').value) || 100000;
const inr = inrR();
const layLevels = bestTabIdx === 0 ? (window._r0LayLevels || []) : (window._r1LayLevels || []);
// Team name β€” from stored names or settings
const teamName = _polyTeamNames[bestTabIdx] || (bestTabIdx === 0 ? t1s() : t2s());
const rebelTeam = teamName;
const hasPositiveArb = row && row.roi > 0;
// Highlight/switch only when there is a real positive arbitrage.
if (hasPositiveArb && bestTabIdx !== null && bestTabIdx !== _polyActiveTab) {
document.querySelectorAll('.poly-tab').forEach((t, i) => {
t.style.borderColor = i === bestTabIdx ? 'var(--g)' : '';
t.style.color = i === bestTabIdx ? 'var(--g)' : '';
});
if (!_polyAutoSwitching && Date.now() >= _polyManualOverrideUntil) {
_polyAutoSwitching = true;
Promise.resolve(switchPolyTab(bestTabIdx))
.catch(() => { })
.finally(() => { _polyAutoSwitching = false; });
}
} else {
document.querySelectorAll('.poly-tab').forEach(t => { t.style.borderColor = ''; t.style.color = ''; });
}
if ($('abPolyTeam')) $('abPolyTeam').textContent = teamName + ' YES';
if ($('abRebelTeam')) $('abRebelTeam').textContent = rebelTeam + ' LAY';
if ($('abPolyShares')) $('abPolyShares').textContent = row ? fu(row.grossSh) : 'β€”';
if ($('abPolySharesLbl')) $('abPolySharesLbl').textContent = 'buy shares (gross)';
if ($('abPolyGross')) $('abPolyGross').textContent = row ? `net ${fu(row.netSh)} Β· fee ${fu(row.feeSh)} sh` : 'net β€” Β· fee β€”';
if ($('abPolyCents')) $('abPolyCents').textContent = row ? row.cents + 'Β’' : 'β€”';
if ($('abRebelOdds')) $('abRebelOdds').textContent = (b > 1) ? b.toFixed(2) : 'β€”';
if ($('abRebelLayStake')) $('abRebelLayStake').textContent = row ? 'β‚Ή' + row.w.toLocaleString('en-IN') : 'β€”';
if ($('abRebelStake')) $('abRebelStake').textContent = row ? 'β‚Ή' + Math.round(row.bs).toLocaleString('en-IN') : 'β€”';
if ($('abLayAlt1')) $('abLayAlt1').innerHTML = `L1 ${fmtAltLay(layLevels[0], p, cap, inr)}`;
if ($('abLayAlt2')) $('abLayAlt2').innerHTML = `L2 ${fmtAltLay(layLevels[1], p, cap, inr)}`;
const pctEl = $('abPct'), lblEl = $('abPctLbl');
if (pctEl && arbPct !== null) {
const isArb = arbPct > 0;
pctEl.textContent = (isArb ? '+' : '') + arbPct.toFixed(1) + '%';
pctEl.className = 'arb-pct ' + (isArb ? 'arb-yes' : 'arb-no');
lblEl.textContent = isArb ? 'ARB βœ“' : 'NO ARB';
lblEl.className = 'arb-pct-lbl ' + (isArb ? 'arb-yes' : 'arb-no');
}
}
function onRC() { const v = $('sRef').value; $('sbr').textContent = v === '0' ? 'Off' : v + 's'; startAR(); }
// ── TABS ──
function swTab(el, n) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('on'));
document.querySelectorAll('.tc').forEach(t => t.classList.remove('on'));
el.classList.add('on'); $('tc-' + n).classList.add('on');
}
// ── DIRECTION ──
function setDir(d, btn) {
dir = d;
document.querySelectorAll('.dir-btn').forEach(b => b.classList.remove('on'));
btn.classList.add('on');
recalc();
}
// ── SLIPPAGE ──
function setSl(v, btn) {
sl = v;
document.querySelectorAll('.slbtn').forEach(b => b.classList.remove('on'));
btn.classList.add('on');
recalc();
}
// ── ARB LOGIC + NOTIFICATIONS ──
function checkArb() {
const best = pickBestArbPair();
if (!best || !best.row) {
if (arbHoldTimer) { clearTimeout(arbHoldTimer); arbHoldTimer = null; }
arbHoldStartedAt = 0;
arbHoldKey = '';
arbHoldCandidate = null;
arbAlertSentKey = '';
lastGap = 0;
return;
}
const tgt = _polyTeamNames[best.idx] || (best.idx === 0 ? t1s() : t2s());
const roiPct = best.row.roi;
const holdKey = `${best.idx}:${tgt}`;
if (roiPct >= minGap()) {
arbHoldCandidate = { ...best, tgt, holdKey, roiPct };
if (arbHoldKey !== holdKey) {
arbHoldKey = holdKey;
arbHoldStartedAt = Date.now();
arbAlertSentKey = '';
}
if (arbHoldTimer) { clearTimeout(arbHoldTimer); arbHoldTimer = null; }
if (arbAlertSentKey !== holdKey && arbHoldStartedAt && (Date.now() - arbHoldStartedAt) >= arbHoldMs) {
arbAlertSentKey = holdKey;
triggerAlerts(arbHoldCandidate);
}
} else {
if (arbHoldTimer) { clearTimeout(arbHoldTimer); arbHoldTimer = null; }
arbHoldStartedAt = 0;
arbHoldKey = '';
arbHoldCandidate = null;
arbAlertSentKey = '';
}
lastGap = roiPct;
}
function triggerAlerts(candidate) {
if ($('sSnd').value === '1') playBeep();
triggerFlash();
sendTelegram(candidate);
}
function playBeep() {
try {
const ac = new (window.AudioContext || window.webkitAudioContext)();
if (!ac) return;
const osc = ac.createOscillator(), gain = ac.createGain();
osc.connect(gain); gain.connect(ac.destination);
osc.frequency.value = 880; osc.type = 'sine';
gain.gain.setValueAtTime(0.3, ac.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + 0.6);
osc.start(ac.currentTime); osc.stop(ac.currentTime + 0.6);
} catch (e) { }
}
function triggerFlash() {
const f = $('flashOv');
f.style.display = 'block'; f.style.animation = 'none';
requestAnimationFrame(() => { f.style.animation = 'flashFade .8s ease-out forwards'; });
setTimeout(() => f.style.display = 'none', 900);
}
async function sendTelegram(candidate) {
const token = $('sTgToken').value.trim(), chat = $('sTgChat').value.trim();
if (!token || !chat) {
console.warn('[TG] Missing bot token or chat id');
return;
}
const inr = inrR();
const cap = parseFloat($('sCap').value) || 100000;
const p = candidate?.p, b = candidate?.b, tgt = candidate?.tgt, row = candidate?.row, roiPct = candidate?.roiPct;
if (!p || !b || !row) return;
const grossShares = fu(row.grossSh);
const netShares = fu(row.netSh);
const prof = Math.max(0, row.min);
const msg = `⚑ IPL ARB ALERT\n\nMatch: ${$('sMatch').value}\nProfit: ${roiPct >= 0 ? '+' : ''}${roiPct.toFixed(2)}%\n\nπŸ“Š POLYMARKET\nBUY ${tgt} YES @ ${p.toFixed(3)}\nStake: $${fu(row.pu)} (β‚Ή${fmt(row.ps)})\nGross Shares: ${grossShares}\nNet Hedge Shares: ${netShares}\nFee: $${fu(row.feeUsd)} (${fu(row.feeSh)} sh)\n\nπŸ“ˆ BHAV\nLAY ${tgt} @ ${b.toFixed(3)}\nStake: β‚Ή${fmt(row.w)}\nLiability: β‚Ή${fmt(Math.round(row.bs))}\n\nβœ… EQUAL PROFIT: +β‚Ή${fmt(prof)}\nβš™οΈ Capital: β‚Ή${fmt(cap)} | INR: ${inr}`;
try {
const resp = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chat_id: chat, text: msg })
});
if (!resp.ok) {
const body = await resp.text().catch(() => '');
console.error('[TG] Send failed', resp.status, body);
return;
}
console.log('[TG] Alert sent');
} catch (e) {
console.error('[TG] Send error', e);
}
}
function recalc() {
updateSubtexts();
const p1 = parseFloat($('cP1').value || 0), b1 = parseFloat($('cB1').value || 0);
const p2 = parseFloat($('cP2').value || 0), b2 = parseFloat($('cB2').value || 0);
const cap = parseFloat($('sCap').value) || 100000;
const inr = inrR();
const manualActive = isManualCalcActive();
const best = manualActive
? pickBestArbPairFromValues([p1, p2], [b1, b2], _polyActiveTab)
: pickBestArbPair();
let currArb = best?.idx === 1 ? 2 : 1;
let p = best?.p ?? p1;
let b = best?.b ?? b1;
const mName = teamNameAt(0), kName = teamNameAt(1);
if ($('btnM1')) $('btnM1').textContent = `${mName.toUpperCase()} WINS`;
if ($('btnM2')) $('btnM2').textContent = `${kName.toUpperCase()} WINS`;
if (currArb === 1) {
$('thWin').textContent = `IF ${mName} WINS`;
$('thLose').textContent = `IF ${kName} WINS`;
} else {
$('thWin').textContent = `IF ${kName} WINS`;
$('thLose').textContent = `IF ${mName} WINS`;
}
if ($('thMinP')) $('thMinP').textContent = calcMode === 'both' ? 'GUARANTEED PROFIT' : 'TARGET PROFIT';
if (!p || !b || !cap || p <= 1) { $('calcTb').innerHTML = '<tr><td colspan="10"><div class="nodata">Capital too low or invalid odds</div></td></tr>'; return; }
polyPrice = p; bhavLay = b;
if (!manualActive) checkArb();
if (!manualActive) updateChart(p, b);
updateArbBox();
let targetMax = 'both';
if (calcMode === 'm1') targetMax = (currArb === 1) ? 'W' : 'L';
if (calcMode === 'm2') targetMax = (currArb === 1) ? 'L' : 'W';
const rows = calcArbRows(p, b, cap, inr, targetMax);
renderCalc(rows, p, b, cap, inr);
}
function renderCalc(rows, p, b, cap, inr) {
const tb = $('calcTb');
if (!rows.length) { tb.innerHTML = '<tr><td colspan="10"><div class="nodata">Capital too low or invalid rates</div></td></tr>'; return; }
const profRows = rows.filter(r => r.tgtProf > 0);
const mx = profRows.length ? profRows[profRows.length - 1] : rows[rows.length - 1];
const sorted = [mx, ...rows.filter(r => r !== mx).reverse()];
const pf = v => v > 0 ? `<span class="pos">+β‚Ή${fmt(v)}</span>` : v < 0 ? `<span class="neg">-β‚Ή${fmt(Math.abs(v))}</span>` : `<span style="color:var(--txt2)">β‚Ή0</span>`;
const pfR = v => v > 0 ? `<span class="pos">+${v.toFixed(2)}%</span>` : `<span class="neg">${v.toFixed(2)}%</span>`;
let h = '';
sorted.forEach(r => {
const isMx = r === mx;
h += `<tr${isMx ? ' class="mx"' : ''}>
<td>β‚Ή${fmt(r.w)}${isMx ? ' ⭐' : ''}</td>
<td class="hl">β‚Ή${fmt(r.bs)}</td>
<td class="hl">$${fu(r.pu)} <span style="color:var(--txt2);font-size:10px">(${r.cents}Β’ Β· fee $${fu(r.feeUsd)})</span></td>
<td style="color:var(--txt2)">${fu(r.grossSh)} gross <span style="color:var(--p)">β†’ ${fu(r.netSh)} net</span></td>
<td>β‚Ή${fmt(r.dep)}</td>
<td style="color:var(--txt2)">β‚Ή${fmt(r.left)}</td>
<td>${pf(r.profW)}</td>
<td>${pf(r.profL)}</td>
<td>${pf(r.tgtProf)}</td>
<td>${pfR(r.roi)}</td>
</tr>`;
});
tb.innerHTML = h;
let pctBhav = (mx.bs / cap) * 100;
let pctPoly = (mx.ps / cap) * 100;
let pctFree = (mx.left / cap) * 100;
if (pctBhav < 0) pctBhav = 0; if (pctPoly < 0) pctPoly = 0; if (pctFree < 0) pctFree = 0;
if ($('waBhav')) {
$('waBhav').style.width = pctBhav + '%';
$('waPoly').style.width = pctPoly + '%';
$('waFree').style.width = pctFree + '%';
$('waBhavT').innerHTML = `Bhav: β‚Ή${fmt(mx.bs)} (${pctBhav.toFixed(1)}%)`;
$('waPolyT').innerHTML = `Poly: β‚Ή${fmt(mx.ps)} = $${fu(mx.pu)} (${mx.cents}Β’, ${pctPoly.toFixed(1)}%)`;
$('waFreeT').innerHTML = `Free: β‚Ή${fmt(mx.left)} (${pctFree.toFixed(1)}%)`;
$('waRoi').innerHTML = mx.roi > 0 ? `Guaranteed ROI: +${mx.roi.toFixed(2)}%` : `Loss ROI: ${mx.roi.toFixed(2)}%`;
$('waRoi').style.color = mx.roi > 0 ? 'var(--y)' : 'var(--r)';
}
}
// ── CHART ──
function initChart() {
const ctx = $('oddsChart').getContext('2d');
myChart = new Chart(ctx, {
type: 'line',
data: {
labels: chartData.labels,
datasets: [
{ label: 'Poly YES', data: chartData.poly, borderColor: '#9b95ff', backgroundColor: 'rgba(155,149,255,.08)', tension: .3, pointRadius: 2, borderWidth: 2 },
{ label: 'Bhav LAY', data: chartData.bhav, borderColor: '#ff7099', backgroundColor: 'rgba(255,112,153,.08)', tension: .3, pointRadius: 2, borderWidth: 2 },
{ label: 'Gap %', data: chartData.gap, borderColor: '#00d47e', backgroundColor: 'rgba(0,212,126,.08)', tension: .3, pointRadius: 2, borderWidth: 2, yAxisID: 'y2' }
]
},
options: {
responsive: true, animation: { duration: 300 },
plugins: { legend: { display: false } },
scales: {
x: { ticks: { color: '#4a5a72', font: { size: 9 }, maxTicksLimit: 8 }, grid: { color: 'rgba(26,37,64,.5)' } },
y: { ticks: { color: '#4a5a72', font: { size: 9 } }, grid: { color: 'rgba(26,37,64,.5)' }, title: { display: true, text: 'Odds', color: '#4a5a72', font: { size: 9 } } },
y2: { position: 'right', ticks: { color: '#00d47e', font: { size: 9 } }, grid: { display: false }, title: { display: true, text: 'Gap %', color: '#00d47e', font: { size: 9 } } }
}
}
});
}
function updateChart(p, b) {
const now = new Date().toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const gapPct = b > 0 ? ((p - b) / b) * 100 : 0;
chartData.labels.push(now);
chartData.poly.push(parseFloat(p.toFixed(3)));
chartData.bhav.push(parseFloat(b.toFixed(3)));
chartData.gap.push(parseFloat(gapPct.toFixed(2)));
// Keep last 60 points
if (chartData.labels.length > 60) { chartData.labels.shift(); chartData.poly.shift(); chartData.bhav.shift(); chartData.gap.shift(); }
if (myChart) { myChart.update('none'); }
const pts = chartData.labels.length;
$('chartInfo').textContent = `${pts} data points Β· Poly range: ${Math.min(...chartData.poly).toFixed(3)}–${Math.max(...chartData.poly).toFixed(3)} Β· Bhav range: ${Math.min(...chartData.bhav).toFixed(3)}–${Math.max(...chartData.bhav).toFixed(3)}`;
}
// ── FETCH COUNTER ──
function incNC() { ncalls++; $('ncall').textContent = ncalls; $('ncall2').textContent = ncalls; }
function tokenIndex(tokenId) {
return _polyTokenIds.findIndex(t => t === tokenId);
}
function getPolyBook(tokenId) {
if (!_polyBooks[tokenId]) _polyBooks[tokenId] = { asks: {}, bids: {} };
return _polyBooks[tokenId];
}
function setPolyBookSnapshot(tokenId, data) {
const book = getPolyBook(tokenId);
book.asks = {};
book.bids = {};
(data.asks || []).forEach(a => {
const p = parseFloat(a.price), sz = parseFloat(a.size);
if (p > 0 && sz > 0) book.asks[p] = sz;
});
(data.bids || []).forEach(b => {
const p = parseFloat(b.price), sz = parseFloat(b.size);
if (p > 0 && sz > 0) book.bids[p] = sz;
});
_polySnapshots[tokenId] = {
asks: Object.entries(book.asks).map(([p, sz]) => ({ price: p, size: sz })),
bids: Object.entries(book.bids).map(([p, sz]) => ({ price: p, size: sz })),
last_trade_price: data.last_trade_price || data.last || 0,
volume: data.volume
};
renderP(_polySnapshots[tokenId], tokenId);
}
function applyPolyDelta(tokenId, data) {
const book = getPolyBook(tokenId);
if (data.asks) data.asks.forEach(a => {
const p = parseFloat(a.price), sz = parseFloat(a.size);
if (sz === 0) delete book.asks[p];
else book.asks[p] = sz;
});
if (data.bids) data.bids.forEach(b => {
const p = parseFloat(b.price), sz = parseFloat(b.size);
if (sz === 0) delete book.bids[p];
else book.bids[p] = sz;
});
_polySnapshots[tokenId] = {
asks: Object.entries(book.asks).map(([p, sz]) => ({ price: p, size: sz })),
bids: Object.entries(book.bids).map(([p, sz]) => ({ price: p, size: sz })),
last_trade_price: data.last_trade_price || data.last || _polyLastByToken[tokenId] || 0,
volume: data.volume || _polyVolumeByToken[tokenId] || 'β€”'
};
renderP(_polySnapshots[tokenId], tokenId);
}
// ── POLY TAB SWITCH ──
async function switchPolyTab(idx, isManual = false) {
if (isManual) _polyManualOverrideUntil = Date.now() + 8000;
_polyActiveTab = idx;
_polyTokenId = _polyTokenIds[idx];
const requestedTokenId = _polyTokenId;
_pBookRows = []; // reset DOM diff cache so full rebuild happens cleanly
// Update tab UI
document.querySelectorAll('.poly-tab').forEach((t, i) => t.classList.toggle('on', i === idx));
if (!_polyTokenId) return;
const cached = _polySnapshots[requestedTokenId];
if (cached && ((cached.asks && cached.asks.length) || (cached.bids && cached.bids.length))) {
renderP(cached, requestedTokenId);
checkArb();
updateArbBox();
setSt('pSt', _polyWsConnectedCount > 0 ? 'LIVE βœ“' : 'SNAPSHOT βœ“', _polyWsConnectedCount > 0 ? 'bdg-ok' : 'bdg-w');
return;
}
setSt('pSt', 'LOADING...', 'bdg-w');
$('pBook').innerHTML = `<tr><td colspan="4"><div class="nodata">Loading ${_polyTeamNames[idx] || 'market'} book...</div></td></tr>`;
$('pLast').textContent = 'β€”';
$('pSprd').textContent = 'β€”';
$('pVol').textContent = 'β€”';
try {
const d = await polyFetch(`https://clob.polymarket.com/book?token_id=${requestedTokenId}`);
if (_polyTokenId !== requestedTokenId) return;
if (d.bids || d.asks) {
setPolyBookSnapshot(requestedTokenId, d);
checkArb();
updateArbBox();
setSt('pSt', _polyWsConnectedCount > 0 ? 'LIVE βœ“' : 'SNAPSHOT βœ“', _polyWsConnectedCount > 0 ? 'bdg-ok' : 'bdg-w');
}
} catch (e) { setSt('pSt', 'ERR', 'bdg-r'); }
}
// ── FETCH POLYMARKET ──
let _polyWsByToken = {};
let _wsHeartbeatTimers = {};
let _wsReconnectTimers = {};
let _polyWsConnectedCount = 0;
let _polyAutoSwitching = false;
let _sliderValue = 0.5;
function onArbSlider(val) {
_sliderValue = parseFloat(val) / 100;
document.getElementById('arbSliderVal').innerText = val + '%';
if (val == 50) {
setCalcMode('both');
} else {
setCalcMode('slider');
}
}
let _polyManualOverrideUntil = 0;
function updatePolyWsStatus() {
const openCount = Object.values(_polyWsByToken).filter(ws => ws && ws.readyState === WebSocket.OPEN).length;
_polyWsConnectedCount = openCount;
if (openCount > 0) setSt('pSt', 'LIVE βœ“', 'bdg-ok');
else if (_polyTokenId && _polySnapshots[_polyTokenId]) setSt('pSt', 'SNAPSHOT βœ“', 'bdg-w');
}
function connectPolyWs(tokenId) {
if (!tokenId) return;
const existingWs = _polyWsByToken[tokenId];
if (existingWs && existingWs.readyState < 2) return;
if (_wsHeartbeatTimers[tokenId]) { clearInterval(_wsHeartbeatTimers[tokenId]); delete _wsHeartbeatTimers[tokenId]; }
if (_wsReconnectTimers[tokenId]) { clearTimeout(_wsReconnectTimers[tokenId]); delete _wsReconnectTimers[tokenId]; }
const ws = new WebSocket('wss://ws-subscriptions-clob.polymarket.com/ws/market');
_polyWsByToken[tokenId] = ws;
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'subscribe', channel: 'market', assets_id: tokenId }));
updatePolyWsStatus();
_wsHeartbeatTimers[tokenId] = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
try { ws.send(JSON.stringify({ type: 'ping' })); } catch { }
}
}, 10000);
};
ws.onmessage = (msg) => {
try {
const data = JSON.parse(msg.data);
if (data.type === 'pong' || data.type === 'ping') return; // ignore heartbeat
if (data.asks || data.bids) applyPolyDelta(tokenId, data);
} catch { }
};
ws.onerror = () => {
polyPollLoop(tokenId);
updatePolyWsStatus();
};
ws.onclose = () => {
if (_wsHeartbeatTimers[tokenId]) { clearInterval(_wsHeartbeatTimers[tokenId]); delete _wsHeartbeatTimers[tokenId]; }
polyPollLoop(tokenId);
updatePolyWsStatus();
_wsReconnectTimers[tokenId] = setTimeout(() => connectPolyWs(tokenId), 2000);
};
}
let _polyTokenId = null;
let _polyBooks = {};
let _lastBestAskByToken = {};
let _polyLastByToken = {};
let _polyVolumeByToken = {};
let _polySnapshots = {};
let _polyTokenIds = [null, null]; // [team1, team2]
let _polyActiveTab = 0; // 0 = team1, 1 = team2
let _polyTeamNames = ['β€”', 'β€”'];
// Fetch via proxy.js /poly-fetch β€” uses headless Chrome TLS fingerprint, instant, no relay
async function polyFetch(targetUrl) {
const r = await fetch(`http://localhost:3000/poly-fetch?url=${encodeURIComponent(targetUrl)}`, { signal: AbortSignal.timeout(20000) });
if (!r.ok) throw new Error(`poly-fetch ${r.status}`);
return await r.json();
}
async function fetchPoly() {
let url = $('sPolyUrl').value.trim();
if (!url) { demoP(); return; }
setSt('pSt', 'LOADING...', 'bdg-w'); incNC();
const slug = (url.includes('polymarket.com') ? url.split('/').filter(Boolean).pop().split('?')[0] : url.trim())
.replace(/^\/+/, '')
.trim();
try {
// Step 1: Resolve slug β†’ token ID
setSt('pSt', 'RESOLVING...', 'bdg-w');
const events = await polyFetch(`https://gamma-api.polymarket.com/events?slug=${slug}`);
if (!events || !events[0]) throw new Error('No event found for slug: ' + slug);
const ev = events[0];
// International/PSL events bundle Toss-Winner, Completed-Match, Tie, Super Over alongside the Moneyline.
// The moneyline market's slug always equals the event slug (sub-markets get suffixes like -toss-winner).
const evSlug = (ev.slug || '').toLowerCase();
const isTradable = m => m && m.clobTokenIds && !m.closed && !m.archived;
const hasTeamOutcomes = m => {
try {
const outs = JSON.parse(m.outcomes || '[]').map(o => String(o).toLowerCase().trim());
return outs.length >= 2 && !outs.every(o => o === 'yes' || o === 'no');
} catch { return false; }
};
const candidates = (ev.markets || []).filter(m => isTradable(m) && hasTeamOutcomes(m));
const market =
candidates.find(m => (m.slug || '').toLowerCase() === evSlug) ||
candidates.sort((a, b) => (Number(b.volume) || 0) - (Number(a.volume) || 0))[0] ||
ev.markets[0];
if (!market || !market.clobTokenIds) throw new Error('No tradable moneyline market in event: ' + slug);
const tokenIds = JSON.parse(market.clobTokenIds);
_polyTokenIds = [tokenIds[0], tokenIds[1] || tokenIds[0]];
_polyTokenId = _polyTokenIds[_polyActiveTab];
// Extract team names from market outcomes or use settings
try {
const outcomes = JSON.parse(market.outcomes || '[]');
_polyTeamNames = [outcomes[0] || t1s(), outcomes[1] || t2s()];
} catch { _polyTeamNames = [t1s(), t2s()]; }
syncTeamLabels();
// Update tab labels
if ($('polyTab0')) $('polyTab0').textContent = _polyTeamNames[0];
if ($('polyTab1')) $('polyTab1').textContent = _polyTeamNames[1];
// Step 2: Initial orderbook snapshots for both tokens
_polyBooks = {};
_polySnapshots = {};
_lastBestAskByToken = {};
setSt('pSt', 'LOADING BOOK...', 'bdg-w');
await Promise.all(_polyTokenIds.filter(Boolean).map(async tokenId => {
const d = await polyFetch(`https://clob.polymarket.com/book?token_id=${tokenId}`);
if (d.bids || d.asks) setPolyBookSnapshot(tokenId, d);
}));
// Step 3: Close old WebSockets
Object.values(_polyWsByToken).forEach(ws => { try { ws.close(); } catch { } });
_polyWsByToken = {};
Object.values(_wsHeartbeatTimers).forEach(timer => clearInterval(timer));
Object.values(_wsReconnectTimers).forEach(timer => clearTimeout(timer));
_wsHeartbeatTimers = {};
_wsReconnectTimers = {};
// Step 4: Live WebSocket with heartbeat + auto-reconnect for both tokens
setSt('pSt', 'CONNECTING WS...', 'bdg-w');
_polyTokenIds.filter(Boolean).forEach(tokenId => connectPolyWs(tokenId));
} catch (e) {
setSt('pSt', 'ERR', 'bdg-r');
$('pBook').innerHTML = `<tr><td colspan="4"><div class="nodata" style="color:var(--r)">
⚠️ ${e.message}<br>
<span style="font-size:10px;color:var(--txt2)">slug: <b style="color:var(--y)">${slug}</b></span>
</div></td></tr>`;
}
}
async function polyPollLoop(tokenId = _polyTokenId) {
// Single snapshot only β€” called once on WS drop, then WS reconnects
// Never loop β€” that floods the Cloudflare Worker
if (!tokenId) return;
try {
const d = await polyFetch(`https://clob.polymarket.com/book?token_id=${tokenId}`);
if (d.bids || d.asks) {
setPolyBookSnapshot(tokenId, d);
if (tokenId === _polyTokenId) setSt('pSt', 'SNAPSHOT βœ“', 'bdg-w');
}
} catch { }
// No setTimeout here β€” WS reconnect handles continuity
}
function demoP() {
renderP({
asks: [{ price: '2.00', size: '2643.99' }, { price: '1.92', size: '124.61' }, { price: '1.85', size: '200' }, { price: '1.79', size: '264' }],
bids: [{ price: '2.08', size: '904.49' }, { price: '2.13', size: '87' }, { price: '2.17', size: '10' }, { price: '2.22', size: '30' }],
last: '1.96', spread: '2Β’', volume: '$208K'
}, 'demo');
setSt('pSt', 'DEMO', 'bdg-w'); $('cP1').value = '2.00'; recalc();
}
// ── POLYMARKET ORDERBOOK RENDERER (no-flicker DOM diff) ──
// Builds a row signature array, compares to existing DOM rows,
// only patches cells that actually changed β€” zero full redraws.
let _pBookRows = []; // last rendered row signatures
function renderP(d, tokenId = _polyTokenId) {
const rawAsks = (d.asks || []).map(a => ({ c: parseFloat(a.price), sz: parseFloat(a.size) }));
const rawBids = (d.bids || []).map(b => ({ c: parseFloat(b.price), sz: parseFloat(b.size) }));
const idx = tokenIndex(tokenId);
const asks = [...rawAsks].sort((a, b) => a.c - b.c).slice(0, 5);
const bids = [...rawBids].sort((a, b) => b.c - a.c).slice(0, 5);
const toOdds = c => c > 0 ? 1 / c : 0;
const incomingLast = parseFloat(d.last_trade_price || d.last || 0);
if (tokenId && incomingLast > 0) _polyLastByToken[tokenId] = incomingLast;
if (tokenId && d.volume) _polyVolumeByToken[tokenId] = d.volume;
const lastC = tokenId ? (_polyLastByToken[tokenId] || 0) : incomingLast;
const lastOdds = lastC > 0 ? toOdds(lastC).toFixed(2) : 'β€”';
const lastCents = lastC > 0 ? Math.round(lastC * 100) + 'Β’' : 'β€”';
const bestAskC = asks[0]?.c, bestBidC = bids[0]?.c;
const spread = bestAskC && bestBidC ? Math.round((bestAskC - bestBidC) * 100) + 'Β’' : 'β€”';
const volume = tokenId ? (_polyVolumeByToken[tokenId] || d.volume || 'β€”') : (d.volume || 'β€”');
if (bestAskC && idx >= 0) {
const bestOdds = toOdds(bestAskC);
_polyPrices[idx] = bestOdds;
updateTopPolyBadges();
if (!isManualCalcActive()) {
if (idx === 0) $('cP1').value = bestOdds.toFixed(3);
else if (idx === 1) $('cP2').value = bestOdds.toFixed(3);
}
polyPrice = bestOdds;
const prevBestAskC = _lastBestAskByToken[tokenId];
if (prevBestAskC === undefined || Math.abs(bestAskC - prevBestAskC) >= 0.01) {
_lastBestAskByToken[tokenId] = bestAskC;
recalc(); updateArbBox();
}
}
if (tokenId !== _polyTokenId && tokenId !== 'demo') return;
// Build row descriptors: [type, odds, cents, shares, total]
const newRows = [];
[...asks].reverse().forEach(a => newRows.push({
type: 'ask', cls: 'ask',
col1: `<span style="color:var(--r);font-size:9px;background:rgba(255,58,94,.1);padding:1px 4px;border-radius:2px">ASK</span>`,
col2: `${toOdds(a.c).toFixed(2)} <span style="font-size:9px;color:var(--txt2)">(${Math.round(a.c * 100)}Β’)</span>`,
col3: a.sz.toLocaleString() + ' sh',
col4: '$' + (a.c * a.sz).toFixed(0),
key: 'ask-' + Math.round(a.c * 100)
}));
newRows.push({
type: 'spr', cls: 'spr',
col1: `LAST: ${lastOdds} (${lastCents}) Β· SPREAD: ${spread} Β· VOL: ${volume}`,
key: 'spr'
});
bids.forEach(b => newRows.push({
type: 'bid', cls: 'bid',
col1: `<span style="color:var(--g);font-size:9px;background:rgba(0,212,126,.1);padding:1px 4px;border-radius:2px">BID</span>`,
col2: `${toOdds(b.c).toFixed(2)} <span style="font-size:9px;color:var(--txt2)">(${Math.round(b.c * 100)}Β’)</span>`,
col3: b.sz.toLocaleString() + ' sh',
col4: '$' + (b.c * b.sz).toFixed(0),
key: 'bid-' + Math.round(b.c * 100)
}));
const tb = $('pBook');
// If row count or structure changed, full rebuild (only on book shape change)
const keysMatch = _pBookRows.length === newRows.length &&
_pBookRows.every((r, i) => r.key === newRows[i].key);
if (!keysMatch) {
// Full rebuild β€” rows added/removed/reordered
let h = '';
newRows.forEach(r => {
if (r.type === 'spr') {
h += `<tr class="spr"><td colspan="4">${r.col1}</td></tr>`;
} else {
h += `<tr class="${r.cls}"><td>${r.col1}</td><td class="pr">${r.col2}</td><td>${r.col3}</td><td>${r.col4}</td></tr>`;
}
});
tb.innerHTML = h;
} else {
// Patch only changed cells β€” no DOM replacement = zero flicker
const rows = tb.querySelectorAll('tr');
newRows.forEach((r, i) => {
const prev = _pBookRows[i];
const tr = rows[i];
if (!tr) return;
if (r.type === 'spr') {
if (r.col1 !== prev.col1) tr.cells[0].innerHTML = r.col1;
} else {
if (r.col2 !== prev.col2) tr.cells[1].innerHTML = r.col2; // odds+cents
if (r.col3 !== prev.col3) tr.cells[2].textContent = r.col3; // shares (text only)
if (r.col4 !== prev.col4) tr.cells[3].textContent = r.col4; // total (text only)
}
});
}
_pBookRows = newRows;
// Meta row β€” simple text, no flicker risk
$('pLast').textContent = lastOdds + ' (' + lastCents + ')';
$('pSprd').textContent = spread;
$('pVol').textContent = volume;
}
function renderPM(m) {
const y = parseFloat(m.outcomePrices?.[1] || 0.5);
renderP({ asks: [{ price: (1 / y).toFixed(3), size: '1000' }], bids: [{ price: ((1 / y) + 0.05).toFixed(3), size: '800' }], last: (1 / y).toFixed(3), spread: '1Β’', volume: m.volume ? `$${(m.volume / 1000).toFixed(0)}K` : 'β€”' });
}
// ── REBEL777 WebSocket CLIENT ──
let rebelWs = null;
let rebelWsActive = false;
function onRebel777UrlChange() { } // placeholder
function fetchRebel777() {
const url = $('sRebel777Url').value.trim();
if (!url) { alert('Enter a rebel777 match URL first'); return; }
// Tell proxy to start scraping this URL
fetch(`http://localhost:3000/start-rebel777?url=${encodeURIComponent(url)}`)
.catch(() => {
setSt('bSt', 'PROXY DOWN', 'bdg-r');
$('bBook').innerHTML = `<div class="nodata" style="color:var(--r)">⚠️ proxy.js not running<br><span style="font-size:10px;color:var(--txt2)">Run: <b style="color:var(--y)">node proxy.js</b></span></div>`;
});
// Connect WebSocket if not already connected
if (rebelWs && rebelWs.readyState < 2) return; // already open/connecting
connectRebelWs();
}
function connectRebelWs() {
setSt('bSt', 'CONNECTING...', 'bdg-w');
try {
// Dynamic WebSocket URL to work on both localhost and cloud (Hugging Face)
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host || 'localhost:3000';
const wsUrl = `${protocol}//${host}`;
console.log(`[REBEL777] Connecting to ${wsUrl}...`);
rebelWs = new WebSocket(wsUrl);
} catch (e) {
setSt('bSt', 'WS ERROR', 'bdg-r'); return;
}
rebelWs.onopen = () => {
rebelWsActive = true;
setSt('bSt', 'LOADING...', 'bdg-w');
// Only show spinner if we haven't received odds yet β€” preserve live display on reconnect
if (!window._hasOdds) {
$('bBook').innerHTML = `<div class="nodata" style="color:var(--y)">⏳ Opening rebel777 page (first load ~6s)...</div>`;
}
console.log('[WS] Connected to proxy');
};
rebelWs.onmessage = (e) => {
try {
const msg = JSON.parse(e.data);
if (msg.type === 'odds') {
window._hasOdds = true;
renderB(msg.data);
setSt('bSt', 'REBEL LIVE βœ“', 'bdg-ok');
incNC();
} else if (msg.type === 'status') {
setSt('bSt', msg.state === 'ready' ? 'REBEL LIVE βœ“' : 'LOADING...', msg.state === 'ready' ? 'bdg-ok' : 'bdg-w');
// Only update display if no odds have arrived yet β€” don't wipe a live book
if (!window._hasOdds) {
if (msg.state === 'ready') {
$('bBook').innerHTML = `<div class="nodata" style="color:var(--g)">βœ“ Connected β€” waiting for first odds...</div>`;
} else {
$('bBook').innerHTML = `<div class="nodata" style="color:var(--y)">⏳ Opening rebel777 page (first load ~6s)...</div>`;
}
}
} else if (msg.type === 'debug') {
console.warn('[rebel debug]', msg.data);
if (!window._hasOdds) {
$('bBook').innerHTML = `<div class="nodata" style="color:var(--y)">
⚠️ Connected but no odds found. bl-boxes: ${msg.data.blBoxCount}, odds spans: ${msg.data.oddsSpanCount}<br>
<span style="font-size:10px;color:var(--txt2)">URL: ${msg.data.url}</span>
</div>`;
}
} else if (msg.type === 'error') {
setSt('bSt', 'ERR', 'bdg-r');
$('bBook').innerHTML = `<div class="nodata" style="color:var(--r)">⚠️ ${msg.msg}</div>`;
}
// ping = heartbeat, ignore
} catch (err) { }
};
rebelWs.onerror = () => { setSt('bSt', 'WS ERR', 'bdg-r'); rebelWsActive = false; };
rebelWs.onclose = () => {
rebelWsActive = false;
setSt('bSt', 'DISCONNECTED', 'bdg-w');
// Auto-reconnect after 3s if URL still set
if ($('sRebel777Url').value.trim()) {
setTimeout(connectRebelWs, 3000);
}
};
}
function stopRebel777() {
fetch('http://localhost:3000/stop-rebel777').catch(() => { });
if (rebelWs) rebelWs.close();
setSt('bSt', 'STOPPED', 'bdg-w');
}
// ── FETCH BHAV ──
async function fetchRebel777() {
const url = $('sRebel777Url').value.trim();
if (!url) { demoB(); return; }
setSt('bSt', 'LOADING...', 'bdg-w'); incNC();
// Dynamic host detection for local/cloud
const host = window.location.host || 'localhost:3000';
const protocol = window.location.protocol;
const proxyUrl = `${protocol}//${host}/start-rebel777?url=${encodeURIComponent(url)}`;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10000);
try {
const r = await fetch(proxyUrl, { signal: controller.signal });
clearTimeout(timer);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const d = await r.json();
if (d.error) throw new Error(d.error);
renderB(d); setSt('bSt', 'LIVE βœ“', 'bdg-ok');
} catch (e) {
clearTimeout(timer);
const isProxyDown = e.name === 'AbortError' ||
(e instanceof TypeError && (
e.message.toLowerCase().includes('failed to fetch') ||
e.message.toLowerCase().includes('networkerror') ||
e.message.toLowerCase().includes('load failed') ||
e.message.toLowerCase().includes('connection refused')
));
setSt('bSt', isProxyDown ? 'PROXY DOWN' : 'ERR', 'bdg-r');
$('bBook').innerHTML = `<tr><td colspan="4" style="color:var(--r);padding:10px;text-align:center;font-size:11px">
⚠️ ${isProxyDown ? 'proxy.js not running β€” start it first' : e.message}<br>
<span style="color:var(--txt2);font-size:10px">Run: <b style="color:var(--y)">node proxy.js</b> in the same folder, then refresh</span><br><br>
<button onclick="demoB()" style="margin-top:6px;background:var(--s2);color:var(--txt2);border:1px solid var(--bd2);padding:4px 12px;border-radius:3px;cursor:pointer;font-family:monospace;font-size:10px">USE DEMO</button>
</td></tr>`;
}
}
function loadManualJson() {
try {
const raw = document.getElementById('manualJson')?.value.trim();
if (!raw) return;
const d = JSON.parse(raw);
renderB(d); setSt('bSt', 'MANUAL βœ“', 'bdg-ok');
} catch (e) { alert('Invalid JSON: ' + e.message); }
}
function demoB() {
renderB({
runners: [
{ name: t1(), back: [{ odds: 2.16, size: 11394 }, { odds: 2.18, size: 17660 }, { odds: 2.20, size: 40718 }], lay: [{ odds: 2.24, size: 26933 }, { odds: 2.26, size: 16449 }, { odds: 2.28, size: 12005 }] },
{ name: t2(), back: [{ odds: 1.79, size: 15108 }, { odds: 1.80, size: 21108 }, { odds: 1.81, size: 32611 }], lay: [{ odds: 1.82, size: 26228 }, { odds: 1.83, size: 21732 }, { odds: 1.84, size: 21083 }] }
]
});
setSt('bSt', 'DEMO', 'bdg-w'); $('cB1').value = '1.82'; recalc();
}
function renderB(d) {
// Support: { result: [...] } with mtype=MATCH_ODDS ← your bhav API format
let runners = [];
if (d.result && Array.isArray(d.result)) {
const matchOdds = d.result.find(m => m.mtype === 'MATCH_ODDS' || m.name === 'Match Odds');
if (matchOdds && matchOdds.runners) runners = matchOdds.runners;
}
// Fallbacks for other formats
if (!runners.length) runners = d.runners || d.markets?.[0]?.runners || [];
if (!runners.length && d.team1) runners = [{ name: d.team1.name, back: d.team1.back, lay: d.team1.lay }, { name: d.team2.name, back: d.team2.back, lay: d.team2.lay }];
let h = '';
runners.forEach((runner, ri) => {
const isT1 = ri === 0, cls = isT1 ? 'm' : 'k';
const displayName = _polyTeamNames[ri] && _polyTeamNames[ri] !== 'β€”' ? _polyTeamNames[ri] : (runner.name || `Runner ${ri + 1}`);
const bk = (runner.back || runner.availableToBack || []).slice(0, 3);
const lk = (runner.lay || runner.availableToLay || []).slice(0, 3);
const layLevels = lk
.map(c => parseFloat(c.odds || c.price))
.filter(v => Number.isFinite(v) && v > 1)
.slice(0, 2);
const bestLay = lk[0] ? parseFloat(lk[0].odds || lk[0].price) : null;
// Store per-runner best lay for same-team arb mapping
if (isT1) {
window._r0Lay = bestLay;
window._r0LayLevels = layLevels;
} else {
window._r1Lay = bestLay;
window._r1LayLevels = layLevels;
}
let cells = '';
for (let i = 0; i < 3; i++) {
const c = bk[i];
cells += c ? `<div class="bc bk${i === 2 ? ' bbk' : ''}"><div class="bo">${parseFloat(c.odds || c.price).toFixed(2)}</div><div class="bl">${(parseInt(c.size) || 0) >= 50000 ? '50K+' : (parseInt(c.size) || 0).toLocaleString()}</div></div>` : `<div class="bc bk"><div class="bo" style="color:var(--bd)">β€”</div></div>`;
}
for (let i = 0; i < 3; i++) {
const c = lk[i];
cells += c ? `<div class="bc lk${i === 0 ? ' blk' : ''}"><div class="bo">${parseFloat(c.odds || c.price).toFixed(2)}</div><div class="bl">${(parseInt(c.size) || 0) >= 50000 ? '50K+' : (parseInt(c.size) || 0).toLocaleString()}</div></div>` : `<div class="bc lk"><div class="bo" style="color:var(--bd)">β€”</div></div>`;
}
h += `<div class="brow"><div class="brh"><span class="btn ${cls}">${displayName}</span><span style="font-size:9px;color:var(--txt2)">LAYβ˜…: <span style="color:var(--lk)">${bestLay ? bestLay.toFixed(2) : 'β€”'}</span></span></div><div class="bcells">${cells}</div></div>`;
});
$('bBook').innerHTML = h;
const lay0 = window._r0Lay, lay1 = window._r1Lay;
if (!isManualCalcActive()) {
if (lay0) $('cB1').value = lay0.toFixed(3);
if (lay1) $('cB2').value = lay1.toFixed(3);
}
bhavLay = lay0 || lay1 || null;
recalc();
updateArbBox();
}
function setSt(id, t, c) { $(id).textContent = t; $(id).className = 'bdg ' + c; }
// ── FALLBACK POLL ──
function startAR() {
if (rtmr) clearInterval(rtmr);
const s = parseInt($('sRef').value);
if (!s) {
// Refresh turned off β€” tell proxy to use a sensible default
fetch('http://localhost:3000/set-interval?ms=4000').catch(() => { });
return;
}
const ms = s * 1000;
// Sync proxy poll rate with UI refresh setting
fetch('http://localhost:3000/set-interval?ms=' + ms).catch(() => { });
// Polymarket should not reconnect its WS on every refresh tick.
rtmr = setInterval(() => {
if ($('sPolyUrl').value.trim()) {
if (_polyTokenIds.filter(Boolean).length) {
_polyTokenIds.filter(Boolean).forEach(tokenId => polyPollLoop(tokenId));
} else if (_polyTokenId) {
polyPollLoop();
}
else fetchPoly();
}
// rebel777 is handled entirely by proxy poll loop β€” no fetchRebel777() here
}, ms);
}
function refreshAll() {
if ($('sPolyUrl').value.trim()) fetchPoly(); else demoP();
if ($('sRebel777Url').value.trim()) fetchRebel777(); else demoB();
}
document.addEventListener('visibilitychange', () => {
if (document.hidden) return;
if (_polyTokenIds.filter(Boolean).length) {
_polyTokenIds.filter(Boolean).forEach(tokenId => {
polyPollLoop(tokenId);
connectPolyWs(tokenId);
});
}
if ($('sRebel777Url')?.value.trim() && (!rebelWs || rebelWs.readyState >= 2)) {
connectRebelWs();
}
checkArb();
});
// ── INIT ──
window.onload = () => {
initChart();
onT(); // set initial team labels
onAH();
onRC();
recalc();
demoP();
demoB();
};
</script>
</body>
</html>