pmxtjs 1.5.7 → 1.7.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/pmxt/client.ts CHANGED
@@ -37,6 +37,10 @@ import {
37
37
  SearchIn,
38
38
  UnifiedEvent,
39
39
  ExecutionPriceResult,
40
+ MarketFilterCriteria,
41
+ MarketFilterFunction,
42
+ EventFilterCriteria,
43
+ EventFilterFunction,
40
44
  } from "./models.js";
41
45
 
42
46
  import { ServerManager } from "./server-manager.js";
@@ -935,6 +939,288 @@ export abstract class Exchange {
935
939
  throw new Error(`Failed to get execution price: ${error}`);
936
940
  }
937
941
  }
942
+
943
+ // ----------------------------------------------------------------------------
944
+ // Filtering Methods
945
+ // ----------------------------------------------------------------------------
946
+
947
+ /**
948
+ * Filter markets based on criteria or custom function.
949
+ *
950
+ * @param markets - Array of markets to filter
951
+ * @param criteria - Filter criteria object, string (simple text search), or predicate function
952
+ * @returns Filtered array of markets
953
+ *
954
+ * @example Simple text search
955
+ * api.filterMarkets(markets, 'Trump')
956
+ *
957
+ * @example Advanced filtering
958
+ * api.filterMarkets(markets, {
959
+ * text: 'Trump',
960
+ * searchIn: ['title', 'tags'],
961
+ * volume24h: { min: 10000 },
962
+ * category: 'Politics',
963
+ * price: { outcome: 'yes', max: 0.5 }
964
+ * })
965
+ *
966
+ * @example Custom predicate
967
+ * api.filterMarkets(markets, m => m.liquidity > 5000 && m.yes?.price < 0.3)
968
+ */
969
+ filterMarkets(
970
+ markets: UnifiedMarket[],
971
+ criteria: string | MarketFilterCriteria | MarketFilterFunction
972
+ ): UnifiedMarket[] {
973
+ // Handle predicate function
974
+ if (typeof criteria === 'function') {
975
+ return markets.filter(criteria);
976
+ }
977
+
978
+ // Handle simple string search
979
+ if (typeof criteria === 'string') {
980
+ const lowerQuery = criteria.toLowerCase();
981
+ return markets.filter(m =>
982
+ m.title.toLowerCase().includes(lowerQuery)
983
+ );
984
+ }
985
+
986
+ // Handle criteria object
987
+ return markets.filter(market => {
988
+ // Text search
989
+ if (criteria.text) {
990
+ const lowerQuery = criteria.text.toLowerCase();
991
+ const searchIn = criteria.searchIn || ['title'];
992
+ let textMatch = false;
993
+
994
+ for (const field of searchIn) {
995
+ if (field === 'title' && market.title?.toLowerCase().includes(lowerQuery)) {
996
+ textMatch = true;
997
+ break;
998
+ }
999
+ if (field === 'description' && market.description?.toLowerCase().includes(lowerQuery)) {
1000
+ textMatch = true;
1001
+ break;
1002
+ }
1003
+ if (field === 'category' && market.category?.toLowerCase().includes(lowerQuery)) {
1004
+ textMatch = true;
1005
+ break;
1006
+ }
1007
+ if (field === 'tags' && market.tags?.some(tag => tag.toLowerCase().includes(lowerQuery))) {
1008
+ textMatch = true;
1009
+ break;
1010
+ }
1011
+ if (field === 'outcomes' && market.outcomes?.some(o => o.label.toLowerCase().includes(lowerQuery))) {
1012
+ textMatch = true;
1013
+ break;
1014
+ }
1015
+ }
1016
+
1017
+ if (!textMatch) return false;
1018
+ }
1019
+
1020
+ // Category filter
1021
+ if (criteria.category && market.category !== criteria.category) {
1022
+ return false;
1023
+ }
1024
+
1025
+ // Tags filter (match ANY of the provided tags)
1026
+ if (criteria.tags && criteria.tags.length > 0) {
1027
+ const hasMatchingTag = criteria.tags.some(tag =>
1028
+ market.tags?.some(marketTag =>
1029
+ marketTag.toLowerCase() === tag.toLowerCase()
1030
+ )
1031
+ );
1032
+ if (!hasMatchingTag) return false;
1033
+ }
1034
+
1035
+ // Volume24h filter
1036
+ if (criteria.volume24h) {
1037
+ if (criteria.volume24h.min !== undefined && market.volume24h < criteria.volume24h.min) {
1038
+ return false;
1039
+ }
1040
+ if (criteria.volume24h.max !== undefined && market.volume24h > criteria.volume24h.max) {
1041
+ return false;
1042
+ }
1043
+ }
1044
+
1045
+ // Volume filter
1046
+ if (criteria.volume) {
1047
+ if (criteria.volume.min !== undefined && (market.volume || 0) < criteria.volume.min) {
1048
+ return false;
1049
+ }
1050
+ if (criteria.volume.max !== undefined && (market.volume || 0) > criteria.volume.max) {
1051
+ return false;
1052
+ }
1053
+ }
1054
+
1055
+ // Liquidity filter
1056
+ if (criteria.liquidity) {
1057
+ if (criteria.liquidity.min !== undefined && market.liquidity < criteria.liquidity.min) {
1058
+ return false;
1059
+ }
1060
+ if (criteria.liquidity.max !== undefined && market.liquidity > criteria.liquidity.max) {
1061
+ return false;
1062
+ }
1063
+ }
1064
+
1065
+ // OpenInterest filter
1066
+ if (criteria.openInterest) {
1067
+ if (criteria.openInterest.min !== undefined && (market.openInterest || 0) < criteria.openInterest.min) {
1068
+ return false;
1069
+ }
1070
+ if (criteria.openInterest.max !== undefined && (market.openInterest || 0) > criteria.openInterest.max) {
1071
+ return false;
1072
+ }
1073
+ }
1074
+
1075
+ // ResolutionDate filter
1076
+ if (criteria.resolutionDate && market.resolutionDate) {
1077
+ const resDate = market.resolutionDate;
1078
+ if (criteria.resolutionDate.before && resDate >= criteria.resolutionDate.before) {
1079
+ return false;
1080
+ }
1081
+ if (criteria.resolutionDate.after && resDate <= criteria.resolutionDate.after) {
1082
+ return false;
1083
+ }
1084
+ }
1085
+
1086
+ // Price filter (for binary markets)
1087
+ if (criteria.price) {
1088
+ const outcome = market[criteria.price.outcome];
1089
+ if (!outcome) return false;
1090
+
1091
+ if (criteria.price.min !== undefined && outcome.price < criteria.price.min) {
1092
+ return false;
1093
+ }
1094
+ if (criteria.price.max !== undefined && outcome.price > criteria.price.max) {
1095
+ return false;
1096
+ }
1097
+ }
1098
+
1099
+ // Price change filter
1100
+ if (criteria.priceChange24h) {
1101
+ const outcome = market[criteria.priceChange24h.outcome];
1102
+ if (!outcome || outcome.priceChange24h === undefined) return false;
1103
+
1104
+ if (criteria.priceChange24h.min !== undefined && outcome.priceChange24h < criteria.priceChange24h.min) {
1105
+ return false;
1106
+ }
1107
+ if (criteria.priceChange24h.max !== undefined && outcome.priceChange24h > criteria.priceChange24h.max) {
1108
+ return false;
1109
+ }
1110
+ }
1111
+
1112
+ return true;
1113
+ });
1114
+ }
1115
+
1116
+ /**
1117
+ * Filter events based on criteria or custom function.
1118
+ *
1119
+ * @param events - Array of events to filter
1120
+ * @param criteria - Filter criteria object, string (simple text search), or predicate function
1121
+ * @returns Filtered array of events
1122
+ *
1123
+ * @example Simple text search
1124
+ * api.filterEvents(events, 'Trump')
1125
+ *
1126
+ * @example Advanced filtering
1127
+ * api.filterEvents(events, {
1128
+ * text: 'Election',
1129
+ * searchIn: ['title', 'tags'],
1130
+ * category: 'Politics',
1131
+ * marketCount: { min: 5 }
1132
+ * })
1133
+ *
1134
+ * @example Custom predicate
1135
+ * api.filterEvents(events, e => e.markets.length > 10)
1136
+ */
1137
+ filterEvents(
1138
+ events: UnifiedEvent[],
1139
+ criteria: string | EventFilterCriteria | EventFilterFunction
1140
+ ): UnifiedEvent[] {
1141
+ // Handle predicate function
1142
+ if (typeof criteria === 'function') {
1143
+ return events.filter(criteria);
1144
+ }
1145
+
1146
+ // Handle simple string search
1147
+ if (typeof criteria === 'string') {
1148
+ const lowerQuery = criteria.toLowerCase();
1149
+ return events.filter(e =>
1150
+ e.title.toLowerCase().includes(lowerQuery)
1151
+ );
1152
+ }
1153
+
1154
+ // Handle criteria object
1155
+ return events.filter(event => {
1156
+ // Text search
1157
+ if (criteria.text) {
1158
+ const lowerQuery = criteria.text.toLowerCase();
1159
+ const searchIn = criteria.searchIn || ['title'];
1160
+ let textMatch = false;
1161
+
1162
+ for (const field of searchIn) {
1163
+ if (field === 'title' && event.title?.toLowerCase().includes(lowerQuery)) {
1164
+ textMatch = true;
1165
+ break;
1166
+ }
1167
+ if (field === 'description' && event.description?.toLowerCase().includes(lowerQuery)) {
1168
+ textMatch = true;
1169
+ break;
1170
+ }
1171
+ if (field === 'category' && event.category?.toLowerCase().includes(lowerQuery)) {
1172
+ textMatch = true;
1173
+ break;
1174
+ }
1175
+ if (field === 'tags' && event.tags?.some(tag => tag.toLowerCase().includes(lowerQuery))) {
1176
+ textMatch = true;
1177
+ break;
1178
+ }
1179
+ }
1180
+
1181
+ if (!textMatch) return false;
1182
+ }
1183
+
1184
+ // Category filter
1185
+ if (criteria.category && event.category !== criteria.category) {
1186
+ return false;
1187
+ }
1188
+
1189
+ // Tags filter (match ANY of the provided tags)
1190
+ if (criteria.tags && criteria.tags.length > 0) {
1191
+ const hasMatchingTag = criteria.tags.some(tag =>
1192
+ event.tags?.some(eventTag =>
1193
+ eventTag.toLowerCase() === tag.toLowerCase()
1194
+ )
1195
+ );
1196
+ if (!hasMatchingTag) return false;
1197
+ }
1198
+
1199
+ // Market count filter
1200
+ if (criteria.marketCount) {
1201
+ const count = event.markets.length;
1202
+ if (criteria.marketCount.min !== undefined && count < criteria.marketCount.min) {
1203
+ return false;
1204
+ }
1205
+ if (criteria.marketCount.max !== undefined && count > criteria.marketCount.max) {
1206
+ return false;
1207
+ }
1208
+ }
1209
+
1210
+ // Total volume filter
1211
+ if (criteria.totalVolume) {
1212
+ const totalVolume = event.markets.reduce((sum, m) => sum + m.volume24h, 0);
1213
+ if (criteria.totalVolume.min !== undefined && totalVolume < criteria.totalVolume.min) {
1214
+ return false;
1215
+ }
1216
+ if (criteria.totalVolume.max !== undefined && totalVolume > criteria.totalVolume.max) {
1217
+ return false;
1218
+ }
1219
+ }
1220
+
1221
+ return true;
1222
+ });
1223
+ }
938
1224
  }
