diff --git a/frontend/assets/settings-store.js b/frontend/assets/settings-store.js new file mode 100644 index 0000000..9ae8c44 --- /dev/null +++ b/frontend/assets/settings-store.js @@ -0,0 +1,82 @@ +/** + * App-wide settings — commission config, etc. + * Exposed as window.SettingsStore. localStorage key "optionsPricer:settings". + * + * Default plan = IBKR Fixed / Lite: + * $0.65 per contract, $1.00 per-order minimum, no per-order maximum. + * Source: https://www.interactivebrokers.com/en/pricing/commissions-options.php + */ +(function () { + "use strict"; + const KEY = "optionsPricer:settings"; + const VERSION = 1; + + const DEFAULTS = { + v: VERSION, + commission: { + plan: "ibkr-fixed", // ibkr-fixed | ibkr-tiered | custom + perContract: 0.65, // $ per option contract + perOrderMin: 1.00, // $ minimum per order + perOrderMax: 0, // $ cap per order; 0 = none + applyPerLeg: false, // true = each leg charged separately; false = whole position counts as one order + }, + }; + + function clone(o) { return JSON.parse(JSON.stringify(o)); } + + function load() { + try { + const raw = localStorage.getItem(KEY); + if (!raw) return clone(DEFAULTS); + const obj = JSON.parse(raw); + if (!obj || obj.v !== VERSION) return clone(DEFAULTS); + // merge over defaults so missing fields fall back + const out = clone(DEFAULTS); + if (obj.commission && typeof obj.commission === "object") { + Object.assign(out.commission, obj.commission); + } + return out; + } catch { return clone(DEFAULTS); } + } + + function save(s) { + const out = clone(DEFAULTS); + if (s && s.commission) Object.assign(out.commission, s.commission); + try { localStorage.setItem(KEY, JSON.stringify(out)); } catch (e) { console.warn("[SettingsStore] save failed:", e); } + return out; + } + + function reset() { + try { localStorage.removeItem(KEY); } catch {} + return clone(DEFAULTS); + } + + /** + * Estimate commission for a position. + * legs: [{ qty }, ...] (only qty matters) + * Returns { entry, exit, roundTrip } in dollars. + */ + function estimate(legs) { + const c = load().commission; + if (!Array.isArray(legs) || legs.length === 0) return { entry: 0, exit: 0, roundTrip: 0 }; + + const orderCost = (contracts) => { + let v = contracts * c.perContract; + if (c.perOrderMin > 0) v = Math.max(v, c.perOrderMin); + if (c.perOrderMax > 0) v = Math.min(v, c.perOrderMax); + return v; + }; + + let entry; + if (c.applyPerLeg) { + entry = legs.reduce((s, l) => s + orderCost(Math.max(1, Math.round(l.qty || 1))), 0); + } else { + const total = legs.reduce((s, l) => s + Math.max(1, Math.round(l.qty || 1)), 0); + entry = orderCost(total); + } + const exit = entry; // assume same fill style on close + return { entry, exit, roundTrip: entry + exit }; + } + + window.SettingsStore = { KEY, VERSION, DEFAULTS: clone(DEFAULTS), load, save, reset, estimate }; +})(); diff --git a/frontend/chain.html b/frontend/chain.html index dd76fe9..7d5e087 100644 --- a/frontend/chain.html +++ b/frontend/chain.html @@ -129,6 +129,18 @@ +
Go to Strategy, build a position, and click Enter Position.
+| Sym | Strategy | Legs | +Entered $ | +Current $ | +Comm. (RT) | +Gross P/L | +Net P/L | +P/L % | +Opened | +Status | ++ | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| + | + | + | + | + | + | + | + | + | + | + | + | + |
+ Used when computing Net P/L on the Positions page. + Defaults match Interactive Brokers Fixed / IBKR Lite for US equity options: + $0.65 per contract, $1.00 minimum per order + (IBKR docs). +
+ +