@riocrypto/common 1.0.2715 → 1.0.2716

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.
Files changed (40) hide show
  1. package/build/events/lmax-trade-updated-event.d.ts +7 -0
  2. package/build/events/lmax-trade-updated-event.js +2 -0
  3. package/build/events/rio-settings-updated-event.d.ts +2 -0
  4. package/build/events/subjects.d.ts +1 -0
  5. package/build/events/subjects.js +1 -0
  6. package/build/helpers/get-minute-of-day-in-timezone.d.ts +13 -0
  7. package/build/helpers/get-minute-of-day-in-timezone.js +37 -0
  8. package/build/helpers/resolve-fx-price-source.d.ts +65 -0
  9. package/build/helpers/resolve-fx-price-source.js +163 -0
  10. package/build/index.d.ts +12 -0
  11. package/build/index.js +12 -0
  12. package/build/types/dashboard-table-type.d.ts +1 -0
  13. package/build/types/dashboard-table-type.js +1 -0
  14. package/build/types/external-trade.d.ts +22 -0
  15. package/build/types/external-trading-provider.d.ts +1 -0
  16. package/build/types/external-trading-provider.js +1 -0
  17. package/build/types/fx-price-source-policy.d.ts +156 -0
  18. package/build/types/fx-price-source-policy.js +106 -0
  19. package/build/types/fx-price-source.d.ts +119 -0
  20. package/build/types/fx-price-source.js +82 -0
  21. package/build/types/fx-provider.d.ts +1 -0
  22. package/build/types/fx-provider.js +1 -0
  23. package/build/types/lmax-balances.d.ts +7 -0
  24. package/build/types/lmax-balances.js +2 -0
  25. package/build/types/lmax-fx-trade-status.d.ts +11 -0
  26. package/build/types/lmax-fx-trade-status.js +15 -0
  27. package/build/types/lmax-fx-trade.d.ts +35 -0
  28. package/build/types/lmax-fx-trade.js +2 -0
  29. package/build/types/lmax-order-book.d.ts +20 -0
  30. package/build/types/lmax-order-book.js +2 -0
  31. package/build/types/lmax-order-type.d.ts +6 -0
  32. package/build/types/lmax-order-type.js +11 -0
  33. package/build/types/lmax-side.d.ts +4 -0
  34. package/build/types/lmax-side.js +9 -0
  35. package/build/types/lmax-time-in-force.d.ts +6 -0
  36. package/build/types/lmax-time-in-force.js +11 -0
  37. package/build/types/market-data.d.ts +27 -16
  38. package/build/types/rio-settings-update.d.ts +2 -0
  39. package/build/types/rio-settings.d.ts +14 -0
  40. package/package.json +1 -1
