fix: payoff regex (\d stripped to d), proper fill zones, canvas annotation plugin
This commit is contained in:
parent
4b0bfb2a12
commit
c9a38be908
1 changed files with 68 additions and 54 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue