mindvest-atlas 0.1.6 → 0.2.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/README.md +1 -0
- package/package.json +1 -1
- package/src/cli.js +5 -0
- package/src/commands/historical.js +157 -0
- package/src/config.js +1 -1
- package/src/util/format.js +61 -0
- package/src/util/help.js +6 -0
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ atlas login
|
|
|
27
27
|
atlas tools
|
|
28
28
|
atlas stock-quote --symbol SPY
|
|
29
29
|
atlas options-chain --symbol SPY --expiration 2026-06-20
|
|
30
|
+
atlas historical-contract --symbol NVDA --expiry 2026-07-13 --strike 205 --side call --from 2026-07-06 --to 2026-07-10
|
|
30
31
|
atlas alerts --symbol SPY --name greek_exposure --save-charts ./charts
|
|
31
32
|
atlas flow stream --symbol NVDA
|
|
32
33
|
```
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { whoamiCommand } from './commands/whoami.js';
|
|
|
12
12
|
import { toolsCommand } from './commands/tools.js';
|
|
13
13
|
import { alertsCommand } from './commands/alerts.js';
|
|
14
14
|
import { flowCommand } from './commands/flow.js';
|
|
15
|
+
import { historicalContractCommand } from './commands/historical.js';
|
|
15
16
|
import { callCommand } from './commands/call.js';
|
|
16
17
|
|
|
17
18
|
export async function main(rawArgs) {
|
|
@@ -46,6 +47,10 @@ export async function main(rawArgs) {
|
|
|
46
47
|
case 'tools': return await toolsCommand(withoutCommand(args));
|
|
47
48
|
case 'alerts': return await alertsCommand(withoutCommand(args));
|
|
48
49
|
case 'flow': return await flowCommand(withoutCommand(args));
|
|
50
|
+
// Friendly front end over Historical-Contract-Greeks (short flags + a table).
|
|
51
|
+
case 'historical-contract':
|
|
52
|
+
case 'hist-contract':
|
|
53
|
+
return await historicalContractCommand(withoutCommand(args));
|
|
49
54
|
default:
|
|
50
55
|
// Treat the first positional as a tool slug (e.g. stock-quote).
|
|
51
56
|
return await callCommand(cmd, withoutCommand(args));
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// `atlas historical-contract --symbol NVDA --expiry 2026-07-13 --strike 205 --side call
|
|
2
|
+
// --from 2026-07-08 --to 2026-07-10`
|
|
3
|
+
//
|
|
4
|
+
// Day-by-day price + greeks for ONE option contract. This is a friendly front end over
|
|
5
|
+
// the `Historical-Contract-Greeks` tool: short flags (--expiry / --from / --to), a
|
|
6
|
+
// side shorthand (--call / --put), per-flag "you forgot this one" errors, and a table
|
|
7
|
+
// instead of raw JSON. The generic caller still works (`atlas historical-contract-greeks
|
|
8
|
+
// --from-date …`); this exists because five required params deserve better ergonomics.
|
|
9
|
+
import { API } from '../config.js';
|
|
10
|
+
import { requestJson, ApiError, AuthError } from '../api/client.js';
|
|
11
|
+
import { formatContractHistory } from '../util/format.js';
|
|
12
|
+
import { color, println, eprintln } from '../util/ui.js';
|
|
13
|
+
|
|
14
|
+
const TOOL = 'Historical-Contract-Greeks';
|
|
15
|
+
|
|
16
|
+
// Flags the user might reach for → the server's parameter name.
|
|
17
|
+
const ALIASES = {
|
|
18
|
+
symbol: ['symbol', 'ticker'],
|
|
19
|
+
expiration: ['expiry', 'expiration', 'exp'],
|
|
20
|
+
strike: ['strike'],
|
|
21
|
+
side: ['side', 'right'],
|
|
22
|
+
from_date: ['from', 'from-date', 'start'],
|
|
23
|
+
to_date: ['to', 'to-date', 'end'],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// A bare `--flag` parses to `true`; that is not a value, so don't accept it.
|
|
27
|
+
function pick(flags, names) {
|
|
28
|
+
for (const n of names) {
|
|
29
|
+
const v = flags[n];
|
|
30
|
+
if (v !== undefined && v !== true && v !== '') return v;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function printHistoricalHelp() {
|
|
36
|
+
const c = color;
|
|
37
|
+
println(`${c.bold('atlas historical-contract')} — day-by-day price and greeks for one option contract`);
|
|
38
|
+
println();
|
|
39
|
+
println(c.bold('USAGE'));
|
|
40
|
+
println(' atlas historical-contract --symbol <TICKER> --expiry <YYYY-MM-DD> \\');
|
|
41
|
+
println(' --strike <PRICE> --side <call|put> \\');
|
|
42
|
+
println(' --from <YYYY-MM-DD> [--to <YYYY-MM-DD>]');
|
|
43
|
+
println();
|
|
44
|
+
println(c.bold('REQUIRED'));
|
|
45
|
+
println(' --symbol, --ticker <T> Underlying ticker (e.g. NVDA)');
|
|
46
|
+
println(' --expiry, --expiration <D> The contract\'s expiration date, YYYY-MM-DD');
|
|
47
|
+
println(' --strike <PRICE> The contract\'s strike (e.g. 205)');
|
|
48
|
+
println(' --side <call|put> Which side (or use --call / --put)');
|
|
49
|
+
println(' --from, --start <D> First day to report, YYYY-MM-DD');
|
|
50
|
+
println();
|
|
51
|
+
println(c.bold('OPTIONAL'));
|
|
52
|
+
println(' --to, --end <D> Last day to report (default: same as --from)');
|
|
53
|
+
println(' --no-greeks Prices only — skip the greeks (faster)');
|
|
54
|
+
println(' --json Raw JSON instead of the table');
|
|
55
|
+
println();
|
|
56
|
+
println(c.bold('NOTES'));
|
|
57
|
+
println(c.dim(' The daily price is the MID of the last bid/ask before the 4pm close.'));
|
|
58
|
+
println(c.dim(' Greeks are calculated, not reported: no data provider publishes greeks for'));
|
|
59
|
+
println(c.dim(' past dates, so implied volatility is solved from that day\'s closing mid.'));
|
|
60
|
+
println(c.dim(' Open interest is not available for past dates at all.'));
|
|
61
|
+
println(c.dim(' One contract per call, up to 90 trading days.'));
|
|
62
|
+
println();
|
|
63
|
+
println(c.dim('Example:'));
|
|
64
|
+
println(c.dim(' atlas historical-contract --symbol NVDA --expiry 2026-07-13 \\'));
|
|
65
|
+
println(c.dim(' --strike 205 --side call --from 2026-07-06 --to 2026-07-10'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Pure: flags -> request body, or the reason it can't be built. Kept separate from the
|
|
69
|
+
// command so the flag mapping is unit-testable without a network round trip.
|
|
70
|
+
// Returns { body } | { missing: [...] } | { error }.
|
|
71
|
+
export function buildHistoricalBody(args) {
|
|
72
|
+
const flags = args.flags || {};
|
|
73
|
+
const positionals = args._ || [];
|
|
74
|
+
|
|
75
|
+
// `atlas historical-contract NVDA …` — allow the ticker as a bare positional too.
|
|
76
|
+
const symbol = pick(flags, ALIASES.symbol) ?? positionals[0];
|
|
77
|
+
const expiration = pick(flags, ALIASES.expiration);
|
|
78
|
+
const strike = pick(flags, ALIASES.strike);
|
|
79
|
+
const from = pick(flags, ALIASES.from_date);
|
|
80
|
+
const to = pick(flags, ALIASES.to_date);
|
|
81
|
+
|
|
82
|
+
let side = pick(flags, ALIASES.side);
|
|
83
|
+
if (side === undefined && flags.call === true) side = 'call';
|
|
84
|
+
if (side === undefined && flags.put === true) side = 'put';
|
|
85
|
+
|
|
86
|
+
// Five required params — name the ones actually missing. A bare "missing parameter"
|
|
87
|
+
// would send the user back to --help every time.
|
|
88
|
+
const missing = [];
|
|
89
|
+
if (!symbol) missing.push('--symbol <TICKER>');
|
|
90
|
+
if (!expiration) missing.push('--expiry <YYYY-MM-DD>');
|
|
91
|
+
if (strike === undefined) missing.push('--strike <PRICE>');
|
|
92
|
+
if (!side) missing.push('--side <call|put>');
|
|
93
|
+
if (!from) missing.push('--from <YYYY-MM-DD>');
|
|
94
|
+
if (missing.length) return { missing };
|
|
95
|
+
|
|
96
|
+
const sideStr = String(side).toLowerCase();
|
|
97
|
+
const sideNorm = sideStr.startsWith('c') ? 'call' : sideStr.startsWith('p') ? 'put' : null;
|
|
98
|
+
if (!sideNorm) return { error: `--side must be call or put (got "${side}")` };
|
|
99
|
+
|
|
100
|
+
const strikeNum = Number(strike);
|
|
101
|
+
if (!Number.isFinite(strikeNum) || strikeNum <= 0) {
|
|
102
|
+
return { error: `--strike must be a positive number (got "${strike}")` };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const body = {
|
|
106
|
+
symbol: String(symbol).toUpperCase(),
|
|
107
|
+
expiration: String(expiration),
|
|
108
|
+
strike: strikeNum,
|
|
109
|
+
side: sideNorm,
|
|
110
|
+
from_date: String(from),
|
|
111
|
+
};
|
|
112
|
+
if (to) body.to_date = String(to);
|
|
113
|
+
// `--no-greeks` parses to flags.greeks === false (the --no- convention).
|
|
114
|
+
if (flags.greeks === false || flags['include-greeks'] === false) body.include_greeks = false;
|
|
115
|
+
return { body };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function historicalContractCommand(args) {
|
|
119
|
+
const flags = args.flags;
|
|
120
|
+
if (flags.help || flags.h) { printHistoricalHelp(); return 0; }
|
|
121
|
+
|
|
122
|
+
const built = buildHistoricalBody(args);
|
|
123
|
+
if (built.missing) {
|
|
124
|
+
eprintln(color.red('Missing required flag(s): ') + built.missing.join(' '));
|
|
125
|
+
eprintln(color.dim('Example: atlas historical-contract --symbol NVDA --expiry 2026-07-13 --strike 205 --side call --from 2026-07-08 --to 2026-07-10'));
|
|
126
|
+
eprintln(color.dim('See: atlas historical-contract --help'));
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
if (built.error) { eprintln(color.red(built.error)); return 1; }
|
|
130
|
+
const body = built.body;
|
|
131
|
+
|
|
132
|
+
let data;
|
|
133
|
+
try {
|
|
134
|
+
data = await requestJson(API.tool(TOOL), { method: 'POST', body });
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (err instanceof AuthError) { eprintln(color.red(err.message)); return 1; }
|
|
137
|
+
if (err instanceof ApiError) {
|
|
138
|
+
eprintln(color.red(`Error (${err.status || '?'}): ${err.message}`));
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
eprintln(color.red('Request failed: ') + err.message);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// The tools API returns 200 with an { error } body on bad input.
|
|
146
|
+
if (data && typeof data === 'object' && typeof data.error === 'string') {
|
|
147
|
+
eprintln(color.red('Error: ') + data.error);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (flags.json || flags.raw) {
|
|
152
|
+
println(JSON.stringify(data, null, 2));
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
for (const line of formatContractHistory(data)) println(line);
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
package/src/config.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
|
|
5
|
-
export const VERSION = '0.
|
|
5
|
+
export const VERSION = '0.2.0';
|
|
6
6
|
export const DEFAULT_BASE_URL = 'https://atlasmcp.finmanagerai.com';
|
|
7
7
|
export const DEFAULT_SCOPE = 'atlas broker';
|
|
8
8
|
export const CLIENT_NAME = 'Atlas CLI';
|
package/src/util/format.js
CHANGED
|
@@ -69,3 +69,64 @@ export function formatTrade(t) {
|
|
|
69
69
|
const oc = t.open_close ? color.dim(t.open_close === 'open' ? 'O' : 'C') : '';
|
|
70
70
|
return `${time} ${ticker} ${cp} ${strike} ${exp} ${side} ${size} ${price} ${prem} ${type} ${tag} ${oc}`.replace(/\s+$/, '');
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
// ── historical-contract: one row per trading day ─────────────────────────────
|
|
74
|
+
// Fixed-width so the columns line up; padding is applied BEFORE color codes so the
|
|
75
|
+
// escape sequences don't count toward the width.
|
|
76
|
+
function cell(v, dp, w) {
|
|
77
|
+
if (v === null || v === undefined || Number.isNaN(Number(v))) return '—'.padStart(w);
|
|
78
|
+
return Number(v).toFixed(dp).padStart(w);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ivCell(v, w) {
|
|
82
|
+
if (v === null || v === undefined || Number.isNaN(Number(v))) return '—'.padStart(w);
|
|
83
|
+
return (Number(v) * 100).toFixed(1).concat('%').padStart(w);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function formatContractHistory(d) {
|
|
87
|
+
const c = color;
|
|
88
|
+
const out = [];
|
|
89
|
+
const days = Array.isArray(d.days) ? d.days : [];
|
|
90
|
+
|
|
91
|
+
out.push(
|
|
92
|
+
c.bold(d.option_symbol || '?') +
|
|
93
|
+
c.dim(` ${d.symbol} ${num(d.strike)} ${d.side} · expires ${d.expiration}`)
|
|
94
|
+
);
|
|
95
|
+
out.push(c.dim(
|
|
96
|
+
`${d.from_date} → ${d.to_date} ${days.length} trading day${days.length === 1 ? '' : 's'}` +
|
|
97
|
+
' price = mid of the last bid/ask before the close'
|
|
98
|
+
));
|
|
99
|
+
out.push('');
|
|
100
|
+
|
|
101
|
+
// Only show greek columns when the response actually carries them (--no-greeks).
|
|
102
|
+
const hasGreeks = days.some((r) => r.delta !== null && r.delta !== undefined);
|
|
103
|
+
const head =
|
|
104
|
+
'DATE'.padEnd(12) + 'DTE'.padStart(4) + 'BID'.padStart(9) + 'ASK'.padStart(9) +
|
|
105
|
+
'MID'.padStart(9) + 'SPOT'.padStart(10) +
|
|
106
|
+
(hasGreeks ? 'IV'.padStart(8) + 'DELTA'.padStart(9) + 'GAMMA'.padStart(9) + 'THETA'.padStart(9) : '');
|
|
107
|
+
out.push(c.dim(head));
|
|
108
|
+
|
|
109
|
+
for (const r of days) {
|
|
110
|
+
let row =
|
|
111
|
+
String(r.date || '').padEnd(12) +
|
|
112
|
+
String(r.dte ?? '—').padStart(4) +
|
|
113
|
+
cell(r.bid, 2, 9) + cell(r.ask, 2, 9) +
|
|
114
|
+
c.bold(cell(r.mid, 2, 9).replace(/^(\s*)/, '$1')) +
|
|
115
|
+
cell(r.underlying_price, 2, 10);
|
|
116
|
+
if (hasGreeks) {
|
|
117
|
+
row += ivCell(r.iv, 8) + cell(r.delta, 3, 9) + cell(r.gamma, 3, 9) + cell(r.theta, 3, 9);
|
|
118
|
+
}
|
|
119
|
+
out.push(row);
|
|
120
|
+
if (r.greeks_error) out.push(c.dim(` ↳ no greeks: ${r.greeks_error}`));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Never let a missing day pass silently — a holiday and a never-quoted strike look
|
|
124
|
+
// identical in a table that just skips rows.
|
|
125
|
+
if (Array.isArray(d.skipped) && d.skipped.length) {
|
|
126
|
+
out.push('');
|
|
127
|
+
for (const s of d.skipped) out.push(c.dim(`skipped ${s.date}: ${s.reason}`));
|
|
128
|
+
}
|
|
129
|
+
if (d.truncated) out.push(c.yellow(String(d.truncated)));
|
|
130
|
+
if (d.note) { out.push(''); out.push(c.dim(d.note)); }
|
|
131
|
+
return out;
|
|
132
|
+
}
|
package/src/util/help.js
CHANGED
|
@@ -22,6 +22,11 @@ export function printRootHelp() {
|
|
|
22
22
|
println(' <tool> --param value [--out <dir>] Call a tool — pass params as flags');
|
|
23
23
|
println(' <tool> --a.b value Nested value via a dotted flag');
|
|
24
24
|
println();
|
|
25
|
+
println(c.bold('MARKET HISTORY'));
|
|
26
|
+
println(' historical-contract --symbol NVDA --expiry 2026-07-13 Day-by-day price + greeks');
|
|
27
|
+
println(' --strike 205 --side call --from 2026-07-06 [--to 2026-07-10] for ONE option contract');
|
|
28
|
+
println(' [--no-greeks] [--json]');
|
|
29
|
+
println();
|
|
25
30
|
println(c.bold('STREAMS'));
|
|
26
31
|
println(' alerts [--symbol SPY] [--name greek_exposure] Live alert feed (SSE)');
|
|
27
32
|
println(' [--save-charts <dir>] [--no-chart] [--json]');
|
|
@@ -42,6 +47,7 @@ export function printRootHelp() {
|
|
|
42
47
|
println(c.dim(' atlas tools'));
|
|
43
48
|
println(c.dim(' atlas stock-quote --symbol SPY'));
|
|
44
49
|
println(c.dim(' atlas options-chain --symbol SPY --expiration 2026-06-20'));
|
|
50
|
+
println(c.dim(' atlas historical-contract --symbol NVDA --expiry 2026-07-13 --strike 205 --side call --from 2026-07-06 --to 2026-07-10'));
|
|
45
51
|
println(c.dim(' atlas update-alert --alert-id <id> --updates.is-active true # nested via a dotted flag'));
|
|
46
52
|
println(c.dim(' atlas create-task --name "Close SPY 3:55" --instruction "Close my SPY position" --schedule-times 15:55'));
|
|
47
53
|
println(c.dim(' atlas run-task --task-id <id>'));
|