83 lines
2.8 KiB
JavaScript
83 lines
2.8 KiB
JavaScript
|
|
/**
|
||
|
|
* 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 };
|
||
|
|
})();
|