commitments-of-traders 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moshe Malka
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/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # commitments-of-traders
2
+
3
+ Typed, zero-dependency client for the **CFTC Commitments of Traders (COT)** reports — Legacy, Disaggregated, and Traders in Financial Futures, futures-only or combined — via the official [Socrata API](https://publicreporting.cftc.gov/). **No API key required.**
4
+
5
+ ```
6
+ npm install commitments-of-traders
7
+ ```
8
+
9
+ ## Why
10
+
11
+ The weekly COT report is one of the most-watched positioning datasets in futures and FX — how commercials, large speculators, and managed money are positioned across every US futures market. The CFTC publishes it through a free Socrata API, but building the SoQL queries (market filters, date ranges, the six different report datasets) by hand is tedious, and npm had no client library — only an MCP server. This is the library.
12
+
13
+ ```ts
14
+ import { getCotReport, latestForMarket, num } from "commitments-of-traders";
15
+
16
+ // Latest E-mini S&P 500 large-speculator positioning:
17
+ const es = await latestForMarket("legacy", "E-MINI S&P 500");
18
+ const net = num(es.noncomm_positions_long_all) - num(es.noncomm_positions_short_all);
19
+
20
+ // Managed-money gold positioning (disaggregated, futures + options):
21
+ const gold = await getCotReport("disaggregated", "combined", { market: "GOLD", limit: 1 });
22
+ num(gold[0].m_money_positions_long_all);
23
+
24
+ // A full year of one market, with a raw SoQL filter ANDed in:
25
+ const history = await getCotReport("tff", "futures-only", {
26
+ market: "10-YEAR U.S. TREASURY",
27
+ since: "2025-01-01",
28
+ where: "open_interest_all > 1000000",
29
+ });
30
+ ```
31
+
32
+ ## Reports
33
+
34
+ All three families, in both futures-only and combined (futures + options) forms — six datasets, all wired up:
35
+
36
+ | `report` | `include: "futures-only"` | `include: "combined"` |
37
+ | --- | --- | --- |
38
+ | `"legacy"` | Legacy — Futures Only | Legacy — Combined |
39
+ | `"disaggregated"` | Disaggregated — Futures Only | Disaggregated — Combined |
40
+ | `"tff"` | TFF — Futures Only | TFF — Combined |
41
+
42
+ (The exact Socrata resource IDs are exported as `DATASETS` and were verified against publicreporting.cftc.gov.)
43
+
44
+ ## API
45
+
46
+ Every function takes an optional final `options`: `{ fetch?, baseUrl?, appToken? }` — inject `fetch` for tests, or a Socrata app token to raise rate limits (not required).
47
+
48
+ - **`getCotReport(report, include?, params?)`** — query a report family (`include` defaults to `"futures-only"`).
49
+ - **`latestForMarket(report, market, include?)`** — the single most recent row for a market, or `null`.
50
+ - **`queryDataset(datasetId, params?)`** — query by raw Socrata id.
51
+ - **`num(value)`** — Socrata returns every column as a string; `num` converts to `number | null`.
52
+
53
+ **`params`**: `market` (case-insensitive contains-match), `contractMarketCode`, `since` / `until` (YYYY-MM-DD), `where` (raw SoQL, ANDed in), `select`, `order`, `limit`, `offset`. Market names and codes are safely escaped. Failures throw `CotError` with `status` and `url`.
54
+
55
+ Rows are typed for the always-present identity columns (market, report date, codes, open interest) with an index signature for the hundreds of position columns that vary by report family. The mocked test suite runs offline; `npm run smoke` exercises the live API.
56
+
57
+ ## Related
58
+
59
+ By the same author, a fixed-income & markets toolkit: [`treasurydirect`](https://github.com/moshejs/treasurydirect) · [`treasury-fiscaldata`](https://github.com/moshejs/treasury-fiscaldata) · [`newyorkfed`](https://github.com/moshejs/newyorkfed) · [`tbill`](https://github.com/moshejs/tbill) · [`compounded-sofr`](https://github.com/moshejs/compounded-sofr) · [`instrument-identifiers`](https://github.com/moshejs/instrument-identifiers) · [`32nds`](https://github.com/moshejs/32nds).
60
+
61
+ ## Author
62
+
63
+ Built by **[Moshe Malka](https://moshemalka.com)** — engineering leader in New York City. Studio work at [Quentin.Code](https://www.quentin.software/).
64
+
65
+ MIT © Moshe Malka
package/dist/index.cjs ADDED
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CotError: () => CotError,
24
+ DATASETS: () => DATASETS,
25
+ getCotReport: () => getCotReport,
26
+ latestForMarket: () => latestForMarket,
27
+ num: () => num,
28
+ queryDataset: () => queryDataset
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+ var DATASETS = {
32
+ legacy: { "futures-only": "6dca-aqww", combined: "jun7-fc8e" },
33
+ disaggregated: { "futures-only": "72hh-3qpy", combined: "kh3c-gbw2" },
34
+ tff: { "futures-only": "gpe5-46if", combined: "yw9f-hn96" }
35
+ };
36
+ var CotError = class extends Error {
37
+ constructor(status, url) {
38
+ super(`CFTC COT request failed with HTTP ${status}: ${url}`);
39
+ this.name = "CotError";
40
+ this.status = status;
41
+ this.url = url;
42
+ }
43
+ };
44
+ var DEFAULT_BASE = "https://publicreporting.cftc.gov";
45
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
46
+ function soqlString(value) {
47
+ return `'${value.replace(/'/g, "''")}'`;
48
+ }
49
+ function checkDate(d, label) {
50
+ if (!ISO_DATE_RE.test(d)) throw new RangeError(`Invalid ${label}: "${d}" (expected YYYY-MM-DD)`);
51
+ }
52
+ function buildWhere(params) {
53
+ const clauses = [];
54
+ if (params.market !== void 0) {
55
+ clauses.push(`upper(market_and_exchange_names) like upper(${soqlString(`%${params.market}%`)})`);
56
+ }
57
+ if (params.contractMarketCode !== void 0) {
58
+ clauses.push(`cftc_contract_market_code = ${soqlString(params.contractMarketCode)}`);
59
+ }
60
+ if (params.since !== void 0) {
61
+ checkDate(params.since, "since");
62
+ clauses.push(`report_date_as_yyyy_mm_dd >= '${params.since}T00:00:00.000'`);
63
+ }
64
+ if (params.until !== void 0) {
65
+ checkDate(params.until, "until");
66
+ clauses.push(`report_date_as_yyyy_mm_dd <= '${params.until}T23:59:59.999'`);
67
+ }
68
+ if (params.where !== void 0) clauses.push(`(${params.where})`);
69
+ return clauses.length ? clauses.join(" AND ") : void 0;
70
+ }
71
+ function resolveDataset(report, include) {
72
+ const byInclude = DATASETS[report];
73
+ if (!byInclude) throw new RangeError(`Unknown report "${report}"`);
74
+ const id = byInclude[include];
75
+ if (!id) throw new RangeError(`Unknown include "${include}"`);
76
+ return id;
77
+ }
78
+ async function request(datasetId, params, opts) {
79
+ const doFetch = opts.fetch ?? globalThis.fetch;
80
+ const base = opts.baseUrl ?? DEFAULT_BASE;
81
+ const sp = new URLSearchParams();
82
+ if (params.select && params.select.length) sp.set("$select", params.select.join(","));
83
+ const where = buildWhere(params);
84
+ if (where) sp.set("$where", where);
85
+ sp.set("$order", params.order ?? "report_date_as_yyyy_mm_dd DESC");
86
+ if (params.limit !== void 0) {
87
+ if (!Number.isInteger(params.limit) || params.limit <= 0 || params.limit > 5e4) {
88
+ throw new RangeError(`limit must be an integer in 1\u201350000, got ${params.limit}`);
89
+ }
90
+ sp.set("$limit", String(params.limit));
91
+ } else {
92
+ sp.set("$limit", "1000");
93
+ }
94
+ if (params.offset !== void 0) {
95
+ if (!Number.isInteger(params.offset) || params.offset < 0) {
96
+ throw new RangeError(`offset must be a non-negative integer, got ${params.offset}`);
97
+ }
98
+ sp.set("$offset", String(params.offset));
99
+ }
100
+ const url = `${base}/resource/${datasetId}.json?${sp.toString()}`;
101
+ const headers = { accept: "application/json" };
102
+ if (opts.appToken) headers["X-App-Token"] = opts.appToken;
103
+ const res = await doFetch(url, { headers });
104
+ if (!res.ok) throw new CotError(res.status, url);
105
+ return await res.json();
106
+ }
107
+ async function getCotReport(report, include = "futures-only", params = {}, opts = {}) {
108
+ return request(resolveDataset(report, include), params, opts);
109
+ }
110
+ async function latestForMarket(report, market, include = "futures-only", opts = {}) {
111
+ const rows = await request(
112
+ resolveDataset(report, include),
113
+ { market, limit: 1, order: "report_date_as_yyyy_mm_dd DESC" },
114
+ opts
115
+ );
116
+ return rows[0] ?? null;
117
+ }
118
+ function queryDataset(datasetId, params = {}, opts = {}) {
119
+ return request(datasetId, params, opts);
120
+ }
121
+ function num(value) {
122
+ if (value === void 0 || value.trim() === "") return null;
123
+ const n = Number(value);
124
+ return Number.isFinite(n) ? n : null;
125
+ }
126
+ // Annotate the CommonJS export names for ESM import in node:
127
+ 0 && (module.exports = {
128
+ CotError,
129
+ DATASETS,
130
+ getCotReport,
131
+ latestForMarket,
132
+ num,
133
+ queryDataset
134
+ });
@@ -0,0 +1,91 @@
1
+ /**
2
+ * commitments-of-traders — typed, zero-dependency client for the CFTC's
3
+ * Commitments of Traders (COT) reports via the official Socrata API at
4
+ * publicreporting.cftc.gov.
5
+ *
6
+ * Covers all three report families (Legacy, Disaggregated, Traders in
7
+ * Financial Futures) in both futures-only and combined (futures + options)
8
+ * forms, with SoQL filtering, sorting, and pagination. No API key required.
9
+ */
10
+ /** COT report family. */
11
+ type CotReport = "legacy" | "disaggregated" | "tff";
12
+ /** Whether to include options: futures only, or futures + options combined. */
13
+ type CotInclude = "futures-only" | "combined";
14
+ /** Socrata dataset resource IDs, confirmed against publicreporting.cftc.gov. */
15
+ declare const DATASETS: Record<CotReport, Record<CotInclude, string>>;
16
+ interface CotOptions {
17
+ /** Custom fetch implementation (testing, proxies). Defaults to global fetch. */
18
+ fetch?: typeof globalThis.fetch;
19
+ /** Base URL. Defaults to "https://publicreporting.cftc.gov". */
20
+ baseUrl?: string;
21
+ /** Optional Socrata app token (raises rate limits; not required). */
22
+ appToken?: string;
23
+ }
24
+ /**
25
+ * A COT report row. All values are strings, as Socrata returns them; the
26
+ * columns differ by report family (Legacy ~120, Disaggregated ~190, TFF ~90),
27
+ * so only the always-present identity columns are typed — everything else is
28
+ * reachable through the index signature. Use {@link num} for numeric columns.
29
+ */
30
+ interface CotRow {
31
+ market_and_exchange_names: string;
32
+ report_date_as_yyyy_mm_dd: string;
33
+ yyyy_report_week_ww: string;
34
+ contract_market_name: string;
35
+ cftc_contract_market_code: string;
36
+ cftc_commodity_code: string;
37
+ open_interest_all: string;
38
+ [column: string]: string | undefined;
39
+ }
40
+ interface QueryParams {
41
+ /** Filter to one market name (SoQL `like %…%`, case-insensitive contains). */
42
+ market?: string;
43
+ /** Exact CFTC contract market code. */
44
+ contractMarketCode?: string;
45
+ /** Report date lower bound (inclusive), YYYY-MM-DD. */
46
+ since?: string;
47
+ /** Report date upper bound (inclusive), YYYY-MM-DD. */
48
+ until?: string;
49
+ /** Raw SoQL `$where` clause; ANDed with the structured filters above. */
50
+ where?: string;
51
+ /** Columns to select (SoQL `$select`). Default all. */
52
+ select?: string[];
53
+ /** Sort column and direction. Default report date descending. */
54
+ order?: string;
55
+ /** Row cap (Socrata `$limit`). Default 1000. */
56
+ limit?: number;
57
+ /** Row offset (Socrata `$offset`). */
58
+ offset?: number;
59
+ }
60
+ declare class CotError extends Error {
61
+ readonly status: number;
62
+ readonly url: string;
63
+ constructor(status: number, url: string);
64
+ }
65
+ /**
66
+ * Query a COT report family.
67
+ *
68
+ * ```ts
69
+ * // The latest E-mini S&P 500 legacy report:
70
+ * const [row] = await getCotReport("legacy", "futures-only", {
71
+ * market: "E-MINI S&P 500",
72
+ * limit: 1,
73
+ * });
74
+ * num(row.noncomm_positions_long_all);
75
+ * ```
76
+ */
77
+ declare function getCotReport(report: CotReport, include?: CotInclude, params?: QueryParams, opts?: CotOptions): Promise<CotRow[]>;
78
+ /**
79
+ * The single most recent report row for a market in a report family.
80
+ * Returns null if the market matches nothing.
81
+ */
82
+ declare function latestForMarket(report: CotReport, market: string, include?: CotInclude, opts?: CotOptions): Promise<CotRow | null>;
83
+ /** Query directly by Socrata dataset id (escape hatch for exotic needs). */
84
+ declare function queryDataset(datasetId: string, params?: QueryParams, opts?: CotOptions): Promise<CotRow[]>;
85
+ /**
86
+ * Parse a COT column string to a number, or null when empty/non-numeric.
87
+ * `num("123456")` → 123456; `num("")` → null.
88
+ */
89
+ declare function num(value: string | undefined): number | null;
90
+
91
+ export { CotError, type CotInclude, type CotOptions, type CotReport, type CotRow, DATASETS, type QueryParams, getCotReport, latestForMarket, num, queryDataset };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * commitments-of-traders — typed, zero-dependency client for the CFTC's
3
+ * Commitments of Traders (COT) reports via the official Socrata API at
4
+ * publicreporting.cftc.gov.
5
+ *
6
+ * Covers all three report families (Legacy, Disaggregated, Traders in
7
+ * Financial Futures) in both futures-only and combined (futures + options)
8
+ * forms, with SoQL filtering, sorting, and pagination. No API key required.
9
+ */
10
+ /** COT report family. */
11
+ type CotReport = "legacy" | "disaggregated" | "tff";
12
+ /** Whether to include options: futures only, or futures + options combined. */
13
+ type CotInclude = "futures-only" | "combined";
14
+ /** Socrata dataset resource IDs, confirmed against publicreporting.cftc.gov. */
15
+ declare const DATASETS: Record<CotReport, Record<CotInclude, string>>;
16
+ interface CotOptions {
17
+ /** Custom fetch implementation (testing, proxies). Defaults to global fetch. */
18
+ fetch?: typeof globalThis.fetch;
19
+ /** Base URL. Defaults to "https://publicreporting.cftc.gov". */
20
+ baseUrl?: string;
21
+ /** Optional Socrata app token (raises rate limits; not required). */
22
+ appToken?: string;
23
+ }
24
+ /**
25
+ * A COT report row. All values are strings, as Socrata returns them; the
26
+ * columns differ by report family (Legacy ~120, Disaggregated ~190, TFF ~90),
27
+ * so only the always-present identity columns are typed — everything else is
28
+ * reachable through the index signature. Use {@link num} for numeric columns.
29
+ */
30
+ interface CotRow {
31
+ market_and_exchange_names: string;
32
+ report_date_as_yyyy_mm_dd: string;
33
+ yyyy_report_week_ww: string;
34
+ contract_market_name: string;
35
+ cftc_contract_market_code: string;
36
+ cftc_commodity_code: string;
37
+ open_interest_all: string;
38
+ [column: string]: string | undefined;
39
+ }
40
+ interface QueryParams {
41
+ /** Filter to one market name (SoQL `like %…%`, case-insensitive contains). */
42
+ market?: string;
43
+ /** Exact CFTC contract market code. */
44
+ contractMarketCode?: string;
45
+ /** Report date lower bound (inclusive), YYYY-MM-DD. */
46
+ since?: string;
47
+ /** Report date upper bound (inclusive), YYYY-MM-DD. */
48
+ until?: string;
49
+ /** Raw SoQL `$where` clause; ANDed with the structured filters above. */
50
+ where?: string;
51
+ /** Columns to select (SoQL `$select`). Default all. */
52
+ select?: string[];
53
+ /** Sort column and direction. Default report date descending. */
54
+ order?: string;
55
+ /** Row cap (Socrata `$limit`). Default 1000. */
56
+ limit?: number;
57
+ /** Row offset (Socrata `$offset`). */
58
+ offset?: number;
59
+ }
60
+ declare class CotError extends Error {
61
+ readonly status: number;
62
+ readonly url: string;
63
+ constructor(status: number, url: string);
64
+ }
65
+ /**
66
+ * Query a COT report family.
67
+ *
68
+ * ```ts
69
+ * // The latest E-mini S&P 500 legacy report:
70
+ * const [row] = await getCotReport("legacy", "futures-only", {
71
+ * market: "E-MINI S&P 500",
72
+ * limit: 1,
73
+ * });
74
+ * num(row.noncomm_positions_long_all);
75
+ * ```
76
+ */
77
+ declare function getCotReport(report: CotReport, include?: CotInclude, params?: QueryParams, opts?: CotOptions): Promise<CotRow[]>;
78
+ /**
79
+ * The single most recent report row for a market in a report family.
80
+ * Returns null if the market matches nothing.
81
+ */
82
+ declare function latestForMarket(report: CotReport, market: string, include?: CotInclude, opts?: CotOptions): Promise<CotRow | null>;
83
+ /** Query directly by Socrata dataset id (escape hatch for exotic needs). */
84
+ declare function queryDataset(datasetId: string, params?: QueryParams, opts?: CotOptions): Promise<CotRow[]>;
85
+ /**
86
+ * Parse a COT column string to a number, or null when empty/non-numeric.
87
+ * `num("123456")` → 123456; `num("")` → null.
88
+ */
89
+ declare function num(value: string | undefined): number | null;
90
+
91
+ export { CotError, type CotInclude, type CotOptions, type CotReport, type CotRow, DATASETS, type QueryParams, getCotReport, latestForMarket, num, queryDataset };
package/dist/index.js ADDED
@@ -0,0 +1,104 @@
1
+ // src/index.ts
2
+ var DATASETS = {
3
+ legacy: { "futures-only": "6dca-aqww", combined: "jun7-fc8e" },
4
+ disaggregated: { "futures-only": "72hh-3qpy", combined: "kh3c-gbw2" },
5
+ tff: { "futures-only": "gpe5-46if", combined: "yw9f-hn96" }
6
+ };
7
+ var CotError = class extends Error {
8
+ constructor(status, url) {
9
+ super(`CFTC COT request failed with HTTP ${status}: ${url}`);
10
+ this.name = "CotError";
11
+ this.status = status;
12
+ this.url = url;
13
+ }
14
+ };
15
+ var DEFAULT_BASE = "https://publicreporting.cftc.gov";
16
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
17
+ function soqlString(value) {
18
+ return `'${value.replace(/'/g, "''")}'`;
19
+ }
20
+ function checkDate(d, label) {
21
+ if (!ISO_DATE_RE.test(d)) throw new RangeError(`Invalid ${label}: "${d}" (expected YYYY-MM-DD)`);
22
+ }
23
+ function buildWhere(params) {
24
+ const clauses = [];
25
+ if (params.market !== void 0) {
26
+ clauses.push(`upper(market_and_exchange_names) like upper(${soqlString(`%${params.market}%`)})`);
27
+ }
28
+ if (params.contractMarketCode !== void 0) {
29
+ clauses.push(`cftc_contract_market_code = ${soqlString(params.contractMarketCode)}`);
30
+ }
31
+ if (params.since !== void 0) {
32
+ checkDate(params.since, "since");
33
+ clauses.push(`report_date_as_yyyy_mm_dd >= '${params.since}T00:00:00.000'`);
34
+ }
35
+ if (params.until !== void 0) {
36
+ checkDate(params.until, "until");
37
+ clauses.push(`report_date_as_yyyy_mm_dd <= '${params.until}T23:59:59.999'`);
38
+ }
39
+ if (params.where !== void 0) clauses.push(`(${params.where})`);
40
+ return clauses.length ? clauses.join(" AND ") : void 0;
41
+ }
42
+ function resolveDataset(report, include) {
43
+ const byInclude = DATASETS[report];
44
+ if (!byInclude) throw new RangeError(`Unknown report "${report}"`);
45
+ const id = byInclude[include];
46
+ if (!id) throw new RangeError(`Unknown include "${include}"`);
47
+ return id;
48
+ }
49
+ async function request(datasetId, params, opts) {
50
+ const doFetch = opts.fetch ?? globalThis.fetch;
51
+ const base = opts.baseUrl ?? DEFAULT_BASE;
52
+ const sp = new URLSearchParams();
53
+ if (params.select && params.select.length) sp.set("$select", params.select.join(","));
54
+ const where = buildWhere(params);
55
+ if (where) sp.set("$where", where);
56
+ sp.set("$order", params.order ?? "report_date_as_yyyy_mm_dd DESC");
57
+ if (params.limit !== void 0) {
58
+ if (!Number.isInteger(params.limit) || params.limit <= 0 || params.limit > 5e4) {
59
+ throw new RangeError(`limit must be an integer in 1\u201350000, got ${params.limit}`);
60
+ }
61
+ sp.set("$limit", String(params.limit));
62
+ } else {
63
+ sp.set("$limit", "1000");
64
+ }
65
+ if (params.offset !== void 0) {
66
+ if (!Number.isInteger(params.offset) || params.offset < 0) {
67
+ throw new RangeError(`offset must be a non-negative integer, got ${params.offset}`);
68
+ }
69
+ sp.set("$offset", String(params.offset));
70
+ }
71
+ const url = `${base}/resource/${datasetId}.json?${sp.toString()}`;
72
+ const headers = { accept: "application/json" };
73
+ if (opts.appToken) headers["X-App-Token"] = opts.appToken;
74
+ const res = await doFetch(url, { headers });
75
+ if (!res.ok) throw new CotError(res.status, url);
76
+ return await res.json();
77
+ }
78
+ async function getCotReport(report, include = "futures-only", params = {}, opts = {}) {
79
+ return request(resolveDataset(report, include), params, opts);
80
+ }
81
+ async function latestForMarket(report, market, include = "futures-only", opts = {}) {
82
+ const rows = await request(
83
+ resolveDataset(report, include),
84
+ { market, limit: 1, order: "report_date_as_yyyy_mm_dd DESC" },
85
+ opts
86
+ );
87
+ return rows[0] ?? null;
88
+ }
89
+ function queryDataset(datasetId, params = {}, opts = {}) {
90
+ return request(datasetId, params, opts);
91
+ }
92
+ function num(value) {
93
+ if (value === void 0 || value.trim() === "") return null;
94
+ const n = Number(value);
95
+ return Number.isFinite(n) ? n : null;
96
+ }
97
+ export {
98
+ CotError,
99
+ DATASETS,
100
+ getCotReport,
101
+ latestForMarket,
102
+ num,
103
+ queryDataset
104
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "commitments-of-traders",
3
+ "version": "1.0.0",
4
+ "description": "Typed, zero-dependency client for the CFTC Commitments of Traders (COT) reports — Legacy, Disaggregated, and Traders in Financial Futures, futures-only or combined, via the official Socrata API. No API key required.",
5
+ "keywords": [
6
+ "cftc",
7
+ "commitments-of-traders",
8
+ "cot",
9
+ "cot-report",
10
+ "futures",
11
+ "positioning",
12
+ "traders-in-financial-futures",
13
+ "disaggregated",
14
+ "finance",
15
+ "trading",
16
+ "api-client",
17
+ "socrata"
18
+ ],
19
+ "homepage": "https://moshemalka.com",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/moshejs/commitments-of-traders.git"
23
+ },
24
+ "bugs": "https://github.com/moshejs/commitments-of-traders/issues",
25
+ "author": "Moshe Malka <hello@moshemalka.com> (https://moshemalka.com)",
26
+ "license": "MIT",
27
+ "type": "module",
28
+ "main": "./dist/index.cjs",
29
+ "module": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js",
35
+ "require": "./dist/index.cjs"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ],
41
+ "sideEffects": false,
42
+ "scripts": {
43
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
44
+ "test": "vitest run",
45
+ "typecheck": "tsc --noEmit",
46
+ "smoke": "node scripts/smoke.mjs",
47
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
48
+ },
49
+ "devDependencies": {
50
+ "tsup": "^8.5.0",
51
+ "typescript": "^5.8.0",
52
+ "vitest": "^3.2.0"
53
+ },
54
+ "engines": {
55
+ "node": ">=18"
56
+ }
57
+ }