fix: payoff regex (\d stripped to d), proper fill zones, canvas annotation plugin

This commit is contained in:
Manohar 2026-05-11 05:16:17 +00:00
parent 4b0bfb2a12
commit c9a38be908

View file

@ -405,18 +405,17 @@ function toggleCard(id){
let payoffChartInst = null; let payoffChartInst = null;
function parseOptionSym(sym) { function parseOptionSym(sym) {
// Angel format: UNDERLYING + YY + M(1digit) + DD + STRIKE + CE/PE var RE1 = new RegExp("^([A-Z]+)(\\d{2})(\\d{1})(\\d{2})(\\d+)(CE|PE)$");
// e.g. SENSEX2651479000CE → underlying=SENSEX, yy=26, m=5, dd=14, strike=79000, type=CE var m = sym.match(RE1);
let m = sym.match(/^([A-Z]+)(d{2})(d{1})(d{2})(d+)(CE|PE)$/);
if (m) { if (m) {
const exp = new Date(`20${m[2]}-${m[3].padStart(2,'0')}-${m[4]}`); 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: exp }; 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) var RE2 = new RegExp("^([A-Z]+)(\\d{2})(\\d{2})(\\d{2})(\\d+)(CE|PE)$");
m = sym.match(/^([A-Z]+)(d{2})(d{2})(d{2})(d+)(CE|PE)$/); m = sym.match(RE2);
if (m) { if (m) {
const exp = new Date(`20${m[2]}-${m[3]}-${m[4]}`); var exp2 = new Date(20+m[2]+-+m[3]+-+m[4]);
return { underlying: m[1], strike: parseInt(m[5]), type: m[6], expiry: exp }; return { underlying: m[1], strike: parseInt(m[5]), type: m[6], expiry: isNaN(exp2)?null:exp2 };
} }
return null; return null;
} }
@ -530,51 +529,15 @@ async function loadPayoff() {
const gc = dk ? 'rgba(255,255,255,.05)' : 'rgba(0,0,0,.05)'; const gc = dk ? 'rgba(255,255,255,.05)' : 'rgba(0,0,0,.05)';
const tc = dk ? '#3D444D' : '#C4B8B0'; const tc = dk ? '#3D444D' : '#C4B8B0';
// Profit fill (above zero) and loss fill (below zero) — use two datasets with clip trick canvas._beLines = breakevens; canvas._spotLine = spot; canvas._chartXs = xs;
const profitData = ys.map(y => ({ y: Math.max(y, 0) }));
const lossData = ys.map(y => ({ y: Math.min(y, 0) }));
if (payoffChartInst) payoffChartInst.destroy(); 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, { payoffChartInst = new Chart(canvas, {
type: 'line', type: 'line',
data: { data: { labels: xs, datasets: [
labels: xs, {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},
datasets: [ {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},
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)',
},
},
],
}, },
options: { options: {
responsive: true, maintainAspectRatio: false, animation: false, responsive: true, maintainAspectRatio: false, animation: false,
@ -594,9 +557,6 @@ async function loadPayoff() {
}, },
}, },
}, },
annotation: (window.ChartAnnotation && {
annotations,
}),
}, },
scales: { scales: {
x: { x: {
@ -620,6 +580,60 @@ async function loadPayoff() {
} }
// End payoff graph // 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(); loadConfig();refresh();
setInterval(refresh,60000); setInterval(refresh,60000);
setInterval(loadMarket,15000); setInterval(loadMarket,15000);