@riocrypto/common 1.0.2715 → 1.0.2717
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/build/events/lmax-trade-updated-event.d.ts +7 -0
- package/build/events/lmax-trade-updated-event.js +2 -0
- package/build/events/rio-settings-updated-event.d.ts +2 -0
- package/build/events/subjects.d.ts +1 -0
- package/build/events/subjects.js +1 -0
- package/build/helpers/get-minute-of-day-in-timezone.d.ts +13 -0
- package/build/helpers/get-minute-of-day-in-timezone.js +37 -0
- package/build/helpers/resolve-fx-price-source.d.ts +65 -0
- package/build/helpers/resolve-fx-price-source.js +163 -0
- package/build/index.d.ts +12 -0
- package/build/index.js +12 -0
- package/build/types/dashboard-table-type.d.ts +1 -0
- package/build/types/dashboard-table-type.js +1 -0
- package/build/types/external-trade.d.ts +22 -0
- package/build/types/external-trading-provider.d.ts +1 -0
- package/build/types/external-trading-provider.js +1 -0
- package/build/types/fx-price-source-policy.d.ts +156 -0
- package/build/types/fx-price-source-policy.js +106 -0
- package/build/types/fx-price-source.d.ts +119 -0
- package/build/types/fx-price-source.js +82 -0
- package/build/types/fx-provider.d.ts +1 -0
- package/build/types/fx-provider.js +1 -0
- package/build/types/lmax-balances.d.ts +7 -0
- package/build/types/lmax-balances.js +2 -0
- package/build/types/lmax-fx-trade-status.d.ts +11 -0
- package/build/types/lmax-fx-trade-status.js +15 -0
- package/build/types/lmax-fx-trade.d.ts +35 -0
- package/build/types/lmax-fx-trade.js +2 -0
- package/build/types/lmax-order-book.d.ts +20 -0
- package/build/types/lmax-order-book.js +2 -0
- package/build/types/lmax-order-type.d.ts +6 -0
- package/build/types/lmax-order-type.js +11 -0
- package/build/types/lmax-side.d.ts +4 -0
- package/build/types/lmax-side.js +9 -0
- package/build/types/lmax-time-in-force.d.ts +6 -0
- package/build/types/lmax-time-in-force.js +11 -0
- package/build/types/market-data.d.ts +27 -16
- package/build/types/rio-settings-update.d.ts +2 -0
- package/build/types/rio-settings.d.ts +14 -0
- package/package.json +1 -1
|
@@ -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",
|
package/build/events/subjects.js
CHANGED
|
@@ -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);
|
|
@@ -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;
|
|
@@ -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 = {}));
|
|
@@ -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,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,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,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,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]?: {
|