fix: parse SENSEX weekly symbol format DD+MON+STRIKE (no year)
Angel One SENSEX weeklies use e.g. SENSEX26JUN77300PE where 26=day, JUN=month, 77300=strike — no year in the symbol. The previous parser tried YY+MON+DD, treating 26 as year and 77 as day, giving an invalid date (2026-06-77) and returning null for ALL option positions. Added Format 3: DD(1-2) + MON(3-letter) + STRIKE + TYPE Year is inferred: current year, or next year if date is >7 days past. Format 2 (YY+MON+DD) is left as-is so it still falls through to Format 3 when its parsed date is invalid (day=77 etc). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
02cc8ed9b3
commit
1fbc9279a4
1 changed files with 35 additions and 11 deletions
|
|
@ -166,14 +166,27 @@ export function calcGreeks(input: GreeksInput): Greeks {
|
||||||
return { iv, delta, gamma, theta, vega, rho, ok: true };
|
return { iv, delta, gamma, theta, vega, rho, ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MONTH_MAP: Record<string, string> = {
|
||||||
|
JAN:'01',FEB:'02',MAR:'03',APR:'04',MAY:'05',JUN:'06',
|
||||||
|
JUL:'07',AUG:'08',SEP:'09',OCT:'10',NOV:'11',DEC:'12',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse SENSEX option symbol -> strike/type/expiry.
|
* Parse Angel One option symbol -> strike/type/expiry.
|
||||||
* Supports two formats:
|
* Handles three formats observed in the wild:
|
||||||
* - SENSEX2660475500CE -> Jun 04 2026, 75500 CE (YY+M+DD)
|
*
|
||||||
* - SENSEX26MAY28076100CE -> May 28 2026, 76100 CE (YY+MON+DD)
|
* Format 1 — YY + M(1-digit) + DD + STRIKE
|
||||||
|
* SENSEX2660475500CE → 2026-06-04, strike 75500
|
||||||
|
*
|
||||||
|
* Format 2 — YY + MON + DD + STRIKE (NIFTY carry-forward style)
|
||||||
|
* NIFTY28FEB2522500CE → 2025-02-28, strike 22500
|
||||||
|
* (NOTE: regex groups are DD, MON, YY order — Angel puts day first)
|
||||||
|
*
|
||||||
|
* Format 3 — DD + MON + STRIKE (SENSEX weekly, no year in symbol)
|
||||||
|
* SENSEX26JUN77300PE → 2026-06-26, strike 77300, year inferred
|
||||||
*/
|
*/
|
||||||
export function parseOptionSymbol(sym: string): { underlying: string; strike: number; type: 'CE' | 'PE'; expiry: Date } | null {
|
export function parseOptionSymbol(sym: string): { underlying: string; strike: number; type: 'CE' | 'PE'; expiry: Date } | null {
|
||||||
// Format 1: YY + M(1) + DD
|
// Format 1: YY + M(1) + DD + STRIKE
|
||||||
let m = sym.match(/^([A-Z]+)(\d{2})(\d{1})(\d{2})(\d+)(CE|PE)$/);
|
let m = sym.match(/^([A-Z]+)(\d{2})(\d{1})(\d{2})(\d+)(CE|PE)$/);
|
||||||
if (m) {
|
if (m) {
|
||||||
const yy = m[2], mo = m[3].padStart(2, '0'), dd = m[4];
|
const yy = m[2], mo = m[3].padStart(2, '0'), dd = m[4];
|
||||||
|
|
@ -182,19 +195,30 @@ export function parseOptionSymbol(sym: string): { underlying: string; strike: nu
|
||||||
return { underlying: m[1], strike: parseInt(m[5]), type: m[6] as 'CE' | 'PE', expiry: exp };
|
return { underlying: m[1], strike: parseInt(m[5]), type: m[6] as 'CE' | 'PE', expiry: exp };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Format 2: YY + MON + DD
|
// Format 2: YY + MON + DD + STRIKE (kept for backward compat; falls through on invalid dates)
|
||||||
|
// Note: SENSEX26JUN77300PE is NOT this format — it falls through to Format 3 below.
|
||||||
m = sym.match(/^([A-Z]+)(\d{2})(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(\d{2})(\d+)(CE|PE)$/i);
|
m = sym.match(/^([A-Z]+)(\d{2})(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(\d{2})(\d+)(CE|PE)$/i);
|
||||||
if (m) {
|
if (m) {
|
||||||
const months: Record<string, string> = {
|
const yy = m[2], mo = MONTH_MAP[m[3].toUpperCase()], dd = m[4];
|
||||||
JAN:'01',FEB:'02',MAR:'03',APR:'04',MAY:'05',JUN:'06',
|
|
||||||
JUL:'07',AUG:'08',SEP:'09',OCT:'10',NOV:'11',DEC:'12',
|
|
||||||
};
|
|
||||||
const yy = m[2], mo = months[m[3].toUpperCase()], dd = m[4];
|
|
||||||
const exp = new Date(`20${yy}-${mo}-${dd}T15:30:00+05:30`);
|
const exp = new Date(`20${yy}-${mo}-${dd}T15:30:00+05:30`);
|
||||||
if (!isNaN(exp.getTime())) {
|
if (!isNaN(exp.getTime())) {
|
||||||
return { underlying: m[1], strike: parseInt(m[5]), type: m[6].toUpperCase() as 'CE' | 'PE', expiry: exp };
|
return { underlying: m[1], strike: parseInt(m[5]), type: m[6].toUpperCase() as 'CE' | 'PE', expiry: exp };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Format 3: DD + MON + STRIKE (SENSEX weekly — no year, infer from calendar)
|
||||||
|
m = sym.match(/^([A-Z]+)(\d{1,2})(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(\d+)(CE|PE)$/i);
|
||||||
|
if (m) {
|
||||||
|
const dd = m[2].padStart(2, '0'), mo = MONTH_MAP[m[3].toUpperCase()];
|
||||||
|
const thisYear = new Date().getFullYear();
|
||||||
|
// Try current year first; if date is already >7 days in the past, try next year
|
||||||
|
let exp = new Date(`${thisYear}-${mo}-${dd}T15:30:00+05:30`);
|
||||||
|
if (isNaN(exp.getTime()) || exp.getTime() < Date.now() - 7 * 86400000) {
|
||||||
|
exp = new Date(`${thisYear + 1}-${mo}-${dd}T15:30:00+05:30`);
|
||||||
|
}
|
||||||
|
if (!isNaN(exp.getTime())) {
|
||||||
|
return { underlying: m[1], strike: parseInt(m[4]), type: m[5].toUpperCase() as 'CE' | 'PE', expiry: exp };
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue