@coinrithm/mcp-trading 0.7.19 → 0.7.20
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/CHANGELOG.md +942 -922
- package/README.md +539 -499
- package/dist/agent/templates.js +30 -30
- package/dist/agent/version.d.ts +1 -1
- package/dist/agent/version.js +1 -1
- package/dist/client.d.ts +45 -0
- package/dist/client.js +56 -4
- package/dist/http.js +5 -2
- package/dist/httpCompletion.d.ts +1 -1
- package/dist/httpCompletion.js +8 -0
- package/dist/instructions.js +1 -1
- package/dist/requestContext.d.ts +5 -0
- package/dist/requestContext.js +25 -0
- package/dist/tools.d.ts +44 -0
- package/dist/tools.js +809 -0
- package/package.json +100 -100
package/dist/tools.js
CHANGED
|
@@ -726,6 +726,411 @@ export function compactPublicPmDisagreements(data) {
|
|
|
726
726
|
: data.data,
|
|
727
727
|
};
|
|
728
728
|
}
|
|
729
|
+
// The 12 venue slugs of PublicPmSourceSlug in openapi.yaml.
|
|
730
|
+
const PUBLIC_PM_SOURCES = [
|
|
731
|
+
"polymarket",
|
|
732
|
+
"kalshi",
|
|
733
|
+
"rothera",
|
|
734
|
+
"limitless",
|
|
735
|
+
"smarkets",
|
|
736
|
+
"manifold",
|
|
737
|
+
"metaculus",
|
|
738
|
+
"predictit",
|
|
739
|
+
"futuur",
|
|
740
|
+
"myriad",
|
|
741
|
+
"forecastex",
|
|
742
|
+
"gemini",
|
|
743
|
+
];
|
|
744
|
+
/**
|
|
745
|
+
* Evenly spaced subset of a time series that always keeps the first and the
|
|
746
|
+
* last point. Values are never rewritten or interpolated.
|
|
747
|
+
*/
|
|
748
|
+
export function samplePoints(points, max) {
|
|
749
|
+
if (points.length <= max)
|
|
750
|
+
return points;
|
|
751
|
+
if (max < 2)
|
|
752
|
+
return points.slice(-1);
|
|
753
|
+
const step = (points.length - 1) / (max - 1);
|
|
754
|
+
return Array.from({ length: max }, (_, k) => points[Math.round(k * step)]);
|
|
755
|
+
}
|
|
756
|
+
function sampledSeries(points, max, mapPoint = (point) => point) {
|
|
757
|
+
if (!Array.isArray(points))
|
|
758
|
+
return { pointCount: 0, sampled: false, points };
|
|
759
|
+
return {
|
|
760
|
+
pointCount: points.length,
|
|
761
|
+
sampled: points.length > max,
|
|
762
|
+
points: samplePoints(points, max).map(mapPoint),
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
/** Price history for one outcome, with the point series bounded to maxPoints. */
|
|
766
|
+
export function compactPublicPmPriceHistory(data, maxPoints) {
|
|
767
|
+
if (!isJsonRecord(data))
|
|
768
|
+
return data;
|
|
769
|
+
const { points, ...rest } = data;
|
|
770
|
+
return { ...rest, ...sampledSeries(points, maxPoints) };
|
|
771
|
+
}
|
|
772
|
+
/** Order book with each side cut to its best `depth` levels as served. */
|
|
773
|
+
export function compactPublicPmOrderBook(data, depth) {
|
|
774
|
+
if (!isJsonRecord(data) || !isJsonRecord(data.orderBook))
|
|
775
|
+
return data;
|
|
776
|
+
const book = data.orderBook;
|
|
777
|
+
const side = (levels) => Array.isArray(levels) ? levels.slice(0, depth) : levels;
|
|
778
|
+
return {
|
|
779
|
+
...data,
|
|
780
|
+
orderBook: {
|
|
781
|
+
...book,
|
|
782
|
+
bidLevels: Array.isArray(book.bids) ? book.bids.length : 0,
|
|
783
|
+
askLevels: Array.isArray(book.asks) ? book.asks.length : 0,
|
|
784
|
+
bids: side(book.bids),
|
|
785
|
+
asks: side(book.asks),
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
// Resolved and surprise rows: { event, resolvedAt, resolvedAtVerified,
|
|
790
|
+
// closedAt, resolutionOutcome, surprise? }. The event gets eventSummary.
|
|
791
|
+
function compactSettledRow(value) {
|
|
792
|
+
if (!isJsonRecord(value))
|
|
793
|
+
return value;
|
|
794
|
+
return {
|
|
795
|
+
...pick(value, [
|
|
796
|
+
"resolvedAt",
|
|
797
|
+
"resolvedAtVerified",
|
|
798
|
+
"closedAt",
|
|
799
|
+
"resolutionOutcome",
|
|
800
|
+
"surprise",
|
|
801
|
+
]),
|
|
802
|
+
event: eventSummary(value.event),
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
/** Resolved archive and surprise pages: rows reduced to settled summaries. */
|
|
806
|
+
export function compactPublicPmSettled(data) {
|
|
807
|
+
if (!isJsonRecord(data))
|
|
808
|
+
return data;
|
|
809
|
+
return {
|
|
810
|
+
...data,
|
|
811
|
+
data: Array.isArray(data.data)
|
|
812
|
+
? data.data.map(compactSettledRow)
|
|
813
|
+
: data.data,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
/** Expiring page: each row is a full event plus expiresInMs. */
|
|
817
|
+
export function compactPublicPmExpiring(data) {
|
|
818
|
+
if (!isJsonRecord(data))
|
|
819
|
+
return data;
|
|
820
|
+
return {
|
|
821
|
+
...data,
|
|
822
|
+
data: Array.isArray(data.data)
|
|
823
|
+
? data.data.map((row) => isJsonRecord(row)
|
|
824
|
+
? {
|
|
825
|
+
...eventSummary(row),
|
|
826
|
+
...pick(row, ["expectedResolveAt", "expiresInMs"]),
|
|
827
|
+
}
|
|
828
|
+
: row)
|
|
829
|
+
: data.data,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const LADDER_CONTRACT_FIELDS = [
|
|
833
|
+
"label",
|
|
834
|
+
"comparator",
|
|
835
|
+
"strikeLow",
|
|
836
|
+
"strikeHigh",
|
|
837
|
+
"unit",
|
|
838
|
+
"probability",
|
|
839
|
+
"bid",
|
|
840
|
+
"ask",
|
|
841
|
+
"tradedRecently",
|
|
842
|
+
];
|
|
843
|
+
const LADDER_EVENT_FIELDS = [
|
|
844
|
+
"source",
|
|
845
|
+
"slug",
|
|
846
|
+
"title",
|
|
847
|
+
"volume24hUsd",
|
|
848
|
+
"liquidityUsd",
|
|
849
|
+
"openInterestUsd",
|
|
850
|
+
];
|
|
851
|
+
function compactLadderContract(value, multiEvent) {
|
|
852
|
+
if (!isJsonRecord(value))
|
|
853
|
+
return value;
|
|
854
|
+
const contract = pick(value, LADDER_CONTRACT_FIELDS);
|
|
855
|
+
// Kalshi-only fields: carried only when the venue reported a value.
|
|
856
|
+
for (const key of ["contractVolume24h", "contractOpenInterest"]) {
|
|
857
|
+
if (value[key] !== null && value[key] !== undefined) {
|
|
858
|
+
contract[key] = value[key];
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (multiEvent && value.eventSlug !== undefined) {
|
|
862
|
+
contract.eventSlug = value.eventSlug;
|
|
863
|
+
}
|
|
864
|
+
return contract;
|
|
865
|
+
}
|
|
866
|
+
function compactLadder(value, group, referenceNotes) {
|
|
867
|
+
if (!isJsonRecord(value))
|
|
868
|
+
return value;
|
|
869
|
+
const ladder = pick(value, [
|
|
870
|
+
"venue",
|
|
871
|
+
"series",
|
|
872
|
+
"windowStartAt",
|
|
873
|
+
"observation",
|
|
874
|
+
"metric",
|
|
875
|
+
"referenceRelation",
|
|
876
|
+
"asOf",
|
|
877
|
+
"asOfBasis",
|
|
878
|
+
"monotonic",
|
|
879
|
+
]);
|
|
880
|
+
// Fields equal to the group's are stated once on the group.
|
|
881
|
+
for (const key of ["expiresAt", "settlementSource", "settlementNote"]) {
|
|
882
|
+
if (value[key] !== undefined && value[key] !== group[key]) {
|
|
883
|
+
ladder[key] = value[key];
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
// referenceNote repeats per relation: keep it once in referenceNotes and
|
|
887
|
+
// on a ladder only when its note differs from that entry.
|
|
888
|
+
const relation = value.referenceRelation;
|
|
889
|
+
if (value.referenceNote !== undefined) {
|
|
890
|
+
if (typeof relation === "string" && !(relation in referenceNotes)) {
|
|
891
|
+
referenceNotes[relation] = value.referenceNote;
|
|
892
|
+
}
|
|
893
|
+
if (typeof relation !== "string" ||
|
|
894
|
+
referenceNotes[relation] !== value.referenceNote) {
|
|
895
|
+
ladder.referenceNote = value.referenceNote;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const events = value.events;
|
|
899
|
+
ladder.events = Array.isArray(events)
|
|
900
|
+
? events.map((event) => isJsonRecord(event) ? pick(event, LADDER_EVENT_FIELDS) : event)
|
|
901
|
+
: events;
|
|
902
|
+
const multiEvent = !Array.isArray(events) || events.length !== 1;
|
|
903
|
+
ladder.contracts = Array.isArray(value.contracts)
|
|
904
|
+
? value.contracts.map((contract) => compactLadderContract(contract, multiEvent))
|
|
905
|
+
: value.contracts;
|
|
906
|
+
return ladder;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Strike ladders without per-contract venue ids, parser details and URLs.
|
|
910
|
+
* detail=full returns the untouched record (externalMarketId included).
|
|
911
|
+
* Verified live 2026-10-16: bitcoin, 10 ladders, 174 contracts went from
|
|
912
|
+
* about 92 KB to about 40 KB.
|
|
913
|
+
*/
|
|
914
|
+
export function compactInstrumentMarkets(data) {
|
|
915
|
+
if (!isJsonRecord(data))
|
|
916
|
+
return data;
|
|
917
|
+
const referenceNotes = {};
|
|
918
|
+
const groups = Array.isArray(data.groups)
|
|
919
|
+
? data.groups.map((group) => {
|
|
920
|
+
if (!isJsonRecord(group))
|
|
921
|
+
return group;
|
|
922
|
+
return {
|
|
923
|
+
...pick(group, ["expiresAt", "settlementSource", "settlementNote"]),
|
|
924
|
+
ladders: Array.isArray(group.ladders)
|
|
925
|
+
? group.ladders.map((ladder) => compactLadder(ladder, group, referenceNotes))
|
|
926
|
+
: group.ladders,
|
|
927
|
+
};
|
|
928
|
+
})
|
|
929
|
+
: data.groups;
|
|
930
|
+
return {
|
|
931
|
+
...pick(data, [
|
|
932
|
+
"version",
|
|
933
|
+
"instrument",
|
|
934
|
+
"reference",
|
|
935
|
+
"generatedAt",
|
|
936
|
+
"horizon",
|
|
937
|
+
]),
|
|
938
|
+
referenceNotes,
|
|
939
|
+
groups,
|
|
940
|
+
crossVenueCount: Array.isArray(data.crossVenue)
|
|
941
|
+
? data.crossVenue.length
|
|
942
|
+
: 0,
|
|
943
|
+
crossVenue: Array.isArray(data.crossVenue)
|
|
944
|
+
? data.crossVenue.slice(0, 20)
|
|
945
|
+
: data.crossVenue,
|
|
946
|
+
...pick(data, ["shortTerm", "coverage", "disclaimer", "meta"]),
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
export const TRADFI_ASSET_CLASSES = [
|
|
950
|
+
"equity",
|
|
951
|
+
"etf",
|
|
952
|
+
"metal",
|
|
953
|
+
"commodity",
|
|
954
|
+
"fx",
|
|
955
|
+
];
|
|
956
|
+
function compactTradfiCatalogItem(value) {
|
|
957
|
+
if (!isJsonRecord(value))
|
|
958
|
+
return value;
|
|
959
|
+
const sub = (key, keys) => {
|
|
960
|
+
const nested = value[key];
|
|
961
|
+
return isJsonRecord(nested) ? pick(nested, keys) : nested;
|
|
962
|
+
};
|
|
963
|
+
return {
|
|
964
|
+
instrument: sub("instrument", [
|
|
965
|
+
"ticker",
|
|
966
|
+
"name",
|
|
967
|
+
"assetClass",
|
|
968
|
+
"unit",
|
|
969
|
+
"venueQuoteAsset",
|
|
970
|
+
"issuer",
|
|
971
|
+
"leverage",
|
|
972
|
+
]),
|
|
973
|
+
availability: value.availability,
|
|
974
|
+
freshness: sub("freshness", ["status", "asOf", "ageSeconds"]),
|
|
975
|
+
latest: sub("latest", [
|
|
976
|
+
"bucketStart",
|
|
977
|
+
"consensusPrice",
|
|
978
|
+
"venueSpreadPct",
|
|
979
|
+
"venueCount",
|
|
980
|
+
]),
|
|
981
|
+
change24h: sub("change24h", ["pct"]),
|
|
982
|
+
change7d: sub("change7d", ["pct"]),
|
|
983
|
+
range24h: sub("range24h", ["low", "high"]),
|
|
984
|
+
live: sub("live", [
|
|
985
|
+
"price",
|
|
986
|
+
"venueCount",
|
|
987
|
+
"sourceAt",
|
|
988
|
+
"ageSeconds",
|
|
989
|
+
"change24hPct",
|
|
990
|
+
]),
|
|
991
|
+
...(value.fundamentals === undefined
|
|
992
|
+
? {}
|
|
993
|
+
: {
|
|
994
|
+
fundamentals: sub("fundamentals", [
|
|
995
|
+
"marketCapUsd",
|
|
996
|
+
"peRatio",
|
|
997
|
+
"dividendYieldPct",
|
|
998
|
+
"sector",
|
|
999
|
+
]),
|
|
1000
|
+
}),
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
/** TradFi catalog filtered by asset class and paged on the MCP side. */
|
|
1004
|
+
export function compactTradfiCatalog(data, opts) {
|
|
1005
|
+
if (!isJsonRecord(data) || !Array.isArray(data.items))
|
|
1006
|
+
return data;
|
|
1007
|
+
const matching = opts.assetClass
|
|
1008
|
+
? data.items.filter((item) => isJsonRecord(item) &&
|
|
1009
|
+
isJsonRecord(item.instrument) &&
|
|
1010
|
+
item.instrument.assetClass === opts.assetClass)
|
|
1011
|
+
: data.items;
|
|
1012
|
+
const items = matching.slice(opts.offset, opts.offset + opts.limit);
|
|
1013
|
+
return {
|
|
1014
|
+
...pick(data, ["version", "generatedAt", "methodology", "count"]),
|
|
1015
|
+
...(opts.assetClass ? { assetClass: opts.assetClass } : {}),
|
|
1016
|
+
matched: matching.length,
|
|
1017
|
+
offset: opts.offset,
|
|
1018
|
+
hasMore: opts.offset + items.length < matching.length,
|
|
1019
|
+
items: items.map(compactTradfiCatalogItem),
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
/** TradFi detail with its 15-minute history bounded to maxPoints. */
|
|
1023
|
+
export function compactTradfiReference(data, maxPoints) {
|
|
1024
|
+
if (!isJsonRecord(data))
|
|
1025
|
+
return data;
|
|
1026
|
+
const { history, ...rest } = data;
|
|
1027
|
+
const series = sampledSeries(history, maxPoints, (point) => isJsonRecord(point)
|
|
1028
|
+
? pick(point, [
|
|
1029
|
+
"bucketStart",
|
|
1030
|
+
"consensusPrice",
|
|
1031
|
+
"minPrice",
|
|
1032
|
+
"maxPrice",
|
|
1033
|
+
"venueCount",
|
|
1034
|
+
])
|
|
1035
|
+
: point);
|
|
1036
|
+
return {
|
|
1037
|
+
...rest,
|
|
1038
|
+
historyCount: series.pointCount,
|
|
1039
|
+
historySampled: series.sampled,
|
|
1040
|
+
history: series.points,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
// Coin slugs as served by /api/coins (lower case; one has an underscore).
|
|
1044
|
+
const COIN_SLUG_PATTERN = /^[a-z0-9][a-z0-9_.-]{0,99}$/;
|
|
1045
|
+
export const COIN_CANDLE_RANGES = ["1H", "1D", "1W", "1M", "3M", "1Y"];
|
|
1046
|
+
/** Decimal string to number; anything else (null, unparsable) passes as is. */
|
|
1047
|
+
function decimalNumber(value) {
|
|
1048
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
1049
|
+
return value;
|
|
1050
|
+
const parsed = Number(value);
|
|
1051
|
+
return Number.isFinite(parsed) ? parsed : value;
|
|
1052
|
+
}
|
|
1053
|
+
function withoutUndefined(record) {
|
|
1054
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Compact coin profile from GET /api/coins/{slug}: price, change, market cap,
|
|
1058
|
+
* volume and the live-price as-of. The profile, not /price, because its
|
|
1059
|
+
* priceUpdatedAt is the live-price write time (probe 2026-10-16: /price
|
|
1060
|
+
* reported dead-pair coins as 0.6 h old while the profile said weeks).
|
|
1061
|
+
*/
|
|
1062
|
+
export function compactCryptoPrice(data, fiat) {
|
|
1063
|
+
if (!isJsonRecord(data))
|
|
1064
|
+
return data;
|
|
1065
|
+
const num = (key) => decimalNumber(data[key]);
|
|
1066
|
+
const price = data.currentPrice;
|
|
1067
|
+
return withoutUndefined({
|
|
1068
|
+
coinId: data.ucid,
|
|
1069
|
+
slug: data.slug,
|
|
1070
|
+
symbol: data.symbol,
|
|
1071
|
+
name: data.name,
|
|
1072
|
+
fiat,
|
|
1073
|
+
// An untracked fiat code is answered in USD with nothing saying so: the
|
|
1074
|
+
// converted price then equals the USD price exactly (rate 1.0).
|
|
1075
|
+
...(fiat === "USD"
|
|
1076
|
+
? {}
|
|
1077
|
+
: {
|
|
1078
|
+
fiatApplied: price === null || price === undefined
|
|
1079
|
+
? null
|
|
1080
|
+
: price !== data.currentPriceUsd,
|
|
1081
|
+
}),
|
|
1082
|
+
price: num("currentPrice"),
|
|
1083
|
+
priceUsd: num("currentPriceUsd"),
|
|
1084
|
+
change24hPct: num("change24h"),
|
|
1085
|
+
high24h: num("high24h"),
|
|
1086
|
+
low24h: num("low24h"),
|
|
1087
|
+
marketCap: num("marketCap"),
|
|
1088
|
+
marketCapChange24hPct: num("marketCapChange24h"),
|
|
1089
|
+
marketCapRank: data.marketCapRank,
|
|
1090
|
+
marketCapSuspect: data.marketCapSuspect,
|
|
1091
|
+
marketCapSuspectReason: data.marketCapSuspectReason,
|
|
1092
|
+
volume24h: num("volume24h"),
|
|
1093
|
+
volumeChange24hPct: num("volumeChange24h"),
|
|
1094
|
+
volumeMissingVenues: data.volumeMissingVenues,
|
|
1095
|
+
priceUpdatedAt: data.priceUpdatedAt,
|
|
1096
|
+
priceAgeHours: data.priceAgeHours,
|
|
1097
|
+
priceStale: data.priceStale,
|
|
1098
|
+
priceStaleAfterHours: data.priceStaleAfterHours,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
function isBar(value) {
|
|
1102
|
+
return (isJsonRecord(value) &&
|
|
1103
|
+
["t", "o", "h", "l", "c"].every((key) => typeof value[key] === "number"));
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Bars from GET /api/coins/{slug}/candles, merged into at most maxPoints
|
|
1107
|
+
* consecutive groups (open of the first bar, high and low across the group,
|
|
1108
|
+
* close of the last). `v` is dropped: it is a rolling 24h volume observed at
|
|
1109
|
+
* each bar, not the volume traded in it, so it cannot be merged.
|
|
1110
|
+
*/
|
|
1111
|
+
export function compactCoinCandles(data, maxPoints) {
|
|
1112
|
+
if (!isJsonRecord(data) || !Array.isArray(data.candles))
|
|
1113
|
+
return data;
|
|
1114
|
+
const bars = data.candles.filter(isBar);
|
|
1115
|
+
const size = Math.max(1, Math.ceil(bars.length / Math.max(1, maxPoints)));
|
|
1116
|
+
const merged = [];
|
|
1117
|
+
for (let i = 0; i < bars.length; i += size) {
|
|
1118
|
+
const group = bars.slice(i, i + size);
|
|
1119
|
+
merged.push({
|
|
1120
|
+
t: group[0].t,
|
|
1121
|
+
o: group[0].o,
|
|
1122
|
+
h: Math.max(...group.map((bar) => bar.h)),
|
|
1123
|
+
l: Math.min(...group.map((bar) => bar.l)),
|
|
1124
|
+
c: group[group.length - 1].c,
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
...pick(data, ["range", "fiat", "rateToUsd"]),
|
|
1129
|
+
barCount: bars.length,
|
|
1130
|
+
barsPerPoint: size,
|
|
1131
|
+
candles: merged,
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
729
1134
|
function mapSuccessfulBody(result, transform) {
|
|
730
1135
|
return result.ok ? { ...result, data: transform(result.data) } : result;
|
|
731
1136
|
}
|
|
@@ -2181,6 +2586,234 @@ export function registerTools(server, client) {
|
|
|
2181
2586
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2182
2587
|
annotations: readOnlyAnnotations("Global prediction-market volume trend"),
|
|
2183
2588
|
}, async () => present(await client.getPublicPmVolumeHistory()));
|
|
2589
|
+
const pmSourceSchema = z
|
|
2590
|
+
.enum(PUBLIC_PM_SOURCES)
|
|
2591
|
+
.describe("Venue slug, as in pm_data_events rows.");
|
|
2592
|
+
const pmSlugSchema = z
|
|
2593
|
+
.string()
|
|
2594
|
+
.trim()
|
|
2595
|
+
.min(1)
|
|
2596
|
+
.max(500)
|
|
2597
|
+
.describe("Event slug on that venue.");
|
|
2598
|
+
const fiatSchema = z
|
|
2599
|
+
.string()
|
|
2600
|
+
.trim()
|
|
2601
|
+
.regex(/^[A-Za-z]{3}$/, "expected a 3-letter currency code")
|
|
2602
|
+
.optional()
|
|
2603
|
+
.describe("Fiat currency code for monetary figures (default USD).");
|
|
2604
|
+
server.registerTool("pm_data_price_history", {
|
|
2605
|
+
title: "Prediction-market outcome price history",
|
|
2606
|
+
description: "Free public native venue price history for ONE outcome of an " +
|
|
2607
|
+
"event: the Yes leg of a binary market, else the first live outcome. " +
|
|
2608
|
+
"The response names it in `outcome`. Points are {t: epoch ms, p: " +
|
|
2609
|
+
"probability as a 0-1 fraction, not the 0-100 points other tools " +
|
|
2610
|
+
"use}. Polymarket and Kalshi only: other venues return points: [], " +
|
|
2611
|
+
"which means not served here, not no trading. interval is a lookback " +
|
|
2612
|
+
"range and the venue picks the spacing; the result is evenly " +
|
|
2613
|
+
"sampled to maxPoints (first and last kept) with the raw pointCount. " +
|
|
2614
|
+
"No API key required.",
|
|
2615
|
+
inputSchema: {
|
|
2616
|
+
source: pmSourceSchema,
|
|
2617
|
+
slug: pmSlugSchema,
|
|
2618
|
+
interval: z
|
|
2619
|
+
.enum(["1h", "6h", "1d", "1w", "1m", "max"])
|
|
2620
|
+
.optional()
|
|
2621
|
+
.describe("Lookback range (default 1w)."),
|
|
2622
|
+
maxPoints: z
|
|
2623
|
+
.number()
|
|
2624
|
+
.int()
|
|
2625
|
+
.min(2)
|
|
2626
|
+
.max(1000)
|
|
2627
|
+
.optional()
|
|
2628
|
+
.describe("Points to return, 2-1000 (default 200)."),
|
|
2629
|
+
},
|
|
2630
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2631
|
+
annotations: readOnlyAnnotations("Prediction-market outcome price history"),
|
|
2632
|
+
}, async ({ source, slug, interval, maxPoints }) => present(mapSuccessfulBody(await client.getPublicPmPriceHistory(source, slug, { interval }), (data) => compactPublicPmPriceHistory(data, maxPoints ?? 200))));
|
|
2633
|
+
server.registerTool("pm_data_orderbook", {
|
|
2634
|
+
title: "Prediction-market outcome order book",
|
|
2635
|
+
description: "Free public live order book for one outcome of an event (the same " +
|
|
2636
|
+
"outcome pm_data_price_history reads, or outcomeExternalMarketId). " +
|
|
2637
|
+
"bid, ask, midpoint and level prices are 0-1 fractions, not 0-100 " +
|
|
2638
|
+
"points. Each side is cut to its best `depth` levels as served, with " +
|
|
2639
|
+
"the full level counts in bidLevels/askLevels. Polymarket and Kalshi " +
|
|
2640
|
+
"only; orderBook: null is valid (venue not served, no resting orders, " +
|
|
2641
|
+
"or a selected outcome book older than 10 s). This is fetch-time " +
|
|
2642
|
+
"depth, not an executable quote: use pm_quote for that. No API key " +
|
|
2643
|
+
"required.",
|
|
2644
|
+
inputSchema: {
|
|
2645
|
+
source: pmSourceSchema,
|
|
2646
|
+
slug: pmSlugSchema,
|
|
2647
|
+
outcomeExternalMarketId: z
|
|
2648
|
+
.string()
|
|
2649
|
+
.trim()
|
|
2650
|
+
.min(1)
|
|
2651
|
+
.max(200)
|
|
2652
|
+
.optional()
|
|
2653
|
+
.describe("Venue market id of another outcome on the event (from pm_data_event)."),
|
|
2654
|
+
depth: z
|
|
2655
|
+
.number()
|
|
2656
|
+
.int()
|
|
2657
|
+
.min(1)
|
|
2658
|
+
.max(50)
|
|
2659
|
+
.optional()
|
|
2660
|
+
.describe("Levels per side, 1-50 (default 10)."),
|
|
2661
|
+
},
|
|
2662
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2663
|
+
annotations: readOnlyAnnotations("Prediction-market outcome order book"),
|
|
2664
|
+
}, async ({ source, slug, outcomeExternalMarketId, depth }) => present(mapSuccessfulBody(await client.getPublicPmOrderBook(source, slug, {
|
|
2665
|
+
outcomeExternalMarketId,
|
|
2666
|
+
}), (data) => compactPublicPmOrderBook(data, depth ?? 10))));
|
|
2667
|
+
server.registerTool("pm_data_resolved", {
|
|
2668
|
+
title: "Resolved prediction markets archive",
|
|
2669
|
+
description: "Free public archive of definitively resolved events across all " +
|
|
2670
|
+
"venues, newest settlement first. Each row has the winning " +
|
|
2671
|
+
"resolutionOutcome (probability in 0-100 points) and a compact event. " +
|
|
2672
|
+
"resolvedAt is set only when the venue's resolution time is " +
|
|
2673
|
+
"provider-verified (resolvedAtVerified); otherwise closedAt carries " +
|
|
2674
|
+
"the observed close. Pass year (and optionally month) for one " +
|
|
2675
|
+
"calendar slice. minVolume keeps substantial markets; " +
|
|
2676
|
+
"meta.totalResolved is then null. No API key required.",
|
|
2677
|
+
inputSchema: {
|
|
2678
|
+
limit: z
|
|
2679
|
+
.number()
|
|
2680
|
+
.int()
|
|
2681
|
+
.min(1)
|
|
2682
|
+
.max(200)
|
|
2683
|
+
.optional()
|
|
2684
|
+
.describe("Rows, 1-100 (default 10); up to 200 when year is set."),
|
|
2685
|
+
offset: z
|
|
2686
|
+
.number()
|
|
2687
|
+
.int()
|
|
2688
|
+
.min(0)
|
|
2689
|
+
.optional()
|
|
2690
|
+
.describe("Pagination offset (default 0)."),
|
|
2691
|
+
source: pmSourceSchema.optional(),
|
|
2692
|
+
minVolume: z
|
|
2693
|
+
.number()
|
|
2694
|
+
.min(0)
|
|
2695
|
+
.optional()
|
|
2696
|
+
.describe("Lifetime-volume floor (default 0, no floor)."),
|
|
2697
|
+
year: z
|
|
2698
|
+
.number()
|
|
2699
|
+
.int()
|
|
2700
|
+
.min(2010)
|
|
2701
|
+
.max(2100)
|
|
2702
|
+
.optional()
|
|
2703
|
+
.describe("Calendar year slice. Required when month is set."),
|
|
2704
|
+
month: z
|
|
2705
|
+
.number()
|
|
2706
|
+
.int()
|
|
2707
|
+
.min(1)
|
|
2708
|
+
.max(12)
|
|
2709
|
+
.optional()
|
|
2710
|
+
.describe("Calendar month 1-12; needs year."),
|
|
2711
|
+
fiat: fiatSchema,
|
|
2712
|
+
},
|
|
2713
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2714
|
+
annotations: readOnlyAnnotations("Resolved prediction markets archive"),
|
|
2715
|
+
}, async ({ limit, offset, source, minVolume, year, month, fiat }) => {
|
|
2716
|
+
if (month !== undefined && year === undefined) {
|
|
2717
|
+
return present({
|
|
2718
|
+
ok: false,
|
|
2719
|
+
status: 400,
|
|
2720
|
+
data: {
|
|
2721
|
+
error: "invalid_period",
|
|
2722
|
+
message: "month requires year.",
|
|
2723
|
+
},
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
const max = year === undefined ? 100 : 200;
|
|
2727
|
+
return present(mapSuccessfulBody(await client.listPublicPmResolved({
|
|
2728
|
+
limit: Math.min(limit ?? 10, max),
|
|
2729
|
+
offset,
|
|
2730
|
+
source,
|
|
2731
|
+
minVolume,
|
|
2732
|
+
year,
|
|
2733
|
+
month,
|
|
2734
|
+
fiat,
|
|
2735
|
+
}), compactPublicPmSettled));
|
|
2736
|
+
});
|
|
2737
|
+
server.registerTool("pm_data_expiring", {
|
|
2738
|
+
title: "Open prediction markets expiring soon",
|
|
2739
|
+
description: "Free public list of OPEN events whose endDate falls within the next " +
|
|
2740
|
+
"`days` (default 7), soonest first, across all venues. Thin markets " +
|
|
2741
|
+
"are excluded (24h volume under 1,000 and lifetime volume under " +
|
|
2742
|
+
"10,000 in venue units). Rows are compact events plus expiresInMs " +
|
|
2743
|
+
"(milliseconds from the response to endDate). endDate is the venue's " +
|
|
2744
|
+
"scheduled end, not a settlement guarantee. No API key required.",
|
|
2745
|
+
inputSchema: {
|
|
2746
|
+
limit: z
|
|
2747
|
+
.number()
|
|
2748
|
+
.int()
|
|
2749
|
+
.min(1)
|
|
2750
|
+
.max(100)
|
|
2751
|
+
.optional()
|
|
2752
|
+
.describe("Rows, 1-100 (default 10)."),
|
|
2753
|
+
offset: z
|
|
2754
|
+
.number()
|
|
2755
|
+
.int()
|
|
2756
|
+
.min(0)
|
|
2757
|
+
.optional()
|
|
2758
|
+
.describe("Pagination offset (default 0)."),
|
|
2759
|
+
days: z
|
|
2760
|
+
.number()
|
|
2761
|
+
.int()
|
|
2762
|
+
.min(1)
|
|
2763
|
+
.max(30)
|
|
2764
|
+
.optional()
|
|
2765
|
+
.describe("Lookahead window in days, 1-30 (default 7)."),
|
|
2766
|
+
source: pmSourceSchema.optional(),
|
|
2767
|
+
fiat: fiatSchema,
|
|
2768
|
+
},
|
|
2769
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2770
|
+
annotations: readOnlyAnnotations("Open prediction markets expiring soon"),
|
|
2771
|
+
}, async ({ limit, offset, days, source, fiat }) => present(mapSuccessfulBody(await client.listPublicPmExpiring({
|
|
2772
|
+
limit: limit ?? 10,
|
|
2773
|
+
offset,
|
|
2774
|
+
days,
|
|
2775
|
+
source,
|
|
2776
|
+
fiat,
|
|
2777
|
+
}), compactPublicPmExpiring)));
|
|
2778
|
+
server.registerTool("pm_data_surprises", {
|
|
2779
|
+
title: "Biggest resolved prediction-market surprises",
|
|
2780
|
+
description: "Free public surprise index: resolved binary markets whose winner " +
|
|
2781
|
+
"was priced under 50 (of 100) about 24 hours before settlement, " +
|
|
2782
|
+
"lowest T-24h winner probability first. surprise.t24hProbability and " +
|
|
2783
|
+
"surprise.t7dProbability are the WINNING outcome's probability in " +
|
|
2784
|
+
"0-100 points (t7d is often null). Multi-strike ladders and markets " +
|
|
2785
|
+
"without provider resolution are excluded. window filters by " +
|
|
2786
|
+
"resolution recency. No API key required.",
|
|
2787
|
+
inputSchema: {
|
|
2788
|
+
limit: z
|
|
2789
|
+
.number()
|
|
2790
|
+
.int()
|
|
2791
|
+
.min(1)
|
|
2792
|
+
.max(100)
|
|
2793
|
+
.optional()
|
|
2794
|
+
.describe("Rows, 1-100 (default 10)."),
|
|
2795
|
+
offset: z
|
|
2796
|
+
.number()
|
|
2797
|
+
.int()
|
|
2798
|
+
.min(0)
|
|
2799
|
+
.optional()
|
|
2800
|
+
.describe("Pagination offset (default 0)."),
|
|
2801
|
+
source: pmSourceSchema.optional(),
|
|
2802
|
+
window: z
|
|
2803
|
+
.enum(["7d", "30d", "90d", "all"])
|
|
2804
|
+
.optional()
|
|
2805
|
+
.describe("Resolved within this window (default all)."),
|
|
2806
|
+
fiat: fiatSchema,
|
|
2807
|
+
},
|
|
2808
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2809
|
+
annotations: readOnlyAnnotations("Biggest resolved prediction-market surprises"),
|
|
2810
|
+
}, async ({ limit, offset, source, window, fiat }) => present(mapSuccessfulBody(await client.listPublicPmSurprises({
|
|
2811
|
+
limit: limit ?? 10,
|
|
2812
|
+
offset,
|
|
2813
|
+
source,
|
|
2814
|
+
window,
|
|
2815
|
+
fiat,
|
|
2816
|
+
}), compactPublicPmSettled)));
|
|
2184
2817
|
server.registerTool("get_crypto_movers", {
|
|
2185
2818
|
title: "Top 24h crypto movers (universe scan)",
|
|
2186
2819
|
description: "Free public scan of CoinRithm's tracked crypto universe for the " +
|
|
@@ -2210,4 +2843,180 @@ export function registerTools(server, client) {
|
|
|
2210
2843
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2211
2844
|
annotations: readOnlyAnnotations("Top 24h crypto movers"),
|
|
2212
2845
|
}, async ({ direction, limit }) => present(mapSuccessfulBody(await client.getPublicCryptoMovers(direction ?? "gainers", limit ?? 20), compactPublicCryptoMovers)));
|
|
2846
|
+
server.registerTool("instrument_markets", {
|
|
2847
|
+
title: "Prediction-market strike ladders on an instrument",
|
|
2848
|
+
description: "Free public strike ladders: the prediction-market contracts written " +
|
|
2849
|
+
"on one crypto, TradFi or index price (for example, will BTC be " +
|
|
2850
|
+
"above $X at time T), grouped by expiry and settlement source, one " +
|
|
2851
|
+
"ladder per venue series, soonest expiry first. Contract " +
|
|
2852
|
+
"probability, bid and ask are venue prices in 0-100 points, never " +
|
|
2853
|
+
"CoinRithm forecasts; tradedRecently:false is not a live price. Each " +
|
|
2854
|
+
"ladder names its settlement source and referenceRelation " +
|
|
2855
|
+
"(explained once in referenceNotes); observation is at_time or " +
|
|
2856
|
+
"touch. No live price is included: reference names the series to " +
|
|
2857
|
+
"read. Venue outcome ids are omitted; use detail=full or " +
|
|
2858
|
+
"pm_data_event on the ladder's event. groups: [] means no contracts; " +
|
|
2859
|
+
"coverage.degraded:true means the read failed. No API key required.",
|
|
2860
|
+
inputSchema: {
|
|
2861
|
+
type: z
|
|
2862
|
+
.enum(["coin", "tradfi", "index", "macro"])
|
|
2863
|
+
.describe("coin (id = coin slug), tradfi (catalog ticker), index (SPX, NDX), macro (none yet)."),
|
|
2864
|
+
id: z
|
|
2865
|
+
.string()
|
|
2866
|
+
.trim()
|
|
2867
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/, "expected a coin slug or ticker")
|
|
2868
|
+
.describe("Coin slug such as bitcoin (the slug of a get_crypto_movers row, not its numeric coinId), or a ticker such as AAPL or SPX."),
|
|
2869
|
+
horizon: z
|
|
2870
|
+
.enum(["hour", "day", "week", "month", "year", "all"])
|
|
2871
|
+
.optional()
|
|
2872
|
+
.describe("Expiry window (default all)."),
|
|
2873
|
+
includeShortTerm: z
|
|
2874
|
+
.boolean()
|
|
2875
|
+
.optional()
|
|
2876
|
+
.describe("true adds up to 20 short-term up/down markets."),
|
|
2877
|
+
limit: z
|
|
2878
|
+
.number()
|
|
2879
|
+
.int()
|
|
2880
|
+
.min(1)
|
|
2881
|
+
.max(200)
|
|
2882
|
+
.optional()
|
|
2883
|
+
.describe("Ladders to return, 1-200 (default 10)."),
|
|
2884
|
+
detail: z
|
|
2885
|
+
.enum(["summary", "full"])
|
|
2886
|
+
.optional()
|
|
2887
|
+
.describe("summary (default) or the untouched record with venue outcome ids."),
|
|
2888
|
+
},
|
|
2889
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2890
|
+
annotations: readOnlyAnnotations("Prediction-market strike ladders on an instrument"),
|
|
2891
|
+
}, async ({ type, id, horizon, includeShortTerm, limit, detail }) => {
|
|
2892
|
+
const result = await client.getPublicInstrumentMarkets(type, id, {
|
|
2893
|
+
horizon,
|
|
2894
|
+
includeShortTerm,
|
|
2895
|
+
// 10 by default: the API's 60 returned about 274 KB for bitcoin.
|
|
2896
|
+
limit: limit ?? 10,
|
|
2897
|
+
});
|
|
2898
|
+
return present(detail === "full"
|
|
2899
|
+
? result
|
|
2900
|
+
: mapSuccessfulBody(result, compactInstrumentMarkets));
|
|
2901
|
+
});
|
|
2902
|
+
server.registerTool("tradfi_references", {
|
|
2903
|
+
title: "TradFi reference prices",
|
|
2904
|
+
description: "Free public TradFi reference prices for equities, ETFs, metals, " +
|
|
2905
|
+
"commodities and FX. A reference is the median of 2-3 derivatives-" +
|
|
2906
|
+
"venue index prices (Binance, Bybit, Gate) in USDT, recorded every " +
|
|
2907
|
+
"15 minutes: a venue index, not an exchange last trade, and the unit " +
|
|
2908
|
+
"is unverified. live is a near-real-time overlay (null when " +
|
|
2909
|
+
"unavailable); freshness is collection time. Omit ticker to page the " +
|
|
2910
|
+
"catalog (latest, 24h/7d change, 24h range, live, key fundamentals). " +
|
|
2911
|
+
"Pass ticker for one instrument with venue constituents, SEC " +
|
|
2912
|
+
"fundamentals, corporate events and history over window, sampled " +
|
|
2913
|
+
"to maxPoints. No API key required.",
|
|
2914
|
+
inputSchema: {
|
|
2915
|
+
ticker: z
|
|
2916
|
+
.string()
|
|
2917
|
+
.trim()
|
|
2918
|
+
.regex(/^[A-Za-z0-9]{1,20}$/, "expected a catalog ticker")
|
|
2919
|
+
.optional()
|
|
2920
|
+
.describe("Catalog ticker such as AAPL or XAU. Omit to list."),
|
|
2921
|
+
window: z
|
|
2922
|
+
.enum(["24h", "7d"])
|
|
2923
|
+
.optional()
|
|
2924
|
+
.describe("Detail only: history window (default 24h)."),
|
|
2925
|
+
maxPoints: z
|
|
2926
|
+
.number()
|
|
2927
|
+
.int()
|
|
2928
|
+
.min(2)
|
|
2929
|
+
.max(1000)
|
|
2930
|
+
.optional()
|
|
2931
|
+
.describe("Detail only: history points, 2-1000 (default 100)."),
|
|
2932
|
+
assetClass: z
|
|
2933
|
+
.enum(TRADFI_ASSET_CLASSES)
|
|
2934
|
+
.optional()
|
|
2935
|
+
.describe("List only: one asset class."),
|
|
2936
|
+
limit: z
|
|
2937
|
+
.number()
|
|
2938
|
+
.int()
|
|
2939
|
+
.min(1)
|
|
2940
|
+
.max(250)
|
|
2941
|
+
.optional()
|
|
2942
|
+
.describe("List only: rows, 1-250 (default 25)."),
|
|
2943
|
+
offset: z
|
|
2944
|
+
.number()
|
|
2945
|
+
.int()
|
|
2946
|
+
.min(0)
|
|
2947
|
+
.optional()
|
|
2948
|
+
.describe("List only: pagination offset (default 0)."),
|
|
2949
|
+
},
|
|
2950
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2951
|
+
annotations: readOnlyAnnotations("TradFi reference prices"),
|
|
2952
|
+
}, async ({ ticker, window, maxPoints, assetClass, limit, offset }) => present(ticker
|
|
2953
|
+
? mapSuccessfulBody(await client.getPublicTradfiReference(ticker, { window }), (data) => compactTradfiReference(data, maxPoints ?? 100))
|
|
2954
|
+
: mapSuccessfulBody(await client.getPublicTradfiReferences(), (data) => compactTradfiCatalog(data, {
|
|
2955
|
+
assetClass,
|
|
2956
|
+
limit: limit ?? 25,
|
|
2957
|
+
offset: offset ?? 0,
|
|
2958
|
+
}))));
|
|
2959
|
+
server.registerTool("crypto_price", {
|
|
2960
|
+
title: "Crypto price for one coin",
|
|
2961
|
+
description: "Free public price for one coin by slug (for example bitcoin): " +
|
|
2962
|
+
"price in fiat and USD, 24h change, high and low, market cap and " +
|
|
2963
|
+
"rank, 24h volume, and freshness. priceUpdatedAt is when the live " +
|
|
2964
|
+
"price was last written; priceStale:true means it is older than " +
|
|
2965
|
+
"priceStaleAfterHours (72), a last known price, not a live one. " +
|
|
2966
|
+
"marketCapSuspect flags a market cap CoinRithm does not trust. " +
|
|
2967
|
+
"price is null for a coin with no live price. fiatApplied:false " +
|
|
2968
|
+
"means the fiat code is not tracked and the values are USD. " +
|
|
2969
|
+
"candles adds bars over that range, merged to at most maxPoints. " +
|
|
2970
|
+
"coinId is the id get_candles and get_market_context take. " +
|
|
2971
|
+
"Unknown slug: 404. No API key required.",
|
|
2972
|
+
inputSchema: {
|
|
2973
|
+
slug: z
|
|
2974
|
+
.string()
|
|
2975
|
+
.trim()
|
|
2976
|
+
.regex(COIN_SLUG_PATTERN, "expected a coin slug such as bitcoin")
|
|
2977
|
+
.describe("Coin slug such as bitcoin or tether-usdt (the slug of a get_crypto_movers row)."),
|
|
2978
|
+
fiat: z
|
|
2979
|
+
.string()
|
|
2980
|
+
.trim()
|
|
2981
|
+
.regex(/^[A-Za-z]{3}$/, "expected a 3-letter currency code")
|
|
2982
|
+
.optional()
|
|
2983
|
+
.describe("Currency code such as EUR or JPY (default USD)."),
|
|
2984
|
+
candles: z
|
|
2985
|
+
.enum(COIN_CANDLE_RANGES)
|
|
2986
|
+
.optional()
|
|
2987
|
+
.describe("Add bars over this range: 1H (1-minute bars), 1D, 1W, 1M, 3M or 1Y (daily)."),
|
|
2988
|
+
maxPoints: z
|
|
2989
|
+
.number()
|
|
2990
|
+
.int()
|
|
2991
|
+
.min(2)
|
|
2992
|
+
.max(400)
|
|
2993
|
+
.optional()
|
|
2994
|
+
.describe("With candles: bars to return, 2-400 (default 24)."),
|
|
2995
|
+
},
|
|
2996
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
2997
|
+
annotations: readOnlyAnnotations("Crypto price for one coin"),
|
|
2998
|
+
}, async ({ slug, fiat, candles, maxPoints }) => {
|
|
2999
|
+
const currency = (fiat ?? "USD").toUpperCase();
|
|
3000
|
+
// USD is the API default; leaving it out shares the web's cache entry.
|
|
3001
|
+
const query = currency === "USD" ? {} : { fiat: currency };
|
|
3002
|
+
const [profile, bars] = await Promise.all([
|
|
3003
|
+
client.getPublicCoin(slug, query),
|
|
3004
|
+
candles
|
|
3005
|
+
? client.getPublicCoinCandles(slug, { range: candles, ...query })
|
|
3006
|
+
: Promise.resolve(undefined),
|
|
3007
|
+
]);
|
|
3008
|
+
const result = mapSuccessfulBody(profile, (data) => compactCryptoPrice(data, currency));
|
|
3009
|
+
if (!result.ok || !bars || !isJsonRecord(result.data)) {
|
|
3010
|
+
return present(result);
|
|
3011
|
+
}
|
|
3012
|
+
return present({
|
|
3013
|
+
...result,
|
|
3014
|
+
data: {
|
|
3015
|
+
...result.data,
|
|
3016
|
+
candles: bars.ok
|
|
3017
|
+
? compactCoinCandles(bars.data, maxPoints ?? 24)
|
|
3018
|
+
: { httpStatus: bars.status, body: bars.data },
|
|
3019
|
+
},
|
|
3020
|
+
});
|
|
3021
|
+
});
|
|
2213
3022
|
}
|