@lacspace/market 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +178 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +126 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lacspace
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
function formatINR(amount, opts = {}) {
|
|
5
|
+
const { symbol = "\u20B9", decimals = 2 } = opts;
|
|
6
|
+
const neg = amount < 0;
|
|
7
|
+
const fixed = Math.abs(amount).toFixed(decimals);
|
|
8
|
+
const [intPart = "0", frac = ""] = fixed.split(".");
|
|
9
|
+
const last3 = intPart.slice(-3);
|
|
10
|
+
const rest = intPart.slice(0, -3);
|
|
11
|
+
const grouped = rest ? rest.replace(/\B(?=(\d{2})+(?!\d))/g, ",") + "," + last3 : last3;
|
|
12
|
+
return `${neg ? "-" : ""}${symbol}${grouped}${decimals > 0 ? "." + frac : ""}`;
|
|
13
|
+
}
|
|
14
|
+
function pnl(o) {
|
|
15
|
+
return (o.sell - o.buy) * o.qty;
|
|
16
|
+
}
|
|
17
|
+
function changePercent(current, reference) {
|
|
18
|
+
if (reference === 0) return 0;
|
|
19
|
+
return (current - reference) / reference * 100;
|
|
20
|
+
}
|
|
21
|
+
function pnlPercent(o) {
|
|
22
|
+
return changePercent(o.sell, o.buy);
|
|
23
|
+
}
|
|
24
|
+
function cagr(begin, end, years) {
|
|
25
|
+
if (begin <= 0 || years <= 0) return NaN;
|
|
26
|
+
return Math.pow(end / begin, 1 / years) - 1;
|
|
27
|
+
}
|
|
28
|
+
function toMillis(d) {
|
|
29
|
+
if (d instanceof Date) return d.getTime();
|
|
30
|
+
if (typeof d === "number") return d;
|
|
31
|
+
return new Date(d).getTime();
|
|
32
|
+
}
|
|
33
|
+
function xirr(flows, guess = 0.1) {
|
|
34
|
+
if (flows.length < 2) return NaN;
|
|
35
|
+
const cf = flows.map((f) => ({ amount: f.amount, t: toMillis(f.date) })).sort((a, b) => a.t - b.t);
|
|
36
|
+
const t0 = cf[0].t;
|
|
37
|
+
const yearFrac = (t) => (t - t0) / (365 * 24 * 3600 * 1e3);
|
|
38
|
+
const npv = (r2) => cf.reduce((s, c) => s + c.amount / Math.pow(1 + r2, yearFrac(c.t)), 0);
|
|
39
|
+
const dnpv = (r2) => cf.reduce((s, c) => {
|
|
40
|
+
const y = yearFrac(c.t);
|
|
41
|
+
return s - y * c.amount / Math.pow(1 + r2, y + 1);
|
|
42
|
+
}, 0);
|
|
43
|
+
let r = guess;
|
|
44
|
+
for (let i = 0; i < 100; i++) {
|
|
45
|
+
const f = npv(r);
|
|
46
|
+
if (Math.abs(f) < 1e-7) return r;
|
|
47
|
+
const d = dnpv(r);
|
|
48
|
+
if (d === 0) break;
|
|
49
|
+
const next = r - f / d;
|
|
50
|
+
if (!isFinite(next)) break;
|
|
51
|
+
if (Math.abs(next - r) < 1e-10) return next;
|
|
52
|
+
r = next;
|
|
53
|
+
}
|
|
54
|
+
return r;
|
|
55
|
+
}
|
|
56
|
+
function averagePrice(trades) {
|
|
57
|
+
let qty = 0;
|
|
58
|
+
let value = 0;
|
|
59
|
+
for (const t of trades) {
|
|
60
|
+
qty += t.qty;
|
|
61
|
+
value += t.price * t.qty;
|
|
62
|
+
}
|
|
63
|
+
return qty === 0 ? 0 : value / qty;
|
|
64
|
+
}
|
|
65
|
+
function positionSize(o) {
|
|
66
|
+
const riskAmount = o.capital * (o.riskPercent / 100);
|
|
67
|
+
const perShareRisk = Math.abs(o.entry - o.stop);
|
|
68
|
+
if (perShareRisk === 0) return 0;
|
|
69
|
+
return Math.floor(riskAmount / perShareRisk);
|
|
70
|
+
}
|
|
71
|
+
function roundToTick(price, tick = 0.05) {
|
|
72
|
+
if (tick <= 0) return price;
|
|
73
|
+
return Number((Math.round(price / tick) * tick).toFixed(4));
|
|
74
|
+
}
|
|
75
|
+
function circuitLimits(prevClose, percent) {
|
|
76
|
+
const delta = prevClose * (percent / 100);
|
|
77
|
+
return {
|
|
78
|
+
upper: roundToTick(prevClose + delta),
|
|
79
|
+
lower: roundToTick(prevClose - delta)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
var IN_DISCOUNT_BROKER = {
|
|
83
|
+
segments: {
|
|
84
|
+
delivery: {
|
|
85
|
+
brokeragePct: 0,
|
|
86
|
+
brokerageCap: 0,
|
|
87
|
+
sttBuy: 1e-3,
|
|
88
|
+
sttSell: 1e-3,
|
|
89
|
+
exchangeTxn: 297e-7,
|
|
90
|
+
stampBuy: 15e-5,
|
|
91
|
+
dpPerScrip: 13.5
|
|
92
|
+
},
|
|
93
|
+
intraday: {
|
|
94
|
+
brokeragePct: 3e-4,
|
|
95
|
+
brokerageCap: 20,
|
|
96
|
+
sttBuy: 0,
|
|
97
|
+
sttSell: 25e-5,
|
|
98
|
+
exchangeTxn: 297e-7,
|
|
99
|
+
stampBuy: 3e-5,
|
|
100
|
+
dpPerScrip: 0
|
|
101
|
+
},
|
|
102
|
+
futures: {
|
|
103
|
+
brokeragePct: 3e-4,
|
|
104
|
+
brokerageCap: 20,
|
|
105
|
+
sttBuy: 0,
|
|
106
|
+
sttSell: 2e-4,
|
|
107
|
+
exchangeTxn: 173e-7,
|
|
108
|
+
stampBuy: 2e-5,
|
|
109
|
+
dpPerScrip: 0
|
|
110
|
+
},
|
|
111
|
+
options: {
|
|
112
|
+
brokeragePct: 0,
|
|
113
|
+
brokerageCap: 20,
|
|
114
|
+
brokerageFlat: 20,
|
|
115
|
+
sttBuy: 0,
|
|
116
|
+
sttSell: 1e-3,
|
|
117
|
+
exchangeTxn: 3503e-7,
|
|
118
|
+
stampBuy: 3e-5,
|
|
119
|
+
dpPerScrip: 0
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
sebi: 1e-6,
|
|
123
|
+
// ₹10 per crore
|
|
124
|
+
gst: 0.18
|
|
125
|
+
};
|
|
126
|
+
function round2(n) {
|
|
127
|
+
return Math.round(n * 100) / 100;
|
|
128
|
+
}
|
|
129
|
+
function legBrokerage(turnover, r) {
|
|
130
|
+
if (turnover <= 0) return 0;
|
|
131
|
+
if (r.brokerageFlat !== void 0) return r.brokerageFlat;
|
|
132
|
+
if (r.brokeragePct === 0) return 0;
|
|
133
|
+
return Math.min(turnover * r.brokeragePct, r.brokerageCap);
|
|
134
|
+
}
|
|
135
|
+
function charges(input, config = IN_DISCOUNT_BROKER) {
|
|
136
|
+
const r = config.segments[input.segment];
|
|
137
|
+
const buyVal = (input.buy || 0) * input.qty;
|
|
138
|
+
const sellVal = (input.sell || 0) * input.qty;
|
|
139
|
+
const turnover = buyVal + sellVal;
|
|
140
|
+
const brokerage = legBrokerage(buyVal, r) + legBrokerage(sellVal, r);
|
|
141
|
+
const stt = buyVal * r.sttBuy + sellVal * r.sttSell;
|
|
142
|
+
const exchangeTxn = turnover * r.exchangeTxn;
|
|
143
|
+
const sebi = turnover * config.sebi;
|
|
144
|
+
const stamp = buyVal * r.stampBuy;
|
|
145
|
+
const gst = (brokerage + exchangeTxn + sebi) * config.gst;
|
|
146
|
+
const dp = sellVal > 0 ? r.dpPerScrip : 0;
|
|
147
|
+
const totalCharges = brokerage + stt + exchangeTxn + sebi + stamp + gst + dp;
|
|
148
|
+
const grossPnl = sellVal - buyVal;
|
|
149
|
+
return {
|
|
150
|
+
turnover: round2(turnover),
|
|
151
|
+
brokerage: round2(brokerage),
|
|
152
|
+
stt: round2(stt),
|
|
153
|
+
exchangeTxn: round2(exchangeTxn),
|
|
154
|
+
sebi: round2(sebi),
|
|
155
|
+
stamp: round2(stamp),
|
|
156
|
+
gst: round2(gst),
|
|
157
|
+
dp: round2(dp),
|
|
158
|
+
totalCharges: round2(totalCharges),
|
|
159
|
+
grossPnl: round2(grossPnl),
|
|
160
|
+
netPnl: round2(grossPnl - totalCharges),
|
|
161
|
+
breakeven: input.qty === 0 ? 0 : round2(totalCharges / input.qty)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
exports.IN_DISCOUNT_BROKER = IN_DISCOUNT_BROKER;
|
|
166
|
+
exports.averagePrice = averagePrice;
|
|
167
|
+
exports.cagr = cagr;
|
|
168
|
+
exports.changePercent = changePercent;
|
|
169
|
+
exports.charges = charges;
|
|
170
|
+
exports.circuitLimits = circuitLimits;
|
|
171
|
+
exports.formatINR = formatINR;
|
|
172
|
+
exports.pnl = pnl;
|
|
173
|
+
exports.pnlPercent = pnlPercent;
|
|
174
|
+
exports.positionSize = positionSize;
|
|
175
|
+
exports.roundToTick = roundToTick;
|
|
176
|
+
exports.xirr = xirr;
|
|
177
|
+
//# sourceMappingURL=index.cjs.map
|
|
178
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["r"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/market\n * The money & mechanics toolkit every stock-market app re-implements.\n *\n * P&L, returns, CAGR, XIRR, tick-size rounding, circuit limits, position sizing\n * — plus a real Indian brokerage & charges calculator (STT, GST, SEBI, stamp,\n * exchange txn) with discount-broker presets.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/* ------------------------------------------------------------------ *\n * Formatting\n * ------------------------------------------------------------------ */\n\nexport interface FormatMoneyOptions {\n symbol?: string;\n decimals?: number;\n}\n\n/**\n * Format a number in the Indian numbering system (lakh / crore grouping).\n * @example formatINR(1234567.5) // \"₹12,34,567.50\"\n */\nexport function formatINR(amount: number, opts: FormatMoneyOptions = {}): string {\n const { symbol = \"₹\", decimals = 2 } = opts;\n const neg = amount < 0;\n const fixed = Math.abs(amount).toFixed(decimals);\n const [intPart = \"0\", frac = \"\"] = fixed.split(\".\");\n const last3 = intPart.slice(-3);\n const rest = intPart.slice(0, -3);\n const grouped = rest\n ? rest.replace(/\\B(?=(\\d{2})+(?!\\d))/g, \",\") + \",\" + last3\n : last3;\n return `${neg ? \"-\" : \"\"}${symbol}${grouped}${decimals > 0 ? \".\" + frac : \"\"}`;\n}\n\n/* ------------------------------------------------------------------ *\n * Returns & P&L\n * ------------------------------------------------------------------ */\n\n/** Absolute profit/loss for a round-trip. */\nexport function pnl(o: { buy: number; sell: number; qty: number }): number {\n return (o.sell - o.buy) * o.qty;\n}\n\n/** Percentage change from `reference` to `current` (e.g. LTP vs prev close). */\nexport function changePercent(current: number, reference: number): number {\n if (reference === 0) return 0;\n return ((current - reference) / reference) * 100;\n}\n\n/** Profit/loss as a percentage of the buy price. */\nexport function pnlPercent(o: { buy: number; sell: number }): number {\n return changePercent(o.sell, o.buy);\n}\n\n/** Compound Annual Growth Rate as a fraction (0.15 = 15%). */\nexport function cagr(begin: number, end: number, years: number): number {\n if (begin <= 0 || years <= 0) return NaN;\n return Math.pow(end / begin, 1 / years) - 1;\n}\n\nexport interface CashFlow {\n /** Negative = money out (investment), positive = money in (redemption). */\n amount: number;\n date: Date | string | number;\n}\n\nfunction toMillis(d: Date | string | number): number {\n if (d instanceof Date) return d.getTime();\n if (typeof d === \"number\") return d;\n return new Date(d).getTime();\n}\n\n/**\n * Extended Internal Rate of Return for irregularly-spaced cash flows.\n * Returns an annualised rate as a fraction. Uses Newton–Raphson.\n * @example xirr([{amount:-10000, date:\"2024-01-01\"}, {amount:12000, date:\"2025-01-01\"}]) // ~0.20\n */\nexport function xirr(flows: CashFlow[], guess = 0.1): number {\n if (flows.length < 2) return NaN;\n const cf = flows\n .map((f) => ({ amount: f.amount, t: toMillis(f.date) }))\n .sort((a, b) => a.t - b.t);\n const t0 = cf[0]!.t;\n const yearFrac = (t: number) => (t - t0) / (365 * 24 * 3600 * 1000);\n const npv = (r: number) =>\n cf.reduce((s, c) => s + c.amount / Math.pow(1 + r, yearFrac(c.t)), 0);\n const dnpv = (r: number) =>\n cf.reduce((s, c) => {\n const y = yearFrac(c.t);\n return s - (y * c.amount) / Math.pow(1 + r, y + 1);\n }, 0);\n\n let r = guess;\n for (let i = 0; i < 100; i++) {\n const f = npv(r);\n if (Math.abs(f) < 1e-7) return r;\n const d = dnpv(r);\n if (d === 0) break;\n const next = r - f / d;\n if (!isFinite(next)) break;\n if (Math.abs(next - r) < 1e-10) return next;\n r = next;\n }\n return r;\n}\n\n/* ------------------------------------------------------------------ *\n * Position mechanics\n * ------------------------------------------------------------------ */\n\n/** Volume-weighted average price across a set of trades. */\nexport function averagePrice(trades: { price: number; qty: number }[]): number {\n let qty = 0;\n let value = 0;\n for (const t of trades) {\n qty += t.qty;\n value += t.price * t.qty;\n }\n return qty === 0 ? 0 : value / qty;\n}\n\n/**\n * Risk-based position sizing. Returns the whole-share quantity so that a stop-out\n * costs at most `riskPercent` of capital.\n * @example positionSize({ capital: 100000, riskPercent: 1, entry: 500, stop: 480 }) // 50\n */\nexport function positionSize(o: {\n capital: number;\n riskPercent: number;\n entry: number;\n stop: number;\n}): number {\n const riskAmount = o.capital * (o.riskPercent / 100);\n const perShareRisk = Math.abs(o.entry - o.stop);\n if (perShareRisk === 0) return 0;\n return Math.floor(riskAmount / perShareRisk);\n}\n\n/** Round a price to the nearest exchange tick (default ₹0.05 for NSE equity). */\nexport function roundToTick(price: number, tick = 0.05): number {\n if (tick <= 0) return price;\n return Number((Math.round(price / tick) * tick).toFixed(4));\n}\n\n/** Upper & lower circuit price for a given previous close and band percent. */\nexport function circuitLimits(\n prevClose: number,\n percent: number,\n): { upper: number; lower: number } {\n const delta = prevClose * (percent / 100);\n return {\n upper: roundToTick(prevClose + delta),\n lower: roundToTick(prevClose - delta),\n };\n}\n\n/* ------------------------------------------------------------------ *\n * Brokerage & statutory charges (India)\n * ------------------------------------------------------------------ */\n\nexport type Segment = \"delivery\" | \"intraday\" | \"futures\" | \"options\";\n\nexport interface SegmentRates {\n /** Brokerage as a fraction of turnover per side (0.0003 = 0.03%). */\n brokeragePct: number;\n /** Per-order brokerage cap (₹). */\n brokerageCap: number;\n /** Flat per-order brokerage (₹) — overrides the pct/cap model when set. */\n brokerageFlat?: number;\n sttBuy: number;\n sttSell: number;\n exchangeTxn: number;\n stampBuy: number;\n /** Depository (DP) charge per scrip on the sell leg (₹). */\n dpPerScrip: number;\n}\n\nexport interface ChargeConfig {\n segments: Record<Segment, SegmentRates>;\n sebi: number;\n gst: number;\n}\n\n/**\n * Default rates approximating an Indian discount broker (Zerodha-style) as of\n * FY2024–25. Statutory rates change — override any field via the `config`\n * argument of {@link charges} and always verify against the live rate card.\n */\nexport const IN_DISCOUNT_BROKER: ChargeConfig = {\n segments: {\n delivery: {\n brokeragePct: 0,\n brokerageCap: 0,\n sttBuy: 0.001,\n sttSell: 0.001,\n exchangeTxn: 0.0000297,\n stampBuy: 0.00015,\n dpPerScrip: 13.5,\n },\n intraday: {\n brokeragePct: 0.0003,\n brokerageCap: 20,\n sttBuy: 0,\n sttSell: 0.00025,\n exchangeTxn: 0.0000297,\n stampBuy: 0.00003,\n dpPerScrip: 0,\n },\n futures: {\n brokeragePct: 0.0003,\n brokerageCap: 20,\n sttBuy: 0,\n sttSell: 0.0002,\n exchangeTxn: 0.0000173,\n stampBuy: 0.00002,\n dpPerScrip: 0,\n },\n options: {\n brokeragePct: 0,\n brokerageCap: 20,\n brokerageFlat: 20,\n sttBuy: 0,\n sttSell: 0.001,\n exchangeTxn: 0.0003503,\n stampBuy: 0.00003,\n dpPerScrip: 0,\n },\n },\n sebi: 0.000001, // ₹10 per crore\n gst: 0.18,\n};\n\nexport interface ChargeInput {\n segment: Segment;\n /** Buy price per unit. Omit / 0 for a sell-only leg. */\n buy: number;\n /** Sell price per unit. Omit / 0 for a buy-only leg. */\n sell: number;\n qty: number;\n}\n\nexport interface ChargeBreakdown {\n turnover: number;\n brokerage: number;\n stt: number;\n exchangeTxn: number;\n sebi: number;\n stamp: number;\n gst: number;\n dp: number;\n totalCharges: number;\n grossPnl: number;\n netPnl: number;\n /** Per-share price move needed just to break even on charges. */\n breakeven: number;\n}\n\nfunction round2(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\nfunction legBrokerage(turnover: number, r: SegmentRates): number {\n if (turnover <= 0) return 0;\n if (r.brokerageFlat !== undefined) return r.brokerageFlat;\n if (r.brokeragePct === 0) return 0;\n return Math.min(turnover * r.brokeragePct, r.brokerageCap);\n}\n\n/**\n * Full brokerage + statutory charges breakdown for a trade, Indian market.\n * @example\n * charges({ segment: \"intraday\", buy: 100, sell: 102, qty: 500 });\n * // { brokerage, stt, gst, sebi, stamp, exchangeTxn, totalCharges, netPnl, breakeven, ... }\n */\nexport function charges(\n input: ChargeInput,\n config: ChargeConfig = IN_DISCOUNT_BROKER,\n): ChargeBreakdown {\n const r = config.segments[input.segment];\n const buyVal = (input.buy || 0) * input.qty;\n const sellVal = (input.sell || 0) * input.qty;\n const turnover = buyVal + sellVal;\n\n const brokerage = legBrokerage(buyVal, r) + legBrokerage(sellVal, r);\n const stt = buyVal * r.sttBuy + sellVal * r.sttSell;\n const exchangeTxn = turnover * r.exchangeTxn;\n const sebi = turnover * config.sebi;\n const stamp = buyVal * r.stampBuy;\n const gst = (brokerage + exchangeTxn + sebi) * config.gst;\n const dp = sellVal > 0 ? r.dpPerScrip : 0;\n\n const totalCharges = brokerage + stt + exchangeTxn + sebi + stamp + gst + dp;\n const grossPnl = sellVal - buyVal;\n\n return {\n turnover: round2(turnover),\n brokerage: round2(brokerage),\n stt: round2(stt),\n exchangeTxn: round2(exchangeTxn),\n sebi: round2(sebi),\n stamp: round2(stamp),\n gst: round2(gst),\n dp: round2(dp),\n totalCharges: round2(totalCharges),\n grossPnl: round2(grossPnl),\n netPnl: round2(grossPnl - totalCharges),\n breakeven: input.qty === 0 ? 0 : round2(totalCharges / input.qty),\n };\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @lacspace/market
|
|
3
|
+
* The money & mechanics toolkit every stock-market app re-implements.
|
|
4
|
+
*
|
|
5
|
+
* P&L, returns, CAGR, XIRR, tick-size rounding, circuit limits, position sizing
|
|
6
|
+
* — plus a real Indian brokerage & charges calculator (STT, GST, SEBI, stamp,
|
|
7
|
+
* exchange txn) with discount-broker presets.
|
|
8
|
+
*
|
|
9
|
+
* Zero dependencies · isomorphic · fully typed.
|
|
10
|
+
*/
|
|
11
|
+
interface FormatMoneyOptions {
|
|
12
|
+
symbol?: string;
|
|
13
|
+
decimals?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Format a number in the Indian numbering system (lakh / crore grouping).
|
|
17
|
+
* @example formatINR(1234567.5) // "₹12,34,567.50"
|
|
18
|
+
*/
|
|
19
|
+
declare function formatINR(amount: number, opts?: FormatMoneyOptions): string;
|
|
20
|
+
/** Absolute profit/loss for a round-trip. */
|
|
21
|
+
declare function pnl(o: {
|
|
22
|
+
buy: number;
|
|
23
|
+
sell: number;
|
|
24
|
+
qty: number;
|
|
25
|
+
}): number;
|
|
26
|
+
/** Percentage change from `reference` to `current` (e.g. LTP vs prev close). */
|
|
27
|
+
declare function changePercent(current: number, reference: number): number;
|
|
28
|
+
/** Profit/loss as a percentage of the buy price. */
|
|
29
|
+
declare function pnlPercent(o: {
|
|
30
|
+
buy: number;
|
|
31
|
+
sell: number;
|
|
32
|
+
}): number;
|
|
33
|
+
/** Compound Annual Growth Rate as a fraction (0.15 = 15%). */
|
|
34
|
+
declare function cagr(begin: number, end: number, years: number): number;
|
|
35
|
+
interface CashFlow {
|
|
36
|
+
/** Negative = money out (investment), positive = money in (redemption). */
|
|
37
|
+
amount: number;
|
|
38
|
+
date: Date | string | number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extended Internal Rate of Return for irregularly-spaced cash flows.
|
|
42
|
+
* Returns an annualised rate as a fraction. Uses Newton–Raphson.
|
|
43
|
+
* @example xirr([{amount:-10000, date:"2024-01-01"}, {amount:12000, date:"2025-01-01"}]) // ~0.20
|
|
44
|
+
*/
|
|
45
|
+
declare function xirr(flows: CashFlow[], guess?: number): number;
|
|
46
|
+
/** Volume-weighted average price across a set of trades. */
|
|
47
|
+
declare function averagePrice(trades: {
|
|
48
|
+
price: number;
|
|
49
|
+
qty: number;
|
|
50
|
+
}[]): number;
|
|
51
|
+
/**
|
|
52
|
+
* Risk-based position sizing. Returns the whole-share quantity so that a stop-out
|
|
53
|
+
* costs at most `riskPercent` of capital.
|
|
54
|
+
* @example positionSize({ capital: 100000, riskPercent: 1, entry: 500, stop: 480 }) // 50
|
|
55
|
+
*/
|
|
56
|
+
declare function positionSize(o: {
|
|
57
|
+
capital: number;
|
|
58
|
+
riskPercent: number;
|
|
59
|
+
entry: number;
|
|
60
|
+
stop: number;
|
|
61
|
+
}): number;
|
|
62
|
+
/** Round a price to the nearest exchange tick (default ₹0.05 for NSE equity). */
|
|
63
|
+
declare function roundToTick(price: number, tick?: number): number;
|
|
64
|
+
/** Upper & lower circuit price for a given previous close and band percent. */
|
|
65
|
+
declare function circuitLimits(prevClose: number, percent: number): {
|
|
66
|
+
upper: number;
|
|
67
|
+
lower: number;
|
|
68
|
+
};
|
|
69
|
+
type Segment = "delivery" | "intraday" | "futures" | "options";
|
|
70
|
+
interface SegmentRates {
|
|
71
|
+
/** Brokerage as a fraction of turnover per side (0.0003 = 0.03%). */
|
|
72
|
+
brokeragePct: number;
|
|
73
|
+
/** Per-order brokerage cap (₹). */
|
|
74
|
+
brokerageCap: number;
|
|
75
|
+
/** Flat per-order brokerage (₹) — overrides the pct/cap model when set. */
|
|
76
|
+
brokerageFlat?: number;
|
|
77
|
+
sttBuy: number;
|
|
78
|
+
sttSell: number;
|
|
79
|
+
exchangeTxn: number;
|
|
80
|
+
stampBuy: number;
|
|
81
|
+
/** Depository (DP) charge per scrip on the sell leg (₹). */
|
|
82
|
+
dpPerScrip: number;
|
|
83
|
+
}
|
|
84
|
+
interface ChargeConfig {
|
|
85
|
+
segments: Record<Segment, SegmentRates>;
|
|
86
|
+
sebi: number;
|
|
87
|
+
gst: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Default rates approximating an Indian discount broker (Zerodha-style) as of
|
|
91
|
+
* FY2024–25. Statutory rates change — override any field via the `config`
|
|
92
|
+
* argument of {@link charges} and always verify against the live rate card.
|
|
93
|
+
*/
|
|
94
|
+
declare const IN_DISCOUNT_BROKER: ChargeConfig;
|
|
95
|
+
interface ChargeInput {
|
|
96
|
+
segment: Segment;
|
|
97
|
+
/** Buy price per unit. Omit / 0 for a sell-only leg. */
|
|
98
|
+
buy: number;
|
|
99
|
+
/** Sell price per unit. Omit / 0 for a buy-only leg. */
|
|
100
|
+
sell: number;
|
|
101
|
+
qty: number;
|
|
102
|
+
}
|
|
103
|
+
interface ChargeBreakdown {
|
|
104
|
+
turnover: number;
|
|
105
|
+
brokerage: number;
|
|
106
|
+
stt: number;
|
|
107
|
+
exchangeTxn: number;
|
|
108
|
+
sebi: number;
|
|
109
|
+
stamp: number;
|
|
110
|
+
gst: number;
|
|
111
|
+
dp: number;
|
|
112
|
+
totalCharges: number;
|
|
113
|
+
grossPnl: number;
|
|
114
|
+
netPnl: number;
|
|
115
|
+
/** Per-share price move needed just to break even on charges. */
|
|
116
|
+
breakeven: number;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Full brokerage + statutory charges breakdown for a trade, Indian market.
|
|
120
|
+
* @example
|
|
121
|
+
* charges({ segment: "intraday", buy: 100, sell: 102, qty: 500 });
|
|
122
|
+
* // { brokerage, stt, gst, sebi, stamp, exchangeTxn, totalCharges, netPnl, breakeven, ... }
|
|
123
|
+
*/
|
|
124
|
+
declare function charges(input: ChargeInput, config?: ChargeConfig): ChargeBreakdown;
|
|
125
|
+
|
|
126
|
+
export { type CashFlow, type ChargeBreakdown, type ChargeConfig, type ChargeInput, type FormatMoneyOptions, IN_DISCOUNT_BROKER, type Segment, type SegmentRates, averagePrice, cagr, changePercent, charges, circuitLimits, formatINR, pnl, pnlPercent, positionSize, roundToTick, xirr };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @lacspace/market
|
|
3
|
+
* The money & mechanics toolkit every stock-market app re-implements.
|
|
4
|
+
*
|
|
5
|
+
* P&L, returns, CAGR, XIRR, tick-size rounding, circuit limits, position sizing
|
|
6
|
+
* — plus a real Indian brokerage & charges calculator (STT, GST, SEBI, stamp,
|
|
7
|
+
* exchange txn) with discount-broker presets.
|
|
8
|
+
*
|
|
9
|
+
* Zero dependencies · isomorphic · fully typed.
|
|
10
|
+
*/
|
|
11
|
+
interface FormatMoneyOptions {
|
|
12
|
+
symbol?: string;
|
|
13
|
+
decimals?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Format a number in the Indian numbering system (lakh / crore grouping).
|
|
17
|
+
* @example formatINR(1234567.5) // "₹12,34,567.50"
|
|
18
|
+
*/
|
|
19
|
+
declare function formatINR(amount: number, opts?: FormatMoneyOptions): string;
|
|
20
|
+
/** Absolute profit/loss for a round-trip. */
|
|
21
|
+
declare function pnl(o: {
|
|
22
|
+
buy: number;
|
|
23
|
+
sell: number;
|
|
24
|
+
qty: number;
|
|
25
|
+
}): number;
|
|
26
|
+
/** Percentage change from `reference` to `current` (e.g. LTP vs prev close). */
|
|
27
|
+
declare function changePercent(current: number, reference: number): number;
|
|
28
|
+
/** Profit/loss as a percentage of the buy price. */
|
|
29
|
+
declare function pnlPercent(o: {
|
|
30
|
+
buy: number;
|
|
31
|
+
sell: number;
|
|
32
|
+
}): number;
|
|
33
|
+
/** Compound Annual Growth Rate as a fraction (0.15 = 15%). */
|
|
34
|
+
declare function cagr(begin: number, end: number, years: number): number;
|
|
35
|
+
interface CashFlow {
|
|
36
|
+
/** Negative = money out (investment), positive = money in (redemption). */
|
|
37
|
+
amount: number;
|
|
38
|
+
date: Date | string | number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extended Internal Rate of Return for irregularly-spaced cash flows.
|
|
42
|
+
* Returns an annualised rate as a fraction. Uses Newton–Raphson.
|
|
43
|
+
* @example xirr([{amount:-10000, date:"2024-01-01"}, {amount:12000, date:"2025-01-01"}]) // ~0.20
|
|
44
|
+
*/
|
|
45
|
+
declare function xirr(flows: CashFlow[], guess?: number): number;
|
|
46
|
+
/** Volume-weighted average price across a set of trades. */
|
|
47
|
+
declare function averagePrice(trades: {
|
|
48
|
+
price: number;
|
|
49
|
+
qty: number;
|
|
50
|
+
}[]): number;
|
|
51
|
+
/**
|
|
52
|
+
* Risk-based position sizing. Returns the whole-share quantity so that a stop-out
|
|
53
|
+
* costs at most `riskPercent` of capital.
|
|
54
|
+
* @example positionSize({ capital: 100000, riskPercent: 1, entry: 500, stop: 480 }) // 50
|
|
55
|
+
*/
|
|
56
|
+
declare function positionSize(o: {
|
|
57
|
+
capital: number;
|
|
58
|
+
riskPercent: number;
|
|
59
|
+
entry: number;
|
|
60
|
+
stop: number;
|
|
61
|
+
}): number;
|
|
62
|
+
/** Round a price to the nearest exchange tick (default ₹0.05 for NSE equity). */
|
|
63
|
+
declare function roundToTick(price: number, tick?: number): number;
|
|
64
|
+
/** Upper & lower circuit price for a given previous close and band percent. */
|
|
65
|
+
declare function circuitLimits(prevClose: number, percent: number): {
|
|
66
|
+
upper: number;
|
|
67
|
+
lower: number;
|
|
68
|
+
};
|
|
69
|
+
type Segment = "delivery" | "intraday" | "futures" | "options";
|
|
70
|
+
interface SegmentRates {
|
|
71
|
+
/** Brokerage as a fraction of turnover per side (0.0003 = 0.03%). */
|
|
72
|
+
brokeragePct: number;
|
|
73
|
+
/** Per-order brokerage cap (₹). */
|
|
74
|
+
brokerageCap: number;
|
|
75
|
+
/** Flat per-order brokerage (₹) — overrides the pct/cap model when set. */
|
|
76
|
+
brokerageFlat?: number;
|
|
77
|
+
sttBuy: number;
|
|
78
|
+
sttSell: number;
|
|
79
|
+
exchangeTxn: number;
|
|
80
|
+
stampBuy: number;
|
|
81
|
+
/** Depository (DP) charge per scrip on the sell leg (₹). */
|
|
82
|
+
dpPerScrip: number;
|
|
83
|
+
}
|
|
84
|
+
interface ChargeConfig {
|
|
85
|
+
segments: Record<Segment, SegmentRates>;
|
|
86
|
+
sebi: number;
|
|
87
|
+
gst: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Default rates approximating an Indian discount broker (Zerodha-style) as of
|
|
91
|
+
* FY2024–25. Statutory rates change — override any field via the `config`
|
|
92
|
+
* argument of {@link charges} and always verify against the live rate card.
|
|
93
|
+
*/
|
|
94
|
+
declare const IN_DISCOUNT_BROKER: ChargeConfig;
|
|
95
|
+
interface ChargeInput {
|
|
96
|
+
segment: Segment;
|
|
97
|
+
/** Buy price per unit. Omit / 0 for a sell-only leg. */
|
|
98
|
+
buy: number;
|
|
99
|
+
/** Sell price per unit. Omit / 0 for a buy-only leg. */
|
|
100
|
+
sell: number;
|
|
101
|
+
qty: number;
|
|
102
|
+
}
|
|
103
|
+
interface ChargeBreakdown {
|
|
104
|
+
turnover: number;
|
|
105
|
+
brokerage: number;
|
|
106
|
+
stt: number;
|
|
107
|
+
exchangeTxn: number;
|
|
108
|
+
sebi: number;
|
|
109
|
+
stamp: number;
|
|
110
|
+
gst: number;
|
|
111
|
+
dp: number;
|
|
112
|
+
totalCharges: number;
|
|
113
|
+
grossPnl: number;
|
|
114
|
+
netPnl: number;
|
|
115
|
+
/** Per-share price move needed just to break even on charges. */
|
|
116
|
+
breakeven: number;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Full brokerage + statutory charges breakdown for a trade, Indian market.
|
|
120
|
+
* @example
|
|
121
|
+
* charges({ segment: "intraday", buy: 100, sell: 102, qty: 500 });
|
|
122
|
+
* // { brokerage, stt, gst, sebi, stamp, exchangeTxn, totalCharges, netPnl, breakeven, ... }
|
|
123
|
+
*/
|
|
124
|
+
declare function charges(input: ChargeInput, config?: ChargeConfig): ChargeBreakdown;
|
|
125
|
+
|
|
126
|
+
export { type CashFlow, type ChargeBreakdown, type ChargeConfig, type ChargeInput, type FormatMoneyOptions, IN_DISCOUNT_BROKER, type Segment, type SegmentRates, averagePrice, cagr, changePercent, charges, circuitLimits, formatINR, pnl, pnlPercent, positionSize, roundToTick, xirr };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function formatINR(amount, opts = {}) {
|
|
3
|
+
const { symbol = "\u20B9", decimals = 2 } = opts;
|
|
4
|
+
const neg = amount < 0;
|
|
5
|
+
const fixed = Math.abs(amount).toFixed(decimals);
|
|
6
|
+
const [intPart = "0", frac = ""] = fixed.split(".");
|
|
7
|
+
const last3 = intPart.slice(-3);
|
|
8
|
+
const rest = intPart.slice(0, -3);
|
|
9
|
+
const grouped = rest ? rest.replace(/\B(?=(\d{2})+(?!\d))/g, ",") + "," + last3 : last3;
|
|
10
|
+
return `${neg ? "-" : ""}${symbol}${grouped}${decimals > 0 ? "." + frac : ""}`;
|
|
11
|
+
}
|
|
12
|
+
function pnl(o) {
|
|
13
|
+
return (o.sell - o.buy) * o.qty;
|
|
14
|
+
}
|
|
15
|
+
function changePercent(current, reference) {
|
|
16
|
+
if (reference === 0) return 0;
|
|
17
|
+
return (current - reference) / reference * 100;
|
|
18
|
+
}
|
|
19
|
+
function pnlPercent(o) {
|
|
20
|
+
return changePercent(o.sell, o.buy);
|
|
21
|
+
}
|
|
22
|
+
function cagr(begin, end, years) {
|
|
23
|
+
if (begin <= 0 || years <= 0) return NaN;
|
|
24
|
+
return Math.pow(end / begin, 1 / years) - 1;
|
|
25
|
+
}
|
|
26
|
+
function toMillis(d) {
|
|
27
|
+
if (d instanceof Date) return d.getTime();
|
|
28
|
+
if (typeof d === "number") return d;
|
|
29
|
+
return new Date(d).getTime();
|
|
30
|
+
}
|
|
31
|
+
function xirr(flows, guess = 0.1) {
|
|
32
|
+
if (flows.length < 2) return NaN;
|
|
33
|
+
const cf = flows.map((f) => ({ amount: f.amount, t: toMillis(f.date) })).sort((a, b) => a.t - b.t);
|
|
34
|
+
const t0 = cf[0].t;
|
|
35
|
+
const yearFrac = (t) => (t - t0) / (365 * 24 * 3600 * 1e3);
|
|
36
|
+
const npv = (r2) => cf.reduce((s, c) => s + c.amount / Math.pow(1 + r2, yearFrac(c.t)), 0);
|
|
37
|
+
const dnpv = (r2) => cf.reduce((s, c) => {
|
|
38
|
+
const y = yearFrac(c.t);
|
|
39
|
+
return s - y * c.amount / Math.pow(1 + r2, y + 1);
|
|
40
|
+
}, 0);
|
|
41
|
+
let r = guess;
|
|
42
|
+
for (let i = 0; i < 100; i++) {
|
|
43
|
+
const f = npv(r);
|
|
44
|
+
if (Math.abs(f) < 1e-7) return r;
|
|
45
|
+
const d = dnpv(r);
|
|
46
|
+
if (d === 0) break;
|
|
47
|
+
const next = r - f / d;
|
|
48
|
+
if (!isFinite(next)) break;
|
|
49
|
+
if (Math.abs(next - r) < 1e-10) return next;
|
|
50
|
+
r = next;
|
|
51
|
+
}
|
|
52
|
+
return r;
|
|
53
|
+
}
|
|
54
|
+
function averagePrice(trades) {
|
|
55
|
+
let qty = 0;
|
|
56
|
+
let value = 0;
|
|
57
|
+
for (const t of trades) {
|
|
58
|
+
qty += t.qty;
|
|
59
|
+
value += t.price * t.qty;
|
|
60
|
+
}
|
|
61
|
+
return qty === 0 ? 0 : value / qty;
|
|
62
|
+
}
|
|
63
|
+
function positionSize(o) {
|
|
64
|
+
const riskAmount = o.capital * (o.riskPercent / 100);
|
|
65
|
+
const perShareRisk = Math.abs(o.entry - o.stop);
|
|
66
|
+
if (perShareRisk === 0) return 0;
|
|
67
|
+
return Math.floor(riskAmount / perShareRisk);
|
|
68
|
+
}
|
|
69
|
+
function roundToTick(price, tick = 0.05) {
|
|
70
|
+
if (tick <= 0) return price;
|
|
71
|
+
return Number((Math.round(price / tick) * tick).toFixed(4));
|
|
72
|
+
}
|
|
73
|
+
function circuitLimits(prevClose, percent) {
|
|
74
|
+
const delta = prevClose * (percent / 100);
|
|
75
|
+
return {
|
|
76
|
+
upper: roundToTick(prevClose + delta),
|
|
77
|
+
lower: roundToTick(prevClose - delta)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
var IN_DISCOUNT_BROKER = {
|
|
81
|
+
segments: {
|
|
82
|
+
delivery: {
|
|
83
|
+
brokeragePct: 0,
|
|
84
|
+
brokerageCap: 0,
|
|
85
|
+
sttBuy: 1e-3,
|
|
86
|
+
sttSell: 1e-3,
|
|
87
|
+
exchangeTxn: 297e-7,
|
|
88
|
+
stampBuy: 15e-5,
|
|
89
|
+
dpPerScrip: 13.5
|
|
90
|
+
},
|
|
91
|
+
intraday: {
|
|
92
|
+
brokeragePct: 3e-4,
|
|
93
|
+
brokerageCap: 20,
|
|
94
|
+
sttBuy: 0,
|
|
95
|
+
sttSell: 25e-5,
|
|
96
|
+
exchangeTxn: 297e-7,
|
|
97
|
+
stampBuy: 3e-5,
|
|
98
|
+
dpPerScrip: 0
|
|
99
|
+
},
|
|
100
|
+
futures: {
|
|
101
|
+
brokeragePct: 3e-4,
|
|
102
|
+
brokerageCap: 20,
|
|
103
|
+
sttBuy: 0,
|
|
104
|
+
sttSell: 2e-4,
|
|
105
|
+
exchangeTxn: 173e-7,
|
|
106
|
+
stampBuy: 2e-5,
|
|
107
|
+
dpPerScrip: 0
|
|
108
|
+
},
|
|
109
|
+
options: {
|
|
110
|
+
brokeragePct: 0,
|
|
111
|
+
brokerageCap: 20,
|
|
112
|
+
brokerageFlat: 20,
|
|
113
|
+
sttBuy: 0,
|
|
114
|
+
sttSell: 1e-3,
|
|
115
|
+
exchangeTxn: 3503e-7,
|
|
116
|
+
stampBuy: 3e-5,
|
|
117
|
+
dpPerScrip: 0
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
sebi: 1e-6,
|
|
121
|
+
// ₹10 per crore
|
|
122
|
+
gst: 0.18
|
|
123
|
+
};
|
|
124
|
+
function round2(n) {
|
|
125
|
+
return Math.round(n * 100) / 100;
|
|
126
|
+
}
|
|
127
|
+
function legBrokerage(turnover, r) {
|
|
128
|
+
if (turnover <= 0) return 0;
|
|
129
|
+
if (r.brokerageFlat !== void 0) return r.brokerageFlat;
|
|
130
|
+
if (r.brokeragePct === 0) return 0;
|
|
131
|
+
return Math.min(turnover * r.brokeragePct, r.brokerageCap);
|
|
132
|
+
}
|
|
133
|
+
function charges(input, config = IN_DISCOUNT_BROKER) {
|
|
134
|
+
const r = config.segments[input.segment];
|
|
135
|
+
const buyVal = (input.buy || 0) * input.qty;
|
|
136
|
+
const sellVal = (input.sell || 0) * input.qty;
|
|
137
|
+
const turnover = buyVal + sellVal;
|
|
138
|
+
const brokerage = legBrokerage(buyVal, r) + legBrokerage(sellVal, r);
|
|
139
|
+
const stt = buyVal * r.sttBuy + sellVal * r.sttSell;
|
|
140
|
+
const exchangeTxn = turnover * r.exchangeTxn;
|
|
141
|
+
const sebi = turnover * config.sebi;
|
|
142
|
+
const stamp = buyVal * r.stampBuy;
|
|
143
|
+
const gst = (brokerage + exchangeTxn + sebi) * config.gst;
|
|
144
|
+
const dp = sellVal > 0 ? r.dpPerScrip : 0;
|
|
145
|
+
const totalCharges = brokerage + stt + exchangeTxn + sebi + stamp + gst + dp;
|
|
146
|
+
const grossPnl = sellVal - buyVal;
|
|
147
|
+
return {
|
|
148
|
+
turnover: round2(turnover),
|
|
149
|
+
brokerage: round2(brokerage),
|
|
150
|
+
stt: round2(stt),
|
|
151
|
+
exchangeTxn: round2(exchangeTxn),
|
|
152
|
+
sebi: round2(sebi),
|
|
153
|
+
stamp: round2(stamp),
|
|
154
|
+
gst: round2(gst),
|
|
155
|
+
dp: round2(dp),
|
|
156
|
+
totalCharges: round2(totalCharges),
|
|
157
|
+
grossPnl: round2(grossPnl),
|
|
158
|
+
netPnl: round2(grossPnl - totalCharges),
|
|
159
|
+
breakeven: input.qty === 0 ? 0 : round2(totalCharges / input.qty)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { IN_DISCOUNT_BROKER, averagePrice, cagr, changePercent, charges, circuitLimits, formatINR, pnl, pnlPercent, positionSize, roundToTick, xirr };
|
|
164
|
+
//# sourceMappingURL=index.js.map
|
|
165
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["r"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/market\n * The money & mechanics toolkit every stock-market app re-implements.\n *\n * P&L, returns, CAGR, XIRR, tick-size rounding, circuit limits, position sizing\n * — plus a real Indian brokerage & charges calculator (STT, GST, SEBI, stamp,\n * exchange txn) with discount-broker presets.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/* ------------------------------------------------------------------ *\n * Formatting\n * ------------------------------------------------------------------ */\n\nexport interface FormatMoneyOptions {\n symbol?: string;\n decimals?: number;\n}\n\n/**\n * Format a number in the Indian numbering system (lakh / crore grouping).\n * @example formatINR(1234567.5) // \"₹12,34,567.50\"\n */\nexport function formatINR(amount: number, opts: FormatMoneyOptions = {}): string {\n const { symbol = \"₹\", decimals = 2 } = opts;\n const neg = amount < 0;\n const fixed = Math.abs(amount).toFixed(decimals);\n const [intPart = \"0\", frac = \"\"] = fixed.split(\".\");\n const last3 = intPart.slice(-3);\n const rest = intPart.slice(0, -3);\n const grouped = rest\n ? rest.replace(/\\B(?=(\\d{2})+(?!\\d))/g, \",\") + \",\" + last3\n : last3;\n return `${neg ? \"-\" : \"\"}${symbol}${grouped}${decimals > 0 ? \".\" + frac : \"\"}`;\n}\n\n/* ------------------------------------------------------------------ *\n * Returns & P&L\n * ------------------------------------------------------------------ */\n\n/** Absolute profit/loss for a round-trip. */\nexport function pnl(o: { buy: number; sell: number; qty: number }): number {\n return (o.sell - o.buy) * o.qty;\n}\n\n/** Percentage change from `reference` to `current` (e.g. LTP vs prev close). */\nexport function changePercent(current: number, reference: number): number {\n if (reference === 0) return 0;\n return ((current - reference) / reference) * 100;\n}\n\n/** Profit/loss as a percentage of the buy price. */\nexport function pnlPercent(o: { buy: number; sell: number }): number {\n return changePercent(o.sell, o.buy);\n}\n\n/** Compound Annual Growth Rate as a fraction (0.15 = 15%). */\nexport function cagr(begin: number, end: number, years: number): number {\n if (begin <= 0 || years <= 0) return NaN;\n return Math.pow(end / begin, 1 / years) - 1;\n}\n\nexport interface CashFlow {\n /** Negative = money out (investment), positive = money in (redemption). */\n amount: number;\n date: Date | string | number;\n}\n\nfunction toMillis(d: Date | string | number): number {\n if (d instanceof Date) return d.getTime();\n if (typeof d === \"number\") return d;\n return new Date(d).getTime();\n}\n\n/**\n * Extended Internal Rate of Return for irregularly-spaced cash flows.\n * Returns an annualised rate as a fraction. Uses Newton–Raphson.\n * @example xirr([{amount:-10000, date:\"2024-01-01\"}, {amount:12000, date:\"2025-01-01\"}]) // ~0.20\n */\nexport function xirr(flows: CashFlow[], guess = 0.1): number {\n if (flows.length < 2) return NaN;\n const cf = flows\n .map((f) => ({ amount: f.amount, t: toMillis(f.date) }))\n .sort((a, b) => a.t - b.t);\n const t0 = cf[0]!.t;\n const yearFrac = (t: number) => (t - t0) / (365 * 24 * 3600 * 1000);\n const npv = (r: number) =>\n cf.reduce((s, c) => s + c.amount / Math.pow(1 + r, yearFrac(c.t)), 0);\n const dnpv = (r: number) =>\n cf.reduce((s, c) => {\n const y = yearFrac(c.t);\n return s - (y * c.amount) / Math.pow(1 + r, y + 1);\n }, 0);\n\n let r = guess;\n for (let i = 0; i < 100; i++) {\n const f = npv(r);\n if (Math.abs(f) < 1e-7) return r;\n const d = dnpv(r);\n if (d === 0) break;\n const next = r - f / d;\n if (!isFinite(next)) break;\n if (Math.abs(next - r) < 1e-10) return next;\n r = next;\n }\n return r;\n}\n\n/* ------------------------------------------------------------------ *\n * Position mechanics\n * ------------------------------------------------------------------ */\n\n/** Volume-weighted average price across a set of trades. */\nexport function averagePrice(trades: { price: number; qty: number }[]): number {\n let qty = 0;\n let value = 0;\n for (const t of trades) {\n qty += t.qty;\n value += t.price * t.qty;\n }\n return qty === 0 ? 0 : value / qty;\n}\n\n/**\n * Risk-based position sizing. Returns the whole-share quantity so that a stop-out\n * costs at most `riskPercent` of capital.\n * @example positionSize({ capital: 100000, riskPercent: 1, entry: 500, stop: 480 }) // 50\n */\nexport function positionSize(o: {\n capital: number;\n riskPercent: number;\n entry: number;\n stop: number;\n}): number {\n const riskAmount = o.capital * (o.riskPercent / 100);\n const perShareRisk = Math.abs(o.entry - o.stop);\n if (perShareRisk === 0) return 0;\n return Math.floor(riskAmount / perShareRisk);\n}\n\n/** Round a price to the nearest exchange tick (default ₹0.05 for NSE equity). */\nexport function roundToTick(price: number, tick = 0.05): number {\n if (tick <= 0) return price;\n return Number((Math.round(price / tick) * tick).toFixed(4));\n}\n\n/** Upper & lower circuit price for a given previous close and band percent. */\nexport function circuitLimits(\n prevClose: number,\n percent: number,\n): { upper: number; lower: number } {\n const delta = prevClose * (percent / 100);\n return {\n upper: roundToTick(prevClose + delta),\n lower: roundToTick(prevClose - delta),\n };\n}\n\n/* ------------------------------------------------------------------ *\n * Brokerage & statutory charges (India)\n * ------------------------------------------------------------------ */\n\nexport type Segment = \"delivery\" | \"intraday\" | \"futures\" | \"options\";\n\nexport interface SegmentRates {\n /** Brokerage as a fraction of turnover per side (0.0003 = 0.03%). */\n brokeragePct: number;\n /** Per-order brokerage cap (₹). */\n brokerageCap: number;\n /** Flat per-order brokerage (₹) — overrides the pct/cap model when set. */\n brokerageFlat?: number;\n sttBuy: number;\n sttSell: number;\n exchangeTxn: number;\n stampBuy: number;\n /** Depository (DP) charge per scrip on the sell leg (₹). */\n dpPerScrip: number;\n}\n\nexport interface ChargeConfig {\n segments: Record<Segment, SegmentRates>;\n sebi: number;\n gst: number;\n}\n\n/**\n * Default rates approximating an Indian discount broker (Zerodha-style) as of\n * FY2024–25. Statutory rates change — override any field via the `config`\n * argument of {@link charges} and always verify against the live rate card.\n */\nexport const IN_DISCOUNT_BROKER: ChargeConfig = {\n segments: {\n delivery: {\n brokeragePct: 0,\n brokerageCap: 0,\n sttBuy: 0.001,\n sttSell: 0.001,\n exchangeTxn: 0.0000297,\n stampBuy: 0.00015,\n dpPerScrip: 13.5,\n },\n intraday: {\n brokeragePct: 0.0003,\n brokerageCap: 20,\n sttBuy: 0,\n sttSell: 0.00025,\n exchangeTxn: 0.0000297,\n stampBuy: 0.00003,\n dpPerScrip: 0,\n },\n futures: {\n brokeragePct: 0.0003,\n brokerageCap: 20,\n sttBuy: 0,\n sttSell: 0.0002,\n exchangeTxn: 0.0000173,\n stampBuy: 0.00002,\n dpPerScrip: 0,\n },\n options: {\n brokeragePct: 0,\n brokerageCap: 20,\n brokerageFlat: 20,\n sttBuy: 0,\n sttSell: 0.001,\n exchangeTxn: 0.0003503,\n stampBuy: 0.00003,\n dpPerScrip: 0,\n },\n },\n sebi: 0.000001, // ₹10 per crore\n gst: 0.18,\n};\n\nexport interface ChargeInput {\n segment: Segment;\n /** Buy price per unit. Omit / 0 for a sell-only leg. */\n buy: number;\n /** Sell price per unit. Omit / 0 for a buy-only leg. */\n sell: number;\n qty: number;\n}\n\nexport interface ChargeBreakdown {\n turnover: number;\n brokerage: number;\n stt: number;\n exchangeTxn: number;\n sebi: number;\n stamp: number;\n gst: number;\n dp: number;\n totalCharges: number;\n grossPnl: number;\n netPnl: number;\n /** Per-share price move needed just to break even on charges. */\n breakeven: number;\n}\n\nfunction round2(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\nfunction legBrokerage(turnover: number, r: SegmentRates): number {\n if (turnover <= 0) return 0;\n if (r.brokerageFlat !== undefined) return r.brokerageFlat;\n if (r.brokeragePct === 0) return 0;\n return Math.min(turnover * r.brokeragePct, r.brokerageCap);\n}\n\n/**\n * Full brokerage + statutory charges breakdown for a trade, Indian market.\n * @example\n * charges({ segment: \"intraday\", buy: 100, sell: 102, qty: 500 });\n * // { brokerage, stt, gst, sebi, stamp, exchangeTxn, totalCharges, netPnl, breakeven, ... }\n */\nexport function charges(\n input: ChargeInput,\n config: ChargeConfig = IN_DISCOUNT_BROKER,\n): ChargeBreakdown {\n const r = config.segments[input.segment];\n const buyVal = (input.buy || 0) * input.qty;\n const sellVal = (input.sell || 0) * input.qty;\n const turnover = buyVal + sellVal;\n\n const brokerage = legBrokerage(buyVal, r) + legBrokerage(sellVal, r);\n const stt = buyVal * r.sttBuy + sellVal * r.sttSell;\n const exchangeTxn = turnover * r.exchangeTxn;\n const sebi = turnover * config.sebi;\n const stamp = buyVal * r.stampBuy;\n const gst = (brokerage + exchangeTxn + sebi) * config.gst;\n const dp = sellVal > 0 ? r.dpPerScrip : 0;\n\n const totalCharges = brokerage + stt + exchangeTxn + sebi + stamp + gst + dp;\n const grossPnl = sellVal - buyVal;\n\n return {\n turnover: round2(turnover),\n brokerage: round2(brokerage),\n stt: round2(stt),\n exchangeTxn: round2(exchangeTxn),\n sebi: round2(sebi),\n stamp: round2(stamp),\n gst: round2(gst),\n dp: round2(dp),\n totalCharges: round2(totalCharges),\n grossPnl: round2(grossPnl),\n netPnl: round2(grossPnl - totalCharges),\n breakeven: input.qty === 0 ? 0 : round2(totalCharges / input.qty),\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lacspace/market",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Stock-market money math — P&L, returns, CAGR, XIRR, tick-size rounding, circuit limits, position sizing and an Indian brokerage & charges calculator (STT, GST, SEBI, stamp). Zero-dependency.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"stock-market",
|
|
31
|
+
"trading",
|
|
32
|
+
"brokerage-calculator",
|
|
33
|
+
"stt",
|
|
34
|
+
"pnl",
|
|
35
|
+
"xirr",
|
|
36
|
+
"cagr",
|
|
37
|
+
"position-sizing",
|
|
38
|
+
"circuit-limits",
|
|
39
|
+
"nse",
|
|
40
|
+
"bse",
|
|
41
|
+
"typescript"
|
|
42
|
+
],
|
|
43
|
+
"author": "Lacspace <contact@lacspace.com>",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"homepage": "https://lacspace.com/packages",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/lacspace/npm-packages.git",
|
|
49
|
+
"directory": "market"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/lacspace/npm-packages/issues"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18"
|
|
56
|
+
}
|
|
57
|
+
}
|