Files
options-pricer/frontend/assets/settings-store.js

83 lines
2.8 KiB
JavaScript
Raw Normal View History

/**
* 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 };
})();