939
1225
 
940
1226
  /**
package/pmxt/models.ts CHANGED
@@ -352,10 +352,97 @@ export interface UnifiedEvent {
352
352
 
353
353
  /**
354
354
  * Search for markets within this event by keyword.
355
- *
355
+ *
356
356
  * @param query - Search query (case-insensitive)
357
357
  * @param searchIn - Where to search - "title", "description", or "both"
358
358
  * @returns List of matching markets
359
359
  */
360
360
  searchMarkets(query: string, searchIn?: SearchIn): UnifiedMarket[];
361
361
  }
362
+
363
+ // ----------------------------------------------------------------------------
364
+ // Advanced Filtering Types
365
+ // ----------------------------------------------------------------------------
366
+
367
+ /**
368
+ * Advanced criteria for filtering markets.
369
+ * Supports text search, numeric ranges, dates, categories, and price filters.
370
+ */
371
+ export interface MarketFilterCriteria {
372
+ /** Text search query */
373
+ text?: string;
374
+
375
+ /** Fields to search in (default: ['title']) */
376
+ searchIn?: ('title' | 'description' | 'category' | 'tags' | 'outcomes')[];
377
+
378
+ /** Filter by 24-hour volume */
379
+ volume24h?: { min?: number; max?: number };
380
+
381
+ /** Filter by total volume */
382
+ volume?: { min?: number; max?: number };
383
+
384
+ /** Filter by liquidity */
385
+ liquidity?: { min?: number; max?: number };
386
+
387
+ /** Filter by open interest */
388
+ openInterest?: { min?: number; max?: number };
389
+
390
+ /** Filter by resolution date */
391
+ resolutionDate?: {
392
+ before?: Date;
393
+ after?: Date;
394
+ };
395
+
396
+ /** Filter by category */
397
+ category?: string;
398
+
399
+ /** Filter by tags (matches if market has ANY of these) */
400
+ tags?: string[];
401
+
402
+ /** Filter by outcome price (for binary markets) */
403
+ price?: {
404
+ outcome: 'yes' | 'no' | 'up' | 'down';
405
+ min?: number;
406
+ max?: number;
407
+ };
408
+
409
+ /** Filter by 24-hour price change */
410
+ priceChange24h?: {
411
+ outcome: 'yes' | 'no' | 'up' | 'down';
412
+ min?: number;
413
+ max?: number;
414
+ };
415
+ }
416
+
417
+ /**
418
+ * Function type for custom market filtering logic.
419
+ */
420
+ export type MarketFilterFunction = (market: UnifiedMarket) => boolean;
421
+
422
+ /**
423
+ * Advanced criteria for filtering events.
424
+ */
425
+ export interface EventFilterCriteria {
426
+ /** Text search query */
427
+ text?: string;
428
+
429
+ /** Fields to search in (default: ['title']) */
430
+ searchIn?: ('title' | 'description' | 'category' | 'tags')[];
431
+
432
+ /** Filter by category */
433
+ category?: string;
434
+
435
+ /** Filter by tags (matches if event has ANY of these) */
436
+ tags?: string[];
437
+
438
+ /** Filter by number of markets in the event */
439
+ marketCount?: { min?: number; max?: number };
440
+
441
+ /** Filter by total volume across all markets */
442
+ totalVolume?: { min?: number; max?: number };
443
+ }
444
+
445
+ /**
446
+ * Function type for custom event filtering logic.
447
+ */
448
+ export type EventFilterFunction = (event: UnifiedEvent) => boolean;
@@ -203,6 +203,21 @@ export class ServerManager {
203
203
  return false;
204
204
  }
205
205
 
206
+ /**
207
+ * Stop the currently running server.
208
+ */
209
+ async stop(): Promise<void> {
210
+ await this.killOldServer();
211
+ }
212
+
213
+ /**
214
+ * Restart the server.
215
+ */
216
+ async restart(): Promise<void> {
217
+ await this.stop();
218
+ await this.ensureServerRunning();
219
+ }
220
+
206
221
  private async killOldServer(): Promise<void> {
207
222
  const info = this.getServerInfo();
208
223
  if (info && info.pid) {