tradeblocks-mcp 3.8.0 → 3.10.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/dist/test-exports.js +125 -20
- package/dist/test-exports.js.map +1 -1
- package/package.json +4 -4
- package/server/{chunk-LUMKSOEC.js → chunk-BFXJCMA2.js} +3 -3
- package/server/{chunk-HC2NGC34.js → chunk-LNAAA74R.js} +24 -15
- package/server/{chunk-HC2NGC34.js.map → chunk-LNAAA74R.js.map} +1 -1
- package/server/{chunk-MVTMOYYT.js → chunk-WFWYMFY5.js} +2 -2
- package/server/index.js +151 -35
- package/server/index.js.map +1 -1
- package/server/{market-provider-3EHVGT6E.js → market-provider-AR7OVVUK.js} +3 -3
- package/server/{sync-P4DLN5YE.js → sync-FNVNZR6P.js} +3 -3
- package/src/market/ingestor/market-ingestor.ts +61 -15
- package/src/tools/analysis.ts +27 -9
- package/src/tools/blocks/health.ts +1 -1
- package/src/utils/exit-triggers.ts +70 -23
- package/src/utils/money.ts +138 -0
- /package/server/{chunk-LUMKSOEC.js.map → chunk-BFXJCMA2.js.map} +0 -0
- /package/server/{chunk-MVTMOYYT.js.map → chunk-WFWYMFY5.js.map} +0 -0
- /package/server/{market-provider-3EHVGT6E.js.map → market-provider-AR7OVVUK.js.map} +0 -0
- /package/server/{sync-P4DLN5YE.js.map → sync-FNVNZR6P.js.map} +0 -0
package/dist/test-exports.js
CHANGED
|
@@ -8209,6 +8209,56 @@ function decomposeGreeks(config) {
|
|
|
8209
8209
|
};
|
|
8210
8210
|
}
|
|
8211
8211
|
|
|
8212
|
+
// src/utils/money.ts
|
|
8213
|
+
var SCALE = 1e6;
|
|
8214
|
+
var MONEY_MAX_DOLLARS = Math.floor(Number.MAX_SAFE_INTEGER / SCALE);
|
|
8215
|
+
var MoneyDomainError = class extends Error {
|
|
8216
|
+
};
|
|
8217
|
+
function toMoneyField(amount, field) {
|
|
8218
|
+
if (!Number.isFinite(amount)) {
|
|
8219
|
+
throw new MoneyDomainError(`${field} must be a finite dollar amount`);
|
|
8220
|
+
}
|
|
8221
|
+
const scaled = Math.round(amount * SCALE);
|
|
8222
|
+
if (!Number.isSafeInteger(scaled)) {
|
|
8223
|
+
throw new MoneyDomainError(
|
|
8224
|
+
`${field} is beyond the largest dollar amount this analysis can represent (about ${MONEY_MAX_DOLLARS.toLocaleString("en-US")})`
|
|
8225
|
+
);
|
|
8226
|
+
}
|
|
8227
|
+
return scaled === 0 ? 0 : scaled;
|
|
8228
|
+
}
|
|
8229
|
+
function applyRatioField(amount, ratio, field) {
|
|
8230
|
+
if (!Number.isFinite(ratio)) {
|
|
8231
|
+
throw new MoneyDomainError(`${field} must be a finite number`);
|
|
8232
|
+
}
|
|
8233
|
+
return toMoneyField(fromMoney(amount) * ratio, field);
|
|
8234
|
+
}
|
|
8235
|
+
function fromMoney(value) {
|
|
8236
|
+
return value / SCALE;
|
|
8237
|
+
}
|
|
8238
|
+
function formatMoney(value) {
|
|
8239
|
+
if (value % 1e4 === 0) return (value / SCALE).toFixed(2);
|
|
8240
|
+
return (value / SCALE).toFixed(6).replace(/(\.\d\d[0-9]*?)0+$/, "$1");
|
|
8241
|
+
}
|
|
8242
|
+
function formatPercent2(ratio) {
|
|
8243
|
+
const negative = ratio < 0;
|
|
8244
|
+
const [coefficient, exponentText] = Math.abs(ratio).toString().split("e");
|
|
8245
|
+
const exponent = Number(exponentText ?? 0);
|
|
8246
|
+
const decimalAt = coefficient.indexOf(".");
|
|
8247
|
+
const digits = coefficient.replace(".", "");
|
|
8248
|
+
const shiftedDecimalAt = (decimalAt === -1 ? digits.length : decimalAt) + exponent + 2;
|
|
8249
|
+
let percentage;
|
|
8250
|
+
if (shiftedDecimalAt <= 0) {
|
|
8251
|
+
percentage = `0.${"0".repeat(-shiftedDecimalAt)}${digits}`;
|
|
8252
|
+
} else if (shiftedDecimalAt >= digits.length) {
|
|
8253
|
+
percentage = `${digits}${"0".repeat(shiftedDecimalAt - digits.length)}`;
|
|
8254
|
+
} else {
|
|
8255
|
+
percentage = `${digits.slice(0, shiftedDecimalAt)}.${digits.slice(shiftedDecimalAt)}`;
|
|
8256
|
+
}
|
|
8257
|
+
percentage = percentage.replace(/^0+(?=\d)/, "");
|
|
8258
|
+
if (percentage.includes(".")) percentage = percentage.replace(/0+$/, "").replace(/\.$/, "");
|
|
8259
|
+
return `${negative ? "-" : ""}${percentage}%`;
|
|
8260
|
+
}
|
|
8261
|
+
|
|
8212
8262
|
// src/utils/exit-triggers.ts
|
|
8213
8263
|
function parseDate(dateStr) {
|
|
8214
8264
|
const [y, m, d] = dateStr.split("-").map(Number);
|
|
@@ -8257,9 +8307,11 @@ function evaluateProfitAction(trigger, pnlPath, _legs) {
|
|
|
8257
8307
|
return { fireEvent: null, partialCloses };
|
|
8258
8308
|
}
|
|
8259
8309
|
const scale = trigger.unit === "percent" ? Math.abs(trigger.entryCost) : 1;
|
|
8310
|
+
const entryCostMoney = trigger.unit === "percent" ? toMoneyField(scale, "entry cost") : 0;
|
|
8311
|
+
const stepDollars = (value, field) => trigger.unit === "percent" ? fromMoney(applyRatioField(entryCostMoney, value, field)) : fromMoney(toMoneyField(value, field));
|
|
8260
8312
|
const normalizedSteps = [...trigger.steps].sort((a, b) => a.armAt - b.armAt).map((step) => ({
|
|
8261
|
-
armAt: step.armAt
|
|
8262
|
-
stopAt: step.stopAt
|
|
8313
|
+
armAt: stepDollars(step.armAt, "steps.armAt"),
|
|
8314
|
+
stopAt: stepDollars(step.stopAt, "steps.stopAt"),
|
|
8263
8315
|
closeAllocationPct: step.closeAllocationPct
|
|
8264
8316
|
}));
|
|
8265
8317
|
let remainingAllocation = 1;
|
|
@@ -8271,7 +8323,7 @@ function evaluateProfitAction(trigger, pnlPath, _legs) {
|
|
|
8271
8323
|
if (pnl > runningMaxPnl) runningMaxPnl = pnl;
|
|
8272
8324
|
for (let s = 0; s < normalizedSteps.length; s++) {
|
|
8273
8325
|
const step = normalizedSteps[s];
|
|
8274
|
-
if (!stepPartialFired[s] && step.closeAllocationPct && runningMaxPnl >= step.armAt) {
|
|
8326
|
+
if (!stepPartialFired[s] && step.closeAllocationPct && Number.isFinite(runningMaxPnl) && runningMaxPnl >= step.armAt) {
|
|
8275
8327
|
stepPartialFired[s] = true;
|
|
8276
8328
|
const closeAmt = remainingAllocation * step.closeAllocationPct;
|
|
8277
8329
|
partialCloses.push({
|
|
@@ -8284,14 +8336,16 @@ function evaluateProfitAction(trigger, pnlPath, _legs) {
|
|
|
8284
8336
|
}
|
|
8285
8337
|
}
|
|
8286
8338
|
let activeFloor = -Infinity;
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8339
|
+
if (Number.isFinite(runningMaxPnl)) {
|
|
8340
|
+
for (const step of normalizedSteps) {
|
|
8341
|
+
if (runningMaxPnl >= step.armAt) {
|
|
8342
|
+
activeFloor = Math.max(activeFloor, step.stopAt);
|
|
8343
|
+
}
|
|
8290
8344
|
}
|
|
8291
8345
|
}
|
|
8292
8346
|
if (activeFloor > -Infinity && remainingAllocation > 0 && pnl <= activeFloor) {
|
|
8293
8347
|
const effectivePnl = pnl * remainingAllocation;
|
|
8294
|
-
const detail = trigger.unit === "percent" ? `Profit action: stop adjusted to ${(activeFloor / scale
|
|
8348
|
+
const detail = trigger.unit === "percent" ? `Profit action: stop adjusted to ${formatPercent2(activeFloor / scale)} ($${formatMoney(toMoneyField(activeFloor, "steps.stopAt"))}) at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)` : `Profit action: stop adjusted to $${formatMoney(toMoneyField(activeFloor, "steps.stopAt"))} at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`;
|
|
8295
8349
|
return {
|
|
8296
8350
|
fireEvent: {
|
|
8297
8351
|
type: "profitAction",
|
|
@@ -8325,12 +8379,16 @@ function evaluateTrigger(trigger, pnlPath, legs) {
|
|
|
8325
8379
|
case "profitTarget": {
|
|
8326
8380
|
if (trigger.unit === "percent" && trigger.entryCost == null) break;
|
|
8327
8381
|
const requiredHits = trigger.requiredHits ?? 2;
|
|
8328
|
-
const
|
|
8329
|
-
|
|
8382
|
+
const ptThresholdMoney = trigger.unit === "percent" ? applyRatioField(
|
|
8383
|
+
toMoneyField(Math.abs(trigger.entryCost), "entry cost"),
|
|
8384
|
+
threshold,
|
|
8385
|
+
"threshold"
|
|
8386
|
+
) : toMoneyField(threshold, "threshold");
|
|
8387
|
+
if (pnl >= fromMoney(ptThresholdMoney)) {
|
|
8330
8388
|
if (point.allLegsSync !== false) profitTargetHits++;
|
|
8331
8389
|
if (profitTargetHits < requiredHits) break;
|
|
8332
8390
|
fired = true;
|
|
8333
|
-
detail = trigger.unit === "percent" ? `P&L $${pnl.toFixed(2)} >= ${(threshold
|
|
8391
|
+
detail = trigger.unit === "percent" ? `P&L $${pnl.toFixed(2)} >= ${formatPercent2(threshold)} of $${formatMoney(toMoneyField(Math.abs(trigger.entryCost), "entry cost"))} ($${formatMoney(ptThresholdMoney)})` : `P&L $${pnl.toFixed(2)} >= target $${formatMoney(ptThresholdMoney)}`;
|
|
8334
8392
|
} else if (point.allLegsSync !== false) {
|
|
8335
8393
|
profitTargetHits = 0;
|
|
8336
8394
|
}
|
|
@@ -8339,19 +8397,24 @@ function evaluateTrigger(trigger, pnlPath, legs) {
|
|
|
8339
8397
|
case "stopLoss": {
|
|
8340
8398
|
const absThreshold = Math.abs(threshold);
|
|
8341
8399
|
if (trigger.unit === "percent" && trigger.entryCost == null) break;
|
|
8342
|
-
const
|
|
8343
|
-
|
|
8400
|
+
const slThresholdMoney = trigger.unit === "percent" ? applyRatioField(
|
|
8401
|
+
toMoneyField(Math.abs(trigger.entryCost), "entry cost"),
|
|
8402
|
+
absThreshold,
|
|
8403
|
+
"threshold"
|
|
8404
|
+
) : toMoneyField(absThreshold, "threshold");
|
|
8405
|
+
if (pnl <= -fromMoney(slThresholdMoney)) {
|
|
8344
8406
|
fired = true;
|
|
8345
|
-
detail = trigger.unit === "percent" ? `P&L $${pnl.toFixed(2)} <= -${(absThreshold
|
|
8407
|
+
detail = trigger.unit === "percent" ? `P&L $${pnl.toFixed(2)} <= -${formatPercent2(absThreshold)} of $${formatMoney(toMoneyField(Math.abs(trigger.entryCost), "entry cost"))} (-$${formatMoney(slThresholdMoney)})` : `P&L $${pnl.toFixed(2)} <= stop -$${formatMoney(slThresholdMoney)}`;
|
|
8346
8408
|
}
|
|
8347
8409
|
break;
|
|
8348
8410
|
}
|
|
8349
8411
|
case "trailingStop": {
|
|
8350
8412
|
const trailAmt = trigger.trailAmount ?? threshold;
|
|
8351
|
-
const
|
|
8352
|
-
|
|
8413
|
+
const trailArmed = Number.isFinite(runningMaxPnl);
|
|
8414
|
+
const dropdown = trailArmed ? runningMaxPnl - pnl : 0;
|
|
8415
|
+
if (trailArmed && dropdown >= fromMoney(toMoneyField(trailAmt, "trailAmount"))) {
|
|
8353
8416
|
fired = true;
|
|
8354
|
-
detail = `Dropdown $${dropdown.toFixed(2)} from max $${runningMaxPnl.toFixed(2)} >= trail $${trailAmt
|
|
8417
|
+
detail = `Dropdown $${dropdown.toFixed(2)} from max $${runningMaxPnl.toFixed(2)} >= trail $${formatMoney(toMoneyField(trailAmt, "trailAmount"))}`;
|
|
8355
8418
|
}
|
|
8356
8419
|
break;
|
|
8357
8420
|
}
|
|
@@ -8610,8 +8673,8 @@ function analyzeExitTriggers(config) {
|
|
|
8610
8673
|
if (!firstToFire) {
|
|
8611
8674
|
summary = `No triggers fired across ${pnlPath.length} data points.`;
|
|
8612
8675
|
} else if (actualExit) {
|
|
8613
|
-
const betterWorse = actualExit.pnlDifference > 0 ? "better" : "worse";
|
|
8614
|
-
summary = `${firstToFire.type} fired at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). Actual exit at ${actualExit.timestamp} (P&L $${actualExit.pnl.toFixed(2)}). Trigger was
|
|
8676
|
+
const betterWorse = actualExit.pnlDifference > 0 ? "better" : actualExit.pnlDifference < 0 ? "worse" : "the same";
|
|
8677
|
+
summary = `${firstToFire.type} fired at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). Actual exit at ${actualExit.timestamp} (P&L $${actualExit.pnl.toFixed(2)}). Trigger was ${actualExit.pnlDifference === 0 ? "the same" : `$${Math.abs(actualExit.pnlDifference).toFixed(2)} ${betterWorse}`}.`;
|
|
8615
8678
|
} else {
|
|
8616
8679
|
summary = `${firstToFire.type} fired first at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ${fireEvents.length} trigger(s) fired total.`;
|
|
8617
8680
|
}
|
|
@@ -12476,7 +12539,10 @@ function isNonTradingDay(asOf) {
|
|
|
12476
12539
|
const d = /* @__PURE__ */ new Date(`${asOf}T12:00:00Z`);
|
|
12477
12540
|
const dow = d.getUTCDay();
|
|
12478
12541
|
if (dow === 0 || dow === 6) return true;
|
|
12479
|
-
|
|
12542
|
+
if (asOf < XNYS_SESSION_CALENDAR_SUPPORTED_FROM || asOf > XNYS_SESSION_CALENDAR_SUPPORTED_THROUGH) {
|
|
12543
|
+
return false;
|
|
12544
|
+
}
|
|
12545
|
+
return !isXnysSessionDate(asOf);
|
|
12480
12546
|
}
|
|
12481
12547
|
function unsupportedProviderResult(provider, operation, target, reason, originalError) {
|
|
12482
12548
|
return {
|
|
@@ -12683,6 +12749,44 @@ var MarketIngestor = class {
|
|
|
12683
12749
|
return result;
|
|
12684
12750
|
}
|
|
12685
12751
|
}
|
|
12752
|
+
async enforceSpotRefreshCompleteness(symbol, asOf, result) {
|
|
12753
|
+
if (result.rowsWritten > 0 || result.status !== "ok") return result;
|
|
12754
|
+
try {
|
|
12755
|
+
const coverage = await this.deps.stores.spot.getCoverage(symbol.toUpperCase(), asOf, asOf);
|
|
12756
|
+
if (coverage.totalDates > 0) {
|
|
12757
|
+
return {
|
|
12758
|
+
status: "skipped",
|
|
12759
|
+
rowsWritten: 0,
|
|
12760
|
+
dateRange: { from: asOf, to: asOf },
|
|
12761
|
+
details: {
|
|
12762
|
+
...result.details ?? {},
|
|
12763
|
+
reason: "using_cached_coverage",
|
|
12764
|
+
dataset: "spot",
|
|
12765
|
+
symbol: symbol.toUpperCase(),
|
|
12766
|
+
originalStatus: result.status,
|
|
12767
|
+
cachedCoverage: {
|
|
12768
|
+
totalDates: coverage.totalDates,
|
|
12769
|
+
earliest: coverage.earliest,
|
|
12770
|
+
latest: coverage.latest
|
|
12771
|
+
}
|
|
12772
|
+
}
|
|
12773
|
+
};
|
|
12774
|
+
}
|
|
12775
|
+
} catch {
|
|
12776
|
+
}
|
|
12777
|
+
return {
|
|
12778
|
+
status: "error",
|
|
12779
|
+
rowsWritten: 0,
|
|
12780
|
+
error: `Spot refresh for ${symbol.toUpperCase()} on ${asOf} returned zero rows and exact-date coverage remains absent`,
|
|
12781
|
+
details: {
|
|
12782
|
+
...result.details ?? {},
|
|
12783
|
+
reason: "zero_rows",
|
|
12784
|
+
dataset: "spot",
|
|
12785
|
+
symbol: symbol.toUpperCase(),
|
|
12786
|
+
asOf
|
|
12787
|
+
}
|
|
12788
|
+
};
|
|
12789
|
+
}
|
|
12686
12790
|
quoteGreeksSourceForProvider(provider) {
|
|
12687
12791
|
if (provider.name === "massive" || provider.name === "thetadata") {
|
|
12688
12792
|
return provider.name;
|
|
@@ -13380,7 +13484,8 @@ var MarketIngestor = class {
|
|
|
13380
13484
|
timespan: "1m",
|
|
13381
13485
|
provider: opts.provider
|
|
13382
13486
|
});
|
|
13383
|
-
const
|
|
13487
|
+
const fallbackResult = await this.applyCoverageFallback("spot", ticker, opts.asOf, rawResult);
|
|
13488
|
+
const result = await this.enforceSpotRefreshCompleteness(ticker, opts.asOf, fallbackResult);
|
|
13384
13489
|
spotResults.push(result);
|
|
13385
13490
|
if (result.status === "error") errors.push(`spot ${ticker}: ${result.error}`);
|
|
13386
13491
|
}
|