@@ -0,0 +1,7 @@
1
+ import { Subjects } from "./subjects";
2
+ export interface LmaxTradeUpdatedEvent {
3
+ subject: Subjects.LmaxTradeUpdated;
4
+ data: {
5
+ id: string;
6
+ };
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,6 +1,7 @@
1
1
  import { Country } from "../types/country";
2
2
  import { DeferredPaymentType } from "../types/deferred-payment-type";
3
3
  import { Fiat } from "../types/fiat";
4
+ import { FXPriceSourcePolicies } from "../types/fx-price-source-policy";
4
5
  import { FXProvider } from "../types/fx-provider";
5
6
  import { FXTradingPolicies } from "../types/fx-trading-policy";
6
7
  import { Side } from "../types/side";
@@ -69,6 +70,7 @@ export interface RioSettingsUpdatedEvent {
69
70
  TVFXDataProvider?: {
70
71
  [key in Fiat]?: TVFXDataProvider;
71
72
  };
73
+ fxPriceSources?: FXPriceSourcePolicies;
72
74
  defaultTwoWaySettlementDateOffsetFee?: {
73
75
  [key in Country]?: {
74
76
  [key in Side]?: {
@@ -203,6 +203,7 @@ export declare enum Subjects {
203
203
  STPDepositCLABECreated = "stpDepositCLABE:created",
204
204
  ExternalTradeUpdated = "externalTrade:updated",
205
205
  StonexTradeUpdated = "stonexTrade:updated",
206
+ LmaxTradeUpdated = "lmaxTrade:updated",
206
207
  ExternalTradeRetryCompleted = "externalTrade:retryCompleted",
207
208
  AxeCreated = "axe:created",
208
209
  AxePartiallyTaken = "axe:partiallyTaken",
@@ -207,6 +207,7 @@ var Subjects;
207
207
  Subjects["STPDepositCLABECreated"] = "stpDepositCLABE:created";
208
208
  Subjects["ExternalTradeUpdated"] = "externalTrade:updated";
209
209
  Subjects["StonexTradeUpdated"] = "stonexTrade:updated";
210
+ Subjects["LmaxTradeUpdated"] = "lmaxTrade:updated";
210
211
  Subjects["ExternalTradeRetryCompleted"] = "externalTrade:retryCompleted";
211
212
  Subjects["AxeCreated"] = "axe:created";
212
213
  Subjects["AxePartiallyTaken"] = "axe:partiallyTaken";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Returns the minute-of-day in the given IANA timezone, in [0, 1440).
3
+ *
4
+ * Uses `Intl.DateTimeFormat` so DST transitions are handled by the
5
+ * platform rather than by manual offset arithmetic. Mexico stopped
6
+ * observing DST in 2022, but Peru, Colombia and the US windows this is
7
+ * also used for should not depend on that staying true.
8
+ *
9
+ * `now` is injectable so a caller evaluating several policies in one
10
+ * pass can pin a single timestamp and avoid straddling a minute
11
+ * boundary partway through.
12
+ */
13
+ export declare const getMinuteOfDayInTimezone: (now: Date, timezone: string) => number;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getMinuteOfDayInTimezone = void 0;
4
+ /**
5
+ * Returns the minute-of-day in the given IANA timezone, in [0, 1440).
6
+ *
7
+ * Uses `Intl.DateTimeFormat` so DST transitions are handled by the
8
+ * platform rather than by manual offset arithmetic. Mexico stopped
9
+ * observing DST in 2022, but Peru, Colombia and the US windows this is
10
+ * also used for should not depend on that staying true.
11
+ *
12
+ * `now` is injectable so a caller evaluating several policies in one
13
+ * pass can pin a single timestamp and avoid straddling a minute
14
+ * boundary partway through.
15
+ */
16
+ const getMinuteOfDayInTimezone = (now, timezone) => {
17
+ const parts = new Intl.DateTimeFormat("en-US", {
18
+ timeZone: timezone,
19
+ hour12: false,
20
+ hour: "2-digit",
21
+ minute: "2-digit",
22
+ }).formatToParts(now);
23
+ let hour = 0;
24
+ let minute = 0;
25
+ for (const part of parts) {
26
+ if (part.type === "hour")
27
+ hour = parseInt(part.value, 10);
28
+ if (part.type === "minute")
29
+ minute = parseInt(part.value, 10);
30
+ }
31
+ // Some Node versions format midnight as hour 24. Clamping keeps the
32
+ // result inside [0, 1440) so a window ending at 1440 is still matched.
33
+ if (hour === 24)
34
+ hour = 0;
35
+ return hour * 60 + minute;
36
+ };
37
+ exports.getMinuteOfDayInTimezone = getMinuteOfDayInTimezone;
@@ -0,0 +1,65 @@
1
+ import { Fiat } from "../types/fiat";
2
+ import { FXPriceSource, FXPriceSourceReason } from "../types/fx-price-source";
3
+ import { FXPriceSourcePolicyForFiat, FXPriceSourceWindow } from "../types/fx-price-source-policy";
4
+ export interface ResolvedFXPriceSource {
5
+ source: FXPriceSource;
6
+ reason: FXPriceSourceReason;
7
+ matchedWindow?: FXPriceSourceWindow;
8
+ }
9
+ /**
10
+ * The source a fiat is priced from when it has no policy at all, which
11
+ * is how every fiat looked before this setting existed.
12
+ *
13
+ * Per-fiat, and defined as the head of that fiat's selectable list, so
14
+ * each currency falls back to its own established publisher: TradingView
15
+ * for MXN, Bloomberg for PEN, SET-ICAP for COP. A single hard-coded
16
+ * default would have quietly pointed PEN and COP at a feed that does not
17
+ * quote them.
18
+ */
19
+ export declare const getDefaultFXPriceSource: (fiat: Fiat) => FXPriceSource;
20
+ export declare const isWellFormedFXPriceSourceWindow: (window: FXPriceSourceWindow, fiat: Fiat) => boolean;
21
+ /**
22
+ * Resolve which source a fiat's canonical rate should be taken from
23
+ * right now, according to configuration alone.
24
+ *
25
+ * This deliberately knows nothing about whether that source is actually
26
+ * healthy. Configuration resolution is stable and shared, so it lives
27
+ * here and the dashboard can call it to show an operator which feed is
28
+ * scheduled to be active without a round trip. Deciding to abandon an
29
+ * unhealthy source is a separate, faster-moving judgement that belongs
30
+ * to the arbiter in `market-data`, next to the observations it needs.
31
+ *
32
+ * Precedence is override, then schedule, then primary. Malformed
33
+ * configuration falls back rather than throwing: this sits on the path
34
+ * that writes the platform's spot rate several times a second, and a
35
+ * bad hand-edit in Mongo should degrade to the previous behavior instead
36
+ * of stopping pricing.
37
+ */
38
+ export declare const resolveConfiguredFXPriceSource: (policy: FXPriceSourcePolicyForFiat | undefined, fiat: Fiat, now?: Date) => ResolvedFXPriceSource;
39
+ /**
40
+ * The single statement of the window coverage rule, shared by the
41
+ * throwing validator on the `rio-settings` PATCH and by the dashboard
42
+ * editor's inline check, so the two cannot disagree about what a valid
43
+ * schedule is.
44
+ *
45
+ * Returns a human-readable description of the first problem, or null
46
+ * when the list is acceptable. An empty list is acceptable and means no
47
+ * schedule is configured.
48
+ *
49
+ * Windows must be ordered, strictly contiguous, start at 0 and end at
50
+ * 1440, matching `validateFXTradingPolicies`. Order is significant and
51
+ * is not sorted first on purpose: if an operator's rows are out of
52
+ * order, saying so is more useful than silently reinterpreting them.
53
+ */
54
+ export declare const findFXPriceSourceWindowCoverageIssue: (windows: FXPriceSourceWindow[], fiat: Fiat) => string | null;
55
+ /**
56
+ * Builds the contiguous three-window day that covers the common case of
57
+ * "use a different source for one stretch of the afternoon", so neither
58
+ * the operator nor the dashboard has to assemble the filler rows by
59
+ * hand. Returns a single full-day window when the stretch covers
60
+ * everything.
61
+ *
62
+ * Requiring full coverage is what makes the stored policy unambiguous;
63
+ * it should not also make the routine edit tedious.
64
+ */
65
+ export declare const buildContiguousFXPriceSourceWindows: (baseSource: FXPriceSource, windowSource: FXPriceSource, startMinute: number, endMinute: number) => FXPriceSourceWindow[];
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildContiguousFXPriceSourceWindows = exports.findFXPriceSourceWindowCoverageIssue = exports.resolveConfiguredFXPriceSource = exports.isWellFormedFXPriceSourceWindow = exports.getDefaultFXPriceSource = void 0;
4
+ const fx_price_source_1 = require("../types/fx-price-source");
5
+ const fx_price_source_policy_1 = require("../types/fx-price-source-policy");
6
+ const get_country_for_fiat_1 = require("./get-country-for-fiat");
7
+ const get_country_timezone_1 = require("./get-country-timezone");
8
+ const get_minute_of_day_in_timezone_1 = require("./get-minute-of-day-in-timezone");
9
+ /**
10
+ * The source a fiat is priced from when it has no policy at all, which
11
+ * is how every fiat looked before this setting existed.
12
+ *
13
+ * Per-fiat, and defined as the head of that fiat's selectable list, so
14
+ * each currency falls back to its own established publisher: TradingView
15
+ * for MXN, Bloomberg for PEN, SET-ICAP for COP. A single hard-coded
16
+ * default would have quietly pointed PEN and COP at a feed that does not
17
+ * quote them.
18
+ */
19
+ const getDefaultFXPriceSource = (fiat) => { var _a; return (_a = (0, fx_price_source_1.getSelectableFXPriceSources)(fiat)[0]) !== null && _a !== void 0 ? _a : fx_price_source_1.FXPriceSource.CurrencyAPI; };
20
+ exports.getDefaultFXPriceSource = getDefaultFXPriceSource;
21
+ const defaultResolution = (fiat) => ({
22
+ source: (0, exports.getDefaultFXPriceSource)(fiat),
23
+ reason: fx_price_source_1.FXPriceSourceReason.Primary,
24
+ });
25
+ const isWellFormedFXPriceSourceWindow = (window, fiat) => {
26
+ if (!window)
27
+ return false;
28
+ const { startMinute, endMinute, source } = window;
29
+ if (!Number.isInteger(startMinute) || !Number.isInteger(endMinute)) {
30
+ return false;
31
+ }
32
+ if (startMinute < 0 || endMinute > fx_price_source_policy_1.MINUTES_IN_DAY)
33
+ return false;
34
+ if (startMinute >= endMinute)
35
+ return false;
36
+ return (0, fx_price_source_1.isSelectableFXPriceSource)(fiat, source);
37
+ };
38
+ exports.isWellFormedFXPriceSourceWindow = isWellFormedFXPriceSourceWindow;
39
+ const windowMatchesMinute = (window, minuteOfDay) => minuteOfDay >= window.startMinute && minuteOfDay < window.endMinute;
40
+ /**
41
+ * Resolve which source a fiat's canonical rate should be taken from
42
+ * right now, according to configuration alone.
43
+ *
44
+ * This deliberately knows nothing about whether that source is actually
45
+ * healthy. Configuration resolution is stable and shared, so it lives
46
+ * here and the dashboard can call it to show an operator which feed is
47
+ * scheduled to be active without a round trip. Deciding to abandon an
48
+ * unhealthy source is a separate, faster-moving judgement that belongs
49
+ * to the arbiter in `market-data`, next to the observations it needs.
50
+ *
51
+ * Precedence is override, then schedule, then primary. Malformed
52
+ * configuration falls back rather than throwing: this sits on the path
53
+ * that writes the platform's spot rate several times a second, and a
54
+ * bad hand-edit in Mongo should degrade to the previous behavior instead
55
+ * of stopping pricing.
56
+ */
57
+ const resolveConfiguredFXPriceSource = (policy, fiat, now = new Date()) => {
58
+ var _a;
59
+ if (!policy)
60
+ return defaultResolution(fiat);
61
+ const override = policy.override;
62
+ if (override &&
63
+ (0, fx_price_source_1.isSelectableFXPriceSource)(fiat, override.source) &&
64
+ override.expiresAt &&
65
+ new Date(override.expiresAt).getTime() > now.getTime()) {
66
+ return {
67
+ source: override.source,
68
+ reason: fx_price_source_1.FXPriceSourceReason.Override,
69
+ };
70
+ }
71
+ const primary = (0, fx_price_source_1.isSelectableFXPriceSource)(fiat, policy.primarySource)
72
+ ? policy.primarySource
73
+ : (0, exports.getDefaultFXPriceSource)(fiat);
74
+ if (policy.mode !== fx_price_source_policy_1.FXPriceSourceMode.Scheduled) {
75
+ return { source: primary, reason: fx_price_source_1.FXPriceSourceReason.Primary };
76
+ }
77
+ const windows = (_a = policy.windows) !== null && _a !== void 0 ? _a : [];
78
+ if (windows.length === 0) {
79
+ return { source: primary, reason: fx_price_source_1.FXPriceSourceReason.Primary };
80
+ }
81
+ const timezone = (0, get_country_timezone_1.getCountryTimezone)((0, get_country_for_fiat_1.getCountryForFiat)(fiat));
82
+ const minuteOfDay = (0, get_minute_of_day_in_timezone_1.getMinuteOfDayInTimezone)(now, timezone);
83
+ const matchedWindow = windows.find((window) => (0, exports.isWellFormedFXPriceSourceWindow)(window, fiat) &&
84
+ windowMatchesMinute(window, minuteOfDay));
85
+ // Validated windows cover the whole day, so a miss means the stored
86
+ // policy is malformed. Falling back to the primary keeps pricing
87
+ // running on the previous behavior rather than stopping it.
88
+ if (!matchedWindow) {
89
+ return { source: primary, reason: fx_price_source_1.FXPriceSourceReason.Primary };
90
+ }
91
+ return {
92
+ source: matchedWindow.source,
93
+ reason: fx_price_source_1.FXPriceSourceReason.Schedule,
94
+ matchedWindow,
95
+ };
96
+ };
97
+ exports.resolveConfiguredFXPriceSource = resolveConfiguredFXPriceSource;
98
+ /**
99
+ * The single statement of the window coverage rule, shared by the
100
+ * throwing validator on the `rio-settings` PATCH and by the dashboard
101
+ * editor's inline check, so the two cannot disagree about what a valid
102
+ * schedule is.
103
+ *
104
+ * Returns a human-readable description of the first problem, or null
105
+ * when the list is acceptable. An empty list is acceptable and means no
106
+ * schedule is configured.
107
+ *
108
+ * Windows must be ordered, strictly contiguous, start at 0 and end at
109
+ * 1440, matching `validateFXTradingPolicies`. Order is significant and
110
+ * is not sorted first on purpose: if an operator's rows are out of
111
+ * order, saying so is more useful than silently reinterpreting them.
112
+ */
113
+ const findFXPriceSourceWindowCoverageIssue = (windows, fiat) => {
114
+ if (!Array.isArray(windows))
115
+ return "windows must be an array";
116
+ if (windows.length === 0)
117
+ return null;
118
+ for (let i = 0; i < windows.length; i += 1) {
119
+ if (!(0, exports.isWellFormedFXPriceSourceWindow)(windows[i], fiat)) {
120
+ return `window[${i}] needs whole-minute startMinute < endMinute within 0..${fx_price_source_policy_1.MINUTES_IN_DAY} and a source selectable for ${fiat} (${(0, fx_price_source_1.getSelectableFXPriceSources)(fiat).join(", ")})`;
121
+ }
122
+ }
123
+ if (windows[0].startMinute !== 0) {
124
+ return `window[0].startMinute must be 0`;
125
+ }
126
+ for (let i = 0; i < windows.length - 1; i += 1) {
127
+ if (windows[i + 1].startMinute !== windows[i].endMinute) {
128
+ return `windows must be contiguous: window[${i}].endMinute (${windows[i].endMinute}) must equal window[${i + 1}].startMinute (${windows[i + 1].startMinute})`;
129
+ }
130
+ }
131
+ const last = windows[windows.length - 1];
132
+ if (last.endMinute !== fx_price_source_policy_1.MINUTES_IN_DAY) {
133
+ return `window[${windows.length - 1}].endMinute must be ${fx_price_source_policy_1.MINUTES_IN_DAY} so the windows cover the full day`;
134
+ }
135
+ return null;
136
+ };
137
+ exports.findFXPriceSourceWindowCoverageIssue = findFXPriceSourceWindowCoverageIssue;
138
+ /**
139
+ * Builds the contiguous three-window day that covers the common case of
140
+ * "use a different source for one stretch of the afternoon", so neither
141
+ * the operator nor the dashboard has to assemble the filler rows by
142
+ * hand. Returns a single full-day window when the stretch covers
143
+ * everything.
144
+ *
145
+ * Requiring full coverage is what makes the stored policy unambiguous;
146
+ * it should not also make the routine edit tedious.
147
+ */
148
+ const buildContiguousFXPriceSourceWindows = (baseSource, windowSource, startMinute, endMinute) => {
149
+ const windows = [];
150
+ if (startMinute > 0) {
151
+ windows.push({ startMinute: 0, endMinute: startMinute, source: baseSource });
152
+ }
153
+ windows.push({ startMinute, endMinute, source: windowSource });
154
+ if (endMinute < fx_price_source_policy_1.MINUTES_IN_DAY) {
155
+ windows.push({
156
+ startMinute: endMinute,
157
+ endMinute: fx_price_source_policy_1.MINUTES_IN_DAY,
158
+ source: baseSource,
159
+ });
160
+ }
161
+ return windows;
162
+ };
163
+ exports.buildContiguousFXPriceSourceWindows = buildContiguousFXPriceSourceWindows;
package/build/index.d.ts CHANGED
@@ -267,6 +267,7 @@ export * from "./events/bank-account-needs-review-event";
267
267
  export * from "./events/bank-account-verification-dispatched-event";
268
268
  export * from "./events/external-trade-updated-event";
269
269
  export * from "./events/stonex-trade-updated-event";
270
+ export * from "./events/lmax-trade-updated-event";
270
271
  export * from "./events/binance-rfq-order-settlement-completed-event";
271
272
  export * from "./events/bank-account-verification-actual-account-holder-name-obtained-event";
272
273
  export * from "./events/user-onboarding-ready-for-review-slack-message-sent-event";
@@ -412,6 +413,8 @@ export * from "./types/arbitrage-alert";
412
413
  export * from "./types/arbitrage-asset";
413
414
  export * from "./types/arbitrage-frequency";
414
415
  export * from "./types/arbitrage-provider";
416
+ export * from "./types/fx-price-source";
417
+ export * from "./types/fx-price-source-policy";
415
418
  export * from "./types/fx-provider";
416
419
  export * from "./types/fx-trading-policy";
417
420
  export * from "./types/liquidity-automation-frequency";
@@ -483,6 +486,13 @@ export * from "./types/stonex-settlement-type";
483
486
  export * from "./types/stonex-log";
484
487
  export * from "./types/stonex-order-type";
485
488
  export * from "./types/stonex-time-in-force";
489
+ export * from "./types/lmax-fx-trade";
490
+ export * from "./types/lmax-fx-trade-status";
491
+ export * from "./types/lmax-order-book";
492
+ export * from "./types/lmax-balances";
493
+ export * from "./types/lmax-side";
494
+ export * from "./types/lmax-order-type";
495
+ export * from "./types/lmax-time-in-force";
486
496
  export * from "./types/emarkets-bot-status";
487
497
  export * from "./types/transnetwork-bot-status";
488
498
  export * from "./types/inbound-bank-deposit";
@@ -578,6 +588,8 @@ export * from "./helpers/get-country-timezone";
578
588
  export * from "./helpers/get-active-platform-fee-ranges";
579
589
  export * from "./helpers/find-platform-fee-range-conflict";
580
590
  export * from "./helpers/get-country-for-fiat";
591
+ export * from "./helpers/get-minute-of-day-in-timezone";
592
+ export * from "./helpers/resolve-fx-price-source";
581
593
  export * from "./helpers/get-processors-for-corridor";
582
594
  export * from "./helpers/get-default-processor";
583
595
  export * from "./helpers/get-routing-candidates";
package/build/index.js CHANGED
@@ -283,6 +283,7 @@ __exportStar(require("./events/bank-account-needs-review-event"), exports);
283
283
  __exportStar(require("./events/bank-account-verification-dispatched-event"), exports);
284
284
  __exportStar(require("./events/external-trade-updated-event"), exports);
285
285
  __exportStar(require("./events/stonex-trade-updated-event"), exports);
286
+ __exportStar(require("./events/lmax-trade-updated-event"), exports);
286
287
  __exportStar(require("./events/binance-rfq-order-settlement-completed-event"), exports);
287
288
  __exportStar(require("./events/bank-account-verification-actual-account-holder-name-obtained-event"), exports);
288
289
  __exportStar(require("./events/user-onboarding-ready-for-review-slack-message-sent-event"), exports);
@@ -428,6 +429,8 @@ __exportStar(require("./types/arbitrage-alert"), exports);
428
429
  __exportStar(require("./types/arbitrage-asset"), exports);
429
430
  __exportStar(require("./types/arbitrage-frequency"), exports);
430
431
  __exportStar(require("./types/arbitrage-provider"), exports);
432
+ __exportStar(require("./types/fx-price-source"), exports);
433
+ __exportStar(require("./types/fx-price-source-policy"), exports);
431
434
  __exportStar(require("./types/fx-provider"), exports);
432
435
  __exportStar(require("./types/fx-trading-policy"), exports);
433
436
  __exportStar(require("./types/liquidity-automation-frequency"), exports);
@@ -499,6 +502,13 @@ __exportStar(require("./types/stonex-settlement-type"), exports);
499
502
  __exportStar(require("./types/stonex-log"), exports);
500
503
  __exportStar(require("./types/stonex-order-type"), exports);
501
504
  __exportStar(require("./types/stonex-time-in-force"), exports);
505
+ __exportStar(require("./types/lmax-fx-trade"), exports);
506
+ __exportStar(require("./types/lmax-fx-trade-status"), exports);
507
+ __exportStar(require("./types/lmax-order-book"), exports);
508
+ __exportStar(require("./types/lmax-balances"), exports);
509
+ __exportStar(require("./types/lmax-side"), exports);
510
+ __exportStar(require("./types/lmax-order-type"), exports);
511
+ __exportStar(require("./types/lmax-time-in-force"), exports);
502
512
  __exportStar(require("./types/emarkets-bot-status"), exports);
503
513
  __exportStar(require("./types/transnetwork-bot-status"), exports);
504
514
  __exportStar(require("./types/inbound-bank-deposit"), exports);
@@ -594,6 +604,8 @@ __exportStar(require("./helpers/get-country-timezone"), exports);
594
604
  __exportStar(require("./helpers/get-active-platform-fee-ranges"), exports);
595
605
  __exportStar(require("./helpers/find-platform-fee-range-conflict"), exports);
596
606
  __exportStar(require("./helpers/get-country-for-fiat"), exports);
607
+ __exportStar(require("./helpers/get-minute-of-day-in-timezone"), exports);
608
+ __exportStar(require("./helpers/resolve-fx-price-source"), exports);
597
609
  __exportStar(require("./helpers/get-processors-for-corridor"), exports);
598
610
  __exportStar(require("./helpers/get-default-processor"), exports);
599
611
  __exportStar(require("./helpers/get-routing-candidates"), exports);
@@ -36,5 +36,6 @@ export declare enum DashboardTableType {
36
36
  IndicativeQuotePages = "indicativeQuotePages",
37
37
  BulkPayments = "bulkPayments",
38
38
  StonexOrders = "stonexOrders",
39
+ LmaxOrders = "lmaxOrders",
39
40
  CircleOrders = "circleOrders"
40
41
  }
@@ -40,5 +40,6 @@ var DashboardTableType;
40
40
  DashboardTableType["IndicativeQuotePages"] = "indicativeQuotePages";
41
41
  DashboardTableType["BulkPayments"] = "bulkPayments";
42
42
  DashboardTableType["StonexOrders"] = "stonexOrders";
43
+ DashboardTableType["LmaxOrders"] = "lmaxOrders";
43
44
  DashboardTableType["CircleOrders"] = "circleOrders";
44
45
  })(DashboardTableType = exports.DashboardTableType || (exports.DashboardTableType = {}));
@@ -4,6 +4,9 @@ import { ExternalTradeStatus } from "./external-trade-status";
4
4
  import { ExternalTradeType } from "./external-trade-type";
5
5
  import { ExternalTradingProvider } from "./external-trading-provider";
6
6
  import { Fiat } from "./fiat";
7
+ import { LmaxOrderType } from "./lmax-order-type";
8
+ import { LmaxSide } from "./lmax-side";
9
+ import { LmaxTimeInForce } from "./lmax-time-in-force";
7
10
  import { Side } from "./side";
8
11
  import { StonexSettlementType } from "./stonex-settlement-type";
9
12
  export interface ExternalTrade {
@@ -64,6 +67,25 @@ export interface ExternalTrade {
64
67
  rate?: number;
65
68
  takeQuoteTradeKey?: string;
66
69
  };
70
+ lmax?: {
71
+ instrumentId?: string;
72
+ symbol?: string;
73
+ lmaxSide?: LmaxSide;
74
+ quantity?: number;
75
+ orderType?: LmaxOrderType;
76
+ timeInForce?: LmaxTimeInForce;
77
+ price?: number;
78
+ stopPrice?: number;
79
+ instructionId?: string;
80
+ orderId?: string;
81
+ matchedQuantity?: number;
82
+ unfilledQuantity?: number;
83
+ cumulativeMatchedQuantity?: number;
84
+ cancelledQuantity?: number;
85
+ matchedCost?: number;
86
+ commission?: number;
87
+ text?: string;
88
+ };
67
89
  };
68
90
  externalTradingAlgorithmId?: string;
69
91
  arbitrageSessionId?: string;
@@ -4,6 +4,7 @@ export declare enum ExternalTradingProvider {
4
4
  Binance = "binance",
5
5
  Transnetwork = "transnetwork",
6
6
  StoneX = "stonex",
7
+ Lmax = "lmax",
7
8
  Kraken = "kraken",
8
9
  Coinbase = "coinbase",
9
10
  CircleRLI = "circleRLI",
@@ -8,6 +8,7 @@ var ExternalTradingProvider;
8
8
  ExternalTradingProvider["Binance"] = "binance";
9
9
  ExternalTradingProvider["Transnetwork"] = "transnetwork";
10
10
  ExternalTradingProvider["StoneX"] = "stonex";
11
+ ExternalTradingProvider["Lmax"] = "lmax";
11
12
  ExternalTradingProvider["Kraken"] = "kraken";
12
13
  ExternalTradingProvider["Coinbase"] = "coinbase";
13
14
  ExternalTradingProvider["CircleRLI"] = "circleRLI";
@@ -0,0 +1,156 @@
1
+ import { Fiat } from "./fiat";
2
+ import { FXPriceSource } from "./fx-price-source";
3
+ export declare enum FXPriceSourceMode {
4
+ /** Always use `primarySource`. Time windows are ignored. */
5
+ Manual = "manual",
6
+ /** Use the matching time window if there is one, else `primarySource`. */
7
+ Scheduled = "scheduled"
8
+ }
9
+ /**
10
+ * One time-of-day window naming the source a fiat is priced from while
11
+ * it is active.
12
+ *
13
+ * Minutes are from midnight in the currency's country-local timezone and
14
+ * the window is `[startMinute, endMinute)`.
15
+ *
16
+ * Windows follow the same coverage rule as `FXTradingPolicyTimeRange`:
17
+ * ordered, strictly contiguous, first starting at 0 and last ending at
18
+ * 1440, so every moment of the day maps to exactly one window. An empty
19
+ * list means "no schedule configured" and the resolver uses
20
+ * `primarySource`. A window spanning midnight is split into two rows
21
+ * with the same source rather than wrapping around.
22
+ *
23
+ * The cost of full coverage is that switching a fiat to LMAX for the
24
+ * Mexico City afternoon takes three rows rather than one. The benefit is
25
+ * that the configuration says what it means at every minute, an operator
26
+ * reading it does not have to hold "and the rest of the day is the
27
+ * primary" in their head, and the editor can render a single unbroken
28
+ * timeline they can check at a glance.
29
+ */
30
+ export interface FXPriceSourceWindow {
31
+ startMinute: number;
32
+ endMinute: number;
33
+ source: FXPriceSource;
34
+ }
35
+ /**
36
+ * When a source is considered unhealthy.
37
+ *
38
+ * `staleMs` is measured against the last report of any kind, and catches
39
+ * a dead publisher. `staticMs` is measured against the last time the
40
+ * value actually moved, and catches a publisher that is alive but whose
41
+ * upstream has frozen. The second is the one that matters for the
42
+ * TradingView USDMXN scrape, which keeps answering through the Mexico
43
+ * City afternoon while its number stops moving.
44
+ *
45
+ * `staticMs` needs headroom over a genuinely quiet market or it will cry
46
+ * wolf overnight and at weekends, which is why it is far larger than
47
+ * `staleMs` and why the defaults differ per source.
48
+ */
49
+ export interface FXPriceSourceHealthThresholds {
50
+ staleMs: number;
51
+ staticMs: number;
52
+ }
53
+ export interface FXPriceSourcePolicyForFiat {
54
+ mode: FXPriceSourceMode;
55
+ /**
56
+ * The source used in `Manual` mode.
57
+ *
58
+ * In `Scheduled` mode the windows cover the whole day, so this is only
59
+ * reached defensively, when the schedule is empty or a hand-edit in
60
+ * Mongo has left it malformed. It is kept required rather than
61
+ * optional precisely so that path always has somewhere safe to land:
62
+ * this sits on the code that writes the platform's spot rate several
63
+ * times a second, and "no source configured" must never be a
64
+ * reachable state.
65
+ */
66
+ primarySource: FXPriceSource;
67
+ /**
68
+ * Ordered, contiguous windows covering `[0, 1440)`. Only consulted
69
+ * when `mode` is `Scheduled`. Empty means no schedule.
70
+ */
71
+ windows?: FXPriceSourceWindow[];
72
+ /**
73
+ * Operator escape hatch for incidents. Wins over both the schedule and
74
+ * the primary while it is unexpired.
75
+ *
76
+ * The expiry is mandatory by construction rather than optional: an
77
+ * override with no end is how a temporary incident workaround becomes
78
+ * permanent undocumented behavior. `rio-settings` already sets the
79
+ * precedent of expiring a manually-set rate via cron with
80
+ * `previousDayUSDMXNConversionRate`.
81
+ */
82
+ override?: {
83
+ source: FXPriceSource;
84
+ expiresAt: Date;
85
+ };
86
+ /**
87
+ * Per-source overrides of `DEFAULT_FX_PRICE_SOURCE_HEALTH`. Absent
88
+ * entries take the default.
89
+ */
90
+ health?: {
91
+ [key in FXPriceSource]?: FXPriceSourceHealthThresholds;
92
+ };
93
+ /**
94
+ * Whether an unhealthy active source should hand over to the other
95
+ * healthy source.
96
+ *
97
+ * Off is the rollback, and reproduces today's behavior exactly: the
98
+ * feed is left where it is, the alert still fires, and a rate that goes
99
+ * far enough out of date is picked up by the currencyapi refresh in
100
+ * `market-data` as it always has been. Defaults to on.
101
+ */
102
+ failoverEnabled?: boolean;
103
+ }
104
+ export type FXPriceSourcePolicies = {
105
+ [key in Fiat]?: FXPriceSourcePolicyForFiat;
106
+ };
107
+ /**
108
+ * Health thresholds used when a fiat has no explicit override.
109
+ *
110
+ * TradingView is scraped roughly twice a second, so 30s without any
111
+ * report already means the publisher is gone; this matches the existing
112
+ * MXN staleness alert threshold in `exchange-rates-bot`. Its static
113
+ * window is deliberately generous because a scraped "last price" legally
114
+ * does sit still in thin conditions.
115
+ *
116
+ * LMAX is a live venue stream. It is allowed longer between ticks,
117
+ * because a quiet book genuinely does not print, but much less time
118
+ * frozen, because a top-of-book that has not moved in two minutes during
119
+ * a session means the stream is broken rather than the market is calm.
120
+ */
121
+ /**
122
+ * How often `market-data` asks currencyapi for a rate purely to prove the
123
+ * fallback still works.
124
+ *
125
+ * The fallback is otherwise only called once every live feed has gone
126
+ * quiet, which means that without a probe its health is unknown exactly
127
+ * until the moment it is needed. Each sweep is a single request covering
128
+ * every fiat, and the interval is deliberately long: this answers "would
129
+ * the parachute open", not "what is the price right now".
130
+ */
131
+ export declare const CURRENCY_API_PROBE_INTERVAL_MS: number;
132
+ /**
133
+ * How often currencyapi is polled while it is the selected source for at
134
+ * least one fiat.
135
+ *
136
+ * Standby cadence is far too slow to price against: it would leave the
137
+ * canonical rate up to half an hour old. Selecting currencyapi therefore
138
+ * switches it to a real polling loop. It is still not a stream, and the
139
+ * upstream refresh rate depends on the account plan, so this is the floor
140
+ * on how fresh a currencyapi-priced rate can be, not a promise of it.
141
+ */
142
+ export declare const CURRENCY_API_ACTIVE_POLL_INTERVAL_MS: number;
143
+ /**
144
+ * Thresholds applied to currencyapi while it is the active source.
145
+ *
146
+ * The standby defaults are sized for a half-hourly probe and would let a
147
+ * dead feed price trades for over an hour before anyone noticed, which is
148
+ * unacceptable once it is the thing quoting customers. `staticMs` stays
149
+ * generous for the same reason it does on standby: the upstream rate can
150
+ * legitimately hold still for a long time on the lower plans.
151
+ */
152
+ export declare const CURRENCY_API_ACTIVE_HEALTH: FXPriceSourceHealthThresholds;
153
+ export declare const DEFAULT_FX_PRICE_SOURCE_HEALTH: {
154
+ [key in FXPriceSource]: FXPriceSourceHealthThresholds;
155
+ };
156
+ export declare const MINUTES_IN_DAY = 1440;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MINUTES_IN_DAY = exports.DEFAULT_FX_PRICE_SOURCE_HEALTH = exports.CURRENCY_API_ACTIVE_HEALTH = exports.CURRENCY_API_ACTIVE_POLL_INTERVAL_MS = exports.CURRENCY_API_PROBE_INTERVAL_MS = exports.FXPriceSourceMode = void 0;
4
+ const fx_price_source_1 = require("./fx-price-source");
5
+ var FXPriceSourceMode;
6
+ (function (FXPriceSourceMode) {
7
+ /** Always use `primarySource`. Time windows are ignored. */
8
+ FXPriceSourceMode["Manual"] = "manual";
9
+ /** Use the matching time window if there is one, else `primarySource`. */
10
+ FXPriceSourceMode["Scheduled"] = "scheduled";
11
+ })(FXPriceSourceMode = exports.FXPriceSourceMode || (exports.FXPriceSourceMode = {}));
12
+ /**
13
+ * Health thresholds used when a fiat has no explicit override.
14
+ *
15
+ * TradingView is scraped roughly twice a second, so 30s without any
16
+ * report already means the publisher is gone; this matches the existing
17
+ * MXN staleness alert threshold in `exchange-rates-bot`. Its static
18
+ * window is deliberately generous because a scraped "last price" legally
19
+ * does sit still in thin conditions.
20
+ *
21
+ * LMAX is a live venue stream. It is allowed longer between ticks,
22
+ * because a quiet book genuinely does not print, but much less time
23
+ * frozen, because a top-of-book that has not moved in two minutes during
24
+ * a session means the stream is broken rather than the market is calm.
25
+ */
26
+ /**
27
+ * How often `market-data` asks currencyapi for a rate purely to prove the
28
+ * fallback still works.
29
+ *
30
+ * The fallback is otherwise only called once every live feed has gone
31
+ * quiet, which means that without a probe its health is unknown exactly
32
+ * until the moment it is needed. Each sweep is a single request covering
33
+ * every fiat, and the interval is deliberately long: this answers "would
34
+ * the parachute open", not "what is the price right now".
35
+ */
36
+ exports.CURRENCY_API_PROBE_INTERVAL_MS = 30 * 60 * 1000;
37
+ /**
38
+ * Static-price detection turned off, expressed as a threshold no real
39
+ * gap will cross rather than as a special case in the evaluator.
40
+ *
41
+ * Used for sources whose price legitimately holds still for long
42
+ * stretches, where a motionless number says nothing about whether the
43
+ * feed works. SET-ICAP publishes a closing rate that is fixed overnight,
44
+ * Bloomberg Línea's USDPEN quote sits still outside Lima market hours,
45
+ * and currencyapi's own refresh rate is as slow as daily on the lower
46
+ * account plans. For all three, reachability is the only real signal, and
47
+ * that is what `staleMs` already measures.
48
+ */
49
+ const STATIC_CHECK_DISABLED_MS = 26 * 60 * 60 * 1000;
50
+ /**
51
+ * How often currencyapi is polled while it is the selected source for at
52
+ * least one fiat.
53
+ *
54
+ * Standby cadence is far too slow to price against: it would leave the
55
+ * canonical rate up to half an hour old. Selecting currencyapi therefore
56
+ * switches it to a real polling loop. It is still not a stream, and the
57
+ * upstream refresh rate depends on the account plan, so this is the floor
58
+ * on how fresh a currencyapi-priced rate can be, not a promise of it.
59
+ */
60
+ exports.CURRENCY_API_ACTIVE_POLL_INTERVAL_MS = 60 * 1000;
61
+ /**
62
+ * Thresholds applied to currencyapi while it is the active source.
63
+ *
64
+ * The standby defaults are sized for a half-hourly probe and would let a
65
+ * dead feed price trades for over an hour before anyone noticed, which is
66
+ * unacceptable once it is the thing quoting customers. `staticMs` stays
67
+ * generous for the same reason it does on standby: the upstream rate can
68
+ * legitimately hold still for a long time on the lower plans.
69
+ */
70
+ exports.CURRENCY_API_ACTIVE_HEALTH = {
71
+ staleMs: exports.CURRENCY_API_ACTIVE_POLL_INTERVAL_MS * 3,
72
+ staticMs: STATIC_CHECK_DISABLED_MS,
73
+ };
74
+ exports.DEFAULT_FX_PRICE_SOURCE_HEALTH = {
75
+ [fx_price_source_1.FXPriceSource.TradingView]: {
76
+ staleMs: 30 * 1000,
77
+ staticMs: 10 * 60 * 1000,
78
+ },
79
+ [fx_price_source_1.FXPriceSource.Lmax]: {
80
+ staleMs: 60 * 1000,
81
+ staticMs: 2 * 60 * 1000,
82
+ },
83
+ /**
84
+ * Bloomberg Línea is scraped for USDPEN once a minute, so four minutes
85
+ * of silence means the scrape is broken rather than merely slow. This
86
+ * matches the window after which the currencyapi fallback would step in
87
+ * for PEN anyway, so the alert and the handover line up.
88
+ */
89
+ [fx_price_source_1.FXPriceSource.Bloomberg]: {
90
+ staleMs: 4 * 60 * 1000,
91
+ staticMs: STATIC_CHECK_DISABLED_MS,
92
+ },
93
+ /**
94
+ * SET-ICAP is polled every 15 seconds; two minutes of nothing is a real
95
+ * outage rather than a slow response.
96
+ */
97
+ [fx_price_source_1.FXPriceSource.SetIcap]: {
98
+ staleMs: 2 * 60 * 1000,
99
+ staticMs: STATIC_CHECK_DISABLED_MS,
100
+ },
101
+ [fx_price_source_1.FXPriceSource.CurrencyAPI]: {
102
+ staleMs: exports.CURRENCY_API_PROBE_INTERVAL_MS * 2.5,
103
+ staticMs: STATIC_CHECK_DISABLED_MS,
104
+ },
105
+ };
106
+ exports.MINUTES_IN_DAY = 1440;
@@ -0,0 +1,119 @@
1
+ import { Fiat } from "./fiat";
2
+ import { TVFXDataProvider } from "./TV-FX-data-provider";
3
+ /**
4
+ * An upstream that can produce a spot FX mid for a fiat.
5
+ *
6
+ * Distinct from `FXProvider`, which is where a trade is *executed*.
7
+ * A source only answers "where did this price come from". LMAX appears
8
+ * in both enums because we both price off it and trade on it.
9
+ *
10
+ * `CurrencyAPI` is the only source available for every fiat, because it
11
+ * is both selectable and the automatic last resort. The rest are tied to
12
+ * particular currencies by what actually publishes them: TradingView and
13
+ * LMAX quote USDMXN, Bloomberg Línea is scraped for USDPEN, and SET-ICAP
14
+ * is the Colombian interbank reference for USDCOP.
15
+ */
16
+ export declare enum FXPriceSource {
17
+ TradingView = "tradingview",
18
+ Lmax = "lmax",
19
+ Bloomberg = "bloomberg",
20
+ SetIcap = "seticap",
21
+ CurrencyAPI = "currencyapi"
22
+ }
23
+ /**
24
+ * Sources an operator may configure for a given fiat, in display order.
25
+ *
26
+ * Per-fiat rather than one global list because the sources are not
27
+ * interchangeable: there is no LMAX book for COP and no SET-ICAP feed for
28
+ * MXN, so a single list would offer choices that cannot be honoured. The
29
+ * arbiter, the settings validator and the dashboard all read this, which
30
+ * is what keeps an impossible combination from being selectable in the
31
+ * first place rather than failing later.
32
+ *
33
+ * `CurrencyAPI` is last for every fiat, and is selectable as well as
34
+ * being the automatic fallback, so a reference rate can be chosen
35
+ * deliberately rather than only being reached once the real feed is gone.
36
+ * Choosing it is not free: it is a periodic REST poll rather than a live
37
+ * feed, so `market-data` polls it far harder while it is the active
38
+ * source and tightens its health thresholds to match. See
39
+ * `CURRENCY_API_ACTIVE_POLL_INTERVAL_MS`.
40
+ */
41
+ export declare const SELECTABLE_FX_PRICE_SOURCES_BY_FIAT: {
42
+ [key in Fiat]?: FXPriceSource[];
43
+ };
44
+ /** Fiats whose price feed is configurable at all. */
45
+ export declare const FIATS_WITH_SELECTABLE_SOURCES: Fiat[];
46
+ export declare const getSelectableFXPriceSources: (fiat: Fiat) => FXPriceSource[];
47
+ export declare const isSelectableFXPriceSource: (fiat: Fiat, source: unknown) => source is FXPriceSource;
48
+ /**
49
+ * One source's latest reading for one fiat, as reported to the arbiter.
50
+ *
51
+ * `value` is always units of fiat per 1 USD (the `conversionRatesFromUSD`
52
+ * convention), so a source that natively quotes the inverse converts
53
+ * before reporting. Reporting both directions was considered and
54
+ * rejected: two numbers that must agree is a bug waiting to happen, and
55
+ * the arbiter already derives the inverse when it writes.
56
+ *
57
+ * `bid` / `ask` are optional because only a real venue has them. They
58
+ * are recorded for operator comparison and are not used in pricing
59
+ * today, since the canonical rate carries a single value.
60
+ *
61
+ * `tvProvider` is only meaningful when the reporting source is
62
+ * TradingView, and names which TradingView exchange the scrape came
63
+ * from. Which exchange to scrape stays a concern of the scraper, so this
64
+ * is carried as provenance rather than as something the arbiter decides.
65
+ */
66
+ export interface FXPriceObservation {
67
+ value: number;
68
+ lastUpdated: Date;
69
+ /**
70
+ * When `value` last actually moved. The arbiter uses this, not
71
+ * `lastUpdated`, to decide a feed has gone static: a scraper that is
72
+ * happily returning the same stale number every 500ms is still
73
+ * "updating" but is no longer telling us anything.
74
+ */
75
+ lastPriceChangedAt?: Date;
76
+ bid?: number;
77
+ ask?: number;
78
+ tvProvider?: TVFXDataProvider;
79
+ }
80
+ /**
81
+ * Why a given source ended up active. Recorded on the canonical rate and
82
+ * surfaced in the dashboard so an operator can tell a deliberate
83
+ * schedule switch apart from a silent failover.
84
+ */
85
+ export declare enum FXPriceSourceReason {
86
+ Override = "override",
87
+ Schedule = "schedule",
88
+ Primary = "primary",
89
+ Failover = "failover"
90
+ }
91
+ /**
92
+ * The arbiter's answer to a reported observation.
93
+ *
94
+ * `accepted` false is a normal, expected outcome, not an error: it means
95
+ * the report was recorded but another source is currently active. A
96
+ * producer publishing twice a second cannot treat losing as a failure,
97
+ * so this is a 200 with `accepted: false` rather than a 4xx, and the
98
+ * distinction matters for what gets alerted on.
99
+ */
100
+ export interface FXPriceObservationResult {
101
+ accepted: boolean;
102
+ activeSource: FXPriceSource;
103
+ reason: FXPriceSourceReason;
104
+ /** The canonical fiat-per-USD rate after this report was handled. */
105
+ value?: number;
106
+ /** Set when the report was rejected as implausible rather than merely losing. */
107
+ rejectedReason?: string;
108
+ }
109
+ /**
110
+ * Why a source is considered unhealthy. `Static` is the failure mode
111
+ * that motivated per-source pricing in the first place: the TradingView
112
+ * USDMXN scrape keeps responding through the Mexico City afternoon but
113
+ * stops moving.
114
+ */
115
+ export declare enum FXPriceSourceHealthIssue {
116
+ NeverReported = "neverReported",
117
+ Stale = "stale",
118
+ Static = "static"
119
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FXPriceSourceHealthIssue = exports.FXPriceSourceReason = exports.isSelectableFXPriceSource = exports.getSelectableFXPriceSources = exports.FIATS_WITH_SELECTABLE_SOURCES = exports.SELECTABLE_FX_PRICE_SOURCES_BY_FIAT = exports.FXPriceSource = void 0;
4
+ const fiat_1 = require("./fiat");
5
+ /**
6
+ * An upstream that can produce a spot FX mid for a fiat.
7
+ *
8
+ * Distinct from `FXProvider`, which is where a trade is *executed*.
9
+ * A source only answers "where did this price come from". LMAX appears
10
+ * in both enums because we both price off it and trade on it.
11
+ *
12
+ * `CurrencyAPI` is the only source available for every fiat, because it
13
+ * is both selectable and the automatic last resort. The rest are tied to
14
+ * particular currencies by what actually publishes them: TradingView and
15
+ * LMAX quote USDMXN, Bloomberg Línea is scraped for USDPEN, and SET-ICAP
16
+ * is the Colombian interbank reference for USDCOP.
17
+ */
18
+ var FXPriceSource;
19
+ (function (FXPriceSource) {
20
+ FXPriceSource["TradingView"] = "tradingview";
21
+ FXPriceSource["Lmax"] = "lmax";
22
+ FXPriceSource["Bloomberg"] = "bloomberg";
23
+ FXPriceSource["SetIcap"] = "seticap";
24
+ FXPriceSource["CurrencyAPI"] = "currencyapi";
25
+ })(FXPriceSource = exports.FXPriceSource || (exports.FXPriceSource = {}));
26
+ /**
27
+ * Sources an operator may configure for a given fiat, in display order.
28
+ *
29
+ * Per-fiat rather than one global list because the sources are not
30
+ * interchangeable: there is no LMAX book for COP and no SET-ICAP feed for
31
+ * MXN, so a single list would offer choices that cannot be honoured. The
32
+ * arbiter, the settings validator and the dashboard all read this, which
33
+ * is what keeps an impossible combination from being selectable in the
34
+ * first place rather than failing later.
35
+ *
36
+ * `CurrencyAPI` is last for every fiat, and is selectable as well as
37
+ * being the automatic fallback, so a reference rate can be chosen
38
+ * deliberately rather than only being reached once the real feed is gone.
39
+ * Choosing it is not free: it is a periodic REST poll rather than a live
40
+ * feed, so `market-data` polls it far harder while it is the active
41
+ * source and tightens its health thresholds to match. See
42
+ * `CURRENCY_API_ACTIVE_POLL_INTERVAL_MS`.
43
+ */
44
+ exports.SELECTABLE_FX_PRICE_SOURCES_BY_FIAT = {
45
+ [fiat_1.Fiat.MXN]: [
46
+ FXPriceSource.TradingView,
47
+ FXPriceSource.Lmax,
48
+ FXPriceSource.CurrencyAPI,
49
+ ],
50
+ [fiat_1.Fiat.PEN]: [FXPriceSource.Bloomberg, FXPriceSource.CurrencyAPI],
51
+ [fiat_1.Fiat.COP]: [FXPriceSource.SetIcap, FXPriceSource.CurrencyAPI],
52
+ };
53
+ /** Fiats whose price feed is configurable at all. */
54
+ exports.FIATS_WITH_SELECTABLE_SOURCES = Object.keys(exports.SELECTABLE_FX_PRICE_SOURCES_BY_FIAT);
55
+ const getSelectableFXPriceSources = (fiat) => { var _a; return (_a = exports.SELECTABLE_FX_PRICE_SOURCES_BY_FIAT[fiat]) !== null && _a !== void 0 ? _a : []; };
56
+ exports.getSelectableFXPriceSources = getSelectableFXPriceSources;
57
+ const isSelectableFXPriceSource = (fiat, source) => (0, exports.getSelectableFXPriceSources)(fiat).includes(source);
58
+ exports.isSelectableFXPriceSource = isSelectableFXPriceSource;
59
+ /**
60
+ * Why a given source ended up active. Recorded on the canonical rate and
61
+ * surfaced in the dashboard so an operator can tell a deliberate
62
+ * schedule switch apart from a silent failover.
63
+ */
64
+ var FXPriceSourceReason;
65
+ (function (FXPriceSourceReason) {
66
+ FXPriceSourceReason["Override"] = "override";
67
+ FXPriceSourceReason["Schedule"] = "schedule";
68
+ FXPriceSourceReason["Primary"] = "primary";
69
+ FXPriceSourceReason["Failover"] = "failover";
70
+ })(FXPriceSourceReason = exports.FXPriceSourceReason || (exports.FXPriceSourceReason = {}));
71
+ /**
72
+ * Why a source is considered unhealthy. `Static` is the failure mode
73
+ * that motivated per-source pricing in the first place: the TradingView
74
+ * USDMXN scrape keeps responding through the Mexico City afternoon but
75
+ * stops moving.
76
+ */
77
+ var FXPriceSourceHealthIssue;
78
+ (function (FXPriceSourceHealthIssue) {
79
+ FXPriceSourceHealthIssue["NeverReported"] = "neverReported";
80
+ FXPriceSourceHealthIssue["Stale"] = "stale";
81
+ FXPriceSourceHealthIssue["Static"] = "static";
82
+ })(FXPriceSourceHealthIssue = exports.FXPriceSourceHealthIssue || (exports.FXPriceSourceHealthIssue = {}));
@@ -6,5 +6,6 @@ export declare enum FXProvider {
6
6
  Bitso = "bitso",
7
7
  Binance = "binance",
8
8
  StoneX = "stonex",
9
+ Lmax = "lmax",
9
10
  Other = "other"
10
11
  }
@@ -10,5 +10,6 @@ var FXProvider;
10
10
  FXProvider["Bitso"] = "bitso";
11
11
  FXProvider["Binance"] = "binance";
12
12
  FXProvider["StoneX"] = "stonex";
13
+ FXProvider["Lmax"] = "lmax";
13
14
  FXProvider["Other"] = "other";
14
15
  })(FXProvider = exports.FXProvider || (exports.FXProvider = {}));
@@ -0,0 +1,7 @@
1
+ export interface LmaxBalances {
2
+ accountId?: string;
3
+ usd: number;
4
+ mxn: number;
5
+ live: boolean;
6
+ timestamp?: Date;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ export declare enum LmaxFXTradeStatus {
2
+ Submitting = "submitting",
3
+ PendingNew = "pendingNew",
4
+ New = "new",
5
+ PartiallyFilled = "partiallyFilled",
6
+ Filled = "filled",
7
+ Cancelled = "cancelled",
8
+ Rejected = "rejected",
9
+ Expired = "expired",
10
+ Failed = "failed"
11
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LmaxFXTradeStatus = void 0;
4
+ var LmaxFXTradeStatus;
5
+ (function (LmaxFXTradeStatus) {
6
+ LmaxFXTradeStatus["Submitting"] = "submitting";
7
+ LmaxFXTradeStatus["PendingNew"] = "pendingNew";
8
+ LmaxFXTradeStatus["New"] = "new";
9
+ LmaxFXTradeStatus["PartiallyFilled"] = "partiallyFilled";
10
+ LmaxFXTradeStatus["Filled"] = "filled";
11
+ LmaxFXTradeStatus["Cancelled"] = "cancelled";
12
+ LmaxFXTradeStatus["Rejected"] = "rejected";
13
+ LmaxFXTradeStatus["Expired"] = "expired";
14
+ LmaxFXTradeStatus["Failed"] = "failed";
15
+ })(LmaxFXTradeStatus = exports.LmaxFXTradeStatus || (exports.LmaxFXTradeStatus = {}));
@@ -0,0 +1,35 @@
1
+ import { LmaxFXTradeStatus } from "./lmax-fx-trade-status";
2
+ import { LmaxOrderType } from "./lmax-order-type";
3
+ import { LmaxSide } from "./lmax-side";
4
+ import { LmaxTimeInForce } from "./lmax-time-in-force";
5
+ import { Side } from "./side";
6
+ export interface LmaxFXTrade {
7
+ id: string;
8
+ createdAt: Date;
9
+ status: LmaxFXTradeStatus;
10
+ side: Side;
11
+ lmaxSide: LmaxSide;
12
+ instrumentId: string;
13
+ symbol: string;
14
+ originCurrency: string;
15
+ destinationCurrency: string;
16
+ quantity: number;
17
+ orderType: LmaxOrderType;
18
+ timeInForce: LmaxTimeInForce;
19
+ price?: number;
20
+ stopPrice?: number;
21
+ instructionId: string;
22
+ cancelInstructionId?: string;
23
+ orderId?: string;
24
+ tradeKey?: string;
25
+ matchedQuantity?: number;
26
+ unfilledQuantity?: number;
27
+ cumulativeMatchedQuantity?: number;
28
+ cancelledQuantity?: number;
29
+ matchedCost?: number;
30
+ commission?: number;
31
+ avgPrice?: number;
32
+ text?: string;
33
+ submitted?: boolean;
34
+ submitAttempts?: number;
35
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,20 @@
1
+ export interface LmaxOrderBookLevel {
2
+ price: number;
3
+ quantity: number;
4
+ }
5
+ export interface LmaxOrderBook {
6
+ instrumentId: string;
7
+ symbol: string;
8
+ streaming: boolean;
9
+ status?: string;
10
+ stale?: boolean;
11
+ bid?: LmaxOrderBookLevel;
12
+ ask?: LmaxOrderBookLevel;
13
+ timestamp?: Date;
14
+ baseCurrency?: string;
15
+ quoteCurrency?: string;
16
+ minimumQuantity?: number;
17
+ quantityIncrement?: number;
18
+ priceIncrement?: number;
19
+ maxOrderUsd?: number;
20
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ export declare enum LmaxOrderType {
2
+ Market = "MARKET",
3
+ Limit = "LIMIT",
4
+ Stop = "STOP",
5
+ StopLimit = "STOP_LIMIT"
6
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LmaxOrderType = void 0;
4
+ // LMAX Account API order-type strings.
5
+ var LmaxOrderType;
6
+ (function (LmaxOrderType) {
7
+ LmaxOrderType["Market"] = "MARKET";
8
+ LmaxOrderType["Limit"] = "LIMIT";
9
+ LmaxOrderType["Stop"] = "STOP";
10
+ LmaxOrderType["StopLimit"] = "STOP_LIMIT";
11
+ })(LmaxOrderType = exports.LmaxOrderType || (exports.LmaxOrderType = {}));
@@ -0,0 +1,4 @@
1
+ export declare enum LmaxSide {
2
+ Bid = "BID",
3
+ Ask = "ASK"
4
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LmaxSide = void 0;
4
+ // LMAX Account API side strings (buy/sell the instrument base currency).
5
+ var LmaxSide;
6
+ (function (LmaxSide) {
7
+ LmaxSide["Bid"] = "BID";
8
+ LmaxSide["Ask"] = "ASK";
9
+ })(LmaxSide = exports.LmaxSide || (exports.LmaxSide = {}));
@@ -0,0 +1,6 @@
1
+ export declare enum LmaxTimeInForce {
2
+ FillOrKill = "FILL_OR_KILL",
3
+ ImmediateOrCancel = "IMMEDIATE_OR_CANCEL",
4
+ GoodForDay = "GOOD_FOR_DAY",
5
+ GoodTilCancelled = "GOOD_TIL_CANCELLED"
6
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LmaxTimeInForce = void 0;
4
+ // LMAX Account API time-in-force strings.
5
+ var LmaxTimeInForce;
6
+ (function (LmaxTimeInForce) {
7
+ LmaxTimeInForce["FillOrKill"] = "FILL_OR_KILL";
8
+ LmaxTimeInForce["ImmediateOrCancel"] = "IMMEDIATE_OR_CANCEL";
9
+ LmaxTimeInForce["GoodForDay"] = "GOOD_FOR_DAY";
10
+ LmaxTimeInForce["GoodTilCancelled"] = "GOOD_TIL_CANCELLED";
11
+ })(LmaxTimeInForce = exports.LmaxTimeInForce || (exports.LmaxTimeInForce = {}));
@@ -1,24 +1,35 @@
1
+ import { FXPriceSource } from "./fx-price-source";
1
2
  import { TVFXDataProvider } from "./TV-FX-data-provider";
3
+ /**
4
+ * The canonical spot rate for one fiat, in one direction.
5
+ *
6
+ * `provider` predates multi-source pricing and names a TradingView
7
+ * exchange. It stays populated when the winning source is TradingView
8
+ * and is left unset otherwise, so existing readers that switch on
9
+ * `TVFXDataProvider` keep seeing either a value they understand or the
10
+ * `undefined` they already tolerate.
11
+ *
12
+ * `bot` is legacy and is superseded by `source`. It was a boolean
13
+ * standing in for "came from the scraper rather than the currencyapi
14
+ * fallback", which stopped being expressible as a boolean once there was
15
+ * more than one real feed. It is still written for the benefit of
16
+ * existing readers; new code should branch on `source`.
17
+ */
18
+ export interface ConversionRate {
19
+ value: number;
20
+ source?: FXPriceSource;
21
+ provider?: TVFXDataProvider;
22
+ bot?: boolean;
23
+ frozen?: boolean;
24
+ lastUpdated: Date;
25
+ lastPriceChangedAt?: Date;
26
+ }
2
27
  export interface MarketData {
3
28
  conversionRatesToUSD: {
4
- [key: string]: {
5
- value: number;
6
- provider?: TVFXDataProvider;
7
- bot?: boolean;
8
- frozen?: boolean;
9
- lastUpdated: Date;
10
- lastPriceChangedAt?: Date;
11
- };
29
+ [key: string]: ConversionRate;
12
30
  };
13
31
  conversionRatesFromUSD: {
14
- [key: string]: {
15
- value: number;
16
- provider?: TVFXDataProvider;
17
- bot?: boolean;
18
- frozen?: boolean;
19
- lastUpdated: Date;
20
- lastPriceChangedAt?: Date;
21
- };
32
+ [key: string]: ConversionRate;
22
33
  };
23
34
  assetPricesInUSD: {
24
35
  [key: string]: {
@@ -1,5 +1,6 @@
1
1
  import { Country } from "./country";
2
2
  import { Fiat } from "./fiat";
3
+ import { FXPriceSourcePolicies } from "./fx-price-source-policy";
3
4
  import { FXProvider } from "./fx-provider";
4
5
  import { FXTradingPolicies } from "./fx-trading-policy";
5
6
  import { Side } from "./side";
@@ -56,6 +57,7 @@ export interface RioSettingsUpdate {
56
57
  TVFXDataProvider?: {
57
58
  [key in Fiat]: TVFXDataProvider;
58
59
  };
60
+ fxPriceSources?: FXPriceSourcePolicies;
59
61
  defaultTwoWaySettlementDateOffsetFee?: {
60
62
  [key in Country]?: {
61
63
  [key in Side]?: {
@@ -2,6 +2,7 @@ import { Country } from "./country";
2
2
  import { Crypto } from "./crypto";
3
3
  import { DeferredPaymentType } from "./deferred-payment-type";
4
4
  import { Fiat } from "./fiat";
5
+ import { FXPriceSourcePolicies } from "./fx-price-source-policy";
5
6
  import { FXProvider } from "./fx-provider";
6
7
  import { FXTradingPolicies } from "./fx-trading-policy";
7
8
  import { ProcessorRoutingConfig } from "./processor-routing";
@@ -74,6 +75,19 @@ export interface RioSettings {
74
75
  TVFXDataProvider: {
75
76
  [key in Fiat]?: TVFXDataProvider;
76
77
  };
78
+ /**
79
+ * Which upstream feed the canonical spot rate for each fiat is taken
80
+ * from, and when. Resolved by `market-data`, which is the only writer
81
+ * of the canonical rate.
82
+ *
83
+ * `TVFXDataProvider` above is the narrower question of *which
84
+ * TradingView exchange* to scrape, and only has any effect while the
85
+ * resolved source for that fiat is TradingView.
86
+ *
87
+ * Absent for a fiat means TradingView with no schedule, which is how
88
+ * pricing behaved before this setting existed.
89
+ */
90
+ fxPriceSources?: FXPriceSourcePolicies;
77
91
  defaultTwoWaySettlementDateOffsetFee?: {
78
92
  [key in Country]?: {
79
93
  [key in Side]?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riocrypto/common",
3
- "version": "1.0.2715",
3
+ "version": "1.0.2716",
4
4
  "description": "",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",