diff --git a/public/index.html b/public/index.html
index 5fc6ed2..7a1efbb 100644
--- a/public/index.html
+++ b/public/index.html
@@ -405,18 +405,17 @@ function toggleCard(id){
let payoffChartInst = null;
function parseOptionSym(sym) {
- // Angel format: UNDERLYING + YY + M(1digit) + DD + STRIKE + CE/PE
- // e.g. SENSEX2651479000CE → underlying=SENSEX, yy=26, m=5, dd=14, strike=79000, type=CE
- let m = sym.match(/^([A-Z]+)(d{2})(d{1})(d{2})(d+)(CE|PE)$/);
+ var RE1 = new RegExp("^([A-Z]+)(\\d{2})(\\d{1})(\\d{2})(\\d+)(CE|PE)$");
+ var m = sym.match(RE1);
if (m) {
- const exp = new Date(`20${m[2]}-${m[3].padStart(2,'0')}-${m[4]}`);
- return { underlying: m[1], strike: parseInt(m[5]), type: m[6], expiry: exp };
+ var exp = new Date(20+m[2]+-+m[3].padStart(2,0)+-+m[4]);
+ return { underlying: m[1], strike: parseInt(m[5]), type: m[6], expiry: isNaN(exp)?null:exp };
}
- // Fallback: UNDERLYING + YYMMDD + STRIKE + CE/PE (2-digit month)
- m = sym.match(/^([A-Z]+)(d{2})(d{2})(d{2})(d+)(CE|PE)$/);
+ var RE2 = new RegExp("^([A-Z]+)(\\d{2})(\\d{2})(\\d{2})(\\d+)(CE|PE)$");
+ m = sym.match(RE2);
if (m) {
- const exp = new Date(`20${m[2]}-${m[3]}-${m[4]}`);
- return { underlying: m[1], strike: parseInt(m[5]), type: m[6], expiry: exp };
+ var exp2 = new Date(20+m[2]+-+m[3]+-+m[4]);
+ return { underlying: m[1], strike: parseInt(m[5]), type: m[6], expiry: isNaN(exp2)?null:exp2 };
}
return null;
}
@@ -530,51 +529,15 @@ async function loadPayoff() {
const gc = dk ? 'rgba(255,255,255,.05)' : 'rgba(0,0,0,.05)';
const tc = dk ? '#3D444D' : '#C4B8B0';
- // Profit fill (above zero) and loss fill (below zero) — use two datasets with clip trick
- const profitData = ys.map(y => ({ y: Math.max(y, 0) }));
- const lossData = ys.map(y => ({ y: Math.min(y, 0) }));
-
+ canvas._beLines = breakevens; canvas._spotLine = spot; canvas._chartXs = xs;
if (payoffChartInst) payoffChartInst.destroy();
-
- const annotations = {};
- // Breakeven vertical lines
- breakevens.forEach((be, i) => {
- annotations['be'+i] = {
- type: 'line', xMin: be, xMax: be,
- borderColor: 'rgba(245,166,35,.7)', borderWidth: 1.5, borderDash: [5,4],
- label: { display: true, content: be.toLocaleString('en-IN'), position: 'start',
- color: '#F5A623', font: { size: 10, family: 'Geist Mono' }, yAdjust: -8 }
- };
- });
- // Current spot
- if (spot) {
- annotations['spot'] = {
- type: 'line', xMin: spot, xMax: spot,
- borderColor: 'rgba(255,107,74,.8)', borderWidth: 2, borderDash: [6,3],
- label: { display: true, content: spot.toLocaleString('en-IN'), position: 'end',
- color: '#FF6B4A', font: { size: 10, family: 'Geist Mono' }, yAdjust: 8 }
- };
- }
-
payoffChartInst = new Chart(canvas, {
type: 'line',
- data: {
- labels: xs,
- datasets: [
- {
- label: 'Profit',
- data: ys,
- borderColor: ys[Math.floor(ys.length/2)] >= 0 ? '#2ECC71' : '#E74C3C',
- borderWidth: 2.5,
- tension: 0,
- pointRadius: 0,
- fill: {
- target: { value: 0 },
- above: 'rgba(46,204,113,0.15)',
- below: 'rgba(231,76,60,0.15)',
- },
- },
- ],
+ data: { labels: xs, datasets: [
+ {label:'Profit Zone',data:ys.map(function(y){return y>=0?y:0;}),borderColor:'transparent',backgroundColor:'rgba(46,204,113,0.18)',tension:0,pointRadius:0,fill:true},
+ {label:'Loss Zone',data:ys.map(function(y){return y<0?y:0;}),borderColor:'transparent',backgroundColor:'rgba(231,76,60,0.18)',tension:0,pointRadius:0,fill:true},
+ {label:'P&L',data:ys,borderColor:'#FF6B4A',borderWidth:2.5,tension:0,pointRadius:0,fill:false},
+ ] },
},
options: {
responsive: true, maintainAspectRatio: false, animation: false,
@@ -594,9 +557,6 @@ async function loadPayoff() {
},
},
},
- annotation: (window.ChartAnnotation && {
- annotations,
- }),
},
scales: {
x: {
@@ -620,6 +580,60 @@ async function loadPayoff() {
}
// End payoff graph
+
+var _payoffPlugin = {
+ id: 'payoffLines',
+ afterDraw: function(chart) {
+ if (!chart.canvas || !chart.canvas._beLines) return;
+ var ctx = chart.ctx;
+ var xs = chart.canvas._chartXs;
+ if (!xs || !xs.length) return;
+ var xScale = chart.scales.x, yScale = chart.scales.y;
+ var lo = xs[0], hi = xs[xs.length-1], range = hi - lo;
+ if (!range) return;
+ function xPx(v) { return xScale.left + (v-lo)/range*(xScale.right-xScale.left); }
+ ctx.save();
+ // Breakeven lines — amber dashed
+ (chart.canvas._beLines || []).forEach(function(be) {
+ var x = xPx(be);
+ if (x < xScale.left || x > xScale.right) return;
+ ctx.beginPath();
+ ctx.setLineDash([5,4]);
+ ctx.strokeStyle = 'rgba(245,166,35,0.85)';
+ ctx.lineWidth = 1.5;
+ ctx.moveTo(x, yScale.top);
+ ctx.lineTo(x, yScale.bottom);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ ctx.fillStyle = '#F5A623';
+ ctx.font = '9px monospace';
+ ctx.textAlign = 'center';
+ ctx.fillText(Number(be).toLocaleString('en-IN'), x, yScale.top + 10);
+ });
+ // Spot line — coral dashed
+ var spot = chart.canvas._spotLine;
+ if (spot) {
+ var x = xPx(spot);
+ if (x >= xScale.left && x <= xScale.right) {
+ ctx.beginPath();
+ ctx.setLineDash([6,3]);
+ ctx.strokeStyle = 'rgba(255,107,74,0.9)';
+ ctx.lineWidth = 2;
+ ctx.moveTo(x, yScale.top);
+ ctx.lineTo(x, yScale.bottom);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ ctx.fillStyle = '#FF6B4A';
+ ctx.font = 'bold 9px monospace';
+ ctx.textAlign = 'center';
+ ctx.fillText('Spot ' + Number(spot).toLocaleString('en-IN'), x, yScale.bottom - 4);
+ }
+ }
+ ctx.restore();
+ }
+};
+Chart.register(_payoffPlugin);
+
loadConfig();refresh();
setInterval(refresh,60000);
setInterval(loadMarket,15000);