@riocrypto/common-server 1.0.2909 → 1.0.2910
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/clients/cluster-client.d.ts +25 -1
- package/build/clients/cluster-client.js +62 -0
- package/build/clients/currency-api-client.d.ts +12 -0
- package/build/clients/currency-api-client.js +29 -0
- package/build/helpers/get-fx-trading-policy.d.ts +12 -0
- package/build/helpers/get-fx-trading-policy.js +29 -8
- package/build/index.d.ts +2 -0
- package/build/index.js +2 -0
- package/build/models/external-trade.d.ts +39 -1
- package/build/models/fx-price-observation.d.ts +46 -0
- package/build/models/fx-price-observation.js +56 -0
- package/build/models/lmax-fx-trade.d.ts +66 -0
- package/build/models/lmax-fx-trade.js +126 -0
- package/build/models/market-data.d.ts +5 -33
- package/build/models/rio-settings.d.ts +3 -1
- package/build/models/rio-settings.js +3 -0
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
-
import { Quote, Fiat, Crypto, BitsoBankAccount, Side, Country, Order, CryptoAddress, BankAccount, AuthRole, Auth, ImportOrderData, TreasuryProvider, FXProvider, EmarketsFXTrade, ExternalTradingAlgorithm, ExternalTrade, ExternalTradeType, ExternalTradingProvider, ExternalTradingAlgorithmType, STPMXNWithdrawal, AuthPermission, DeferredPaymentType, TwoWaySettlementType, OrderType, EmarketsSettlementType, EmarketsOrderType, BulkBankPayout, BulkCryptoPayout, BulkBankPayment, BulkCryptoPayment, OrderStatus, TWAPSession, TransnetworkFXTrade, TransnetworkSettlementType, TransnetworkOrderType, AuthMethod, StonexFXTrade, CancelExternalTradeReason } from "@riocrypto/common";
|
|
3
|
+
import { Quote, Fiat, Crypto, BitsoBankAccount, Side, Country, Order, CryptoAddress, BankAccount, AuthRole, Auth, ImportOrderData, TreasuryProvider, FXProvider, EmarketsFXTrade, ExternalTradingAlgorithm, ExternalTrade, ExternalTradeType, ExternalTradingProvider, ExternalTradingAlgorithmType, STPMXNWithdrawal, AuthPermission, DeferredPaymentType, TwoWaySettlementType, OrderType, EmarketsSettlementType, EmarketsOrderType, BulkBankPayout, BulkCryptoPayout, BulkBankPayment, BulkCryptoPayment, OrderStatus, TWAPSession, TransnetworkFXTrade, TransnetworkSettlementType, TransnetworkOrderType, AuthMethod, StonexFXTrade, LmaxFXTrade, CancelExternalTradeReason, FXPriceSource, FXPriceObservationResult, TVFXDataProvider } from "@riocrypto/common";
|
|
4
4
|
import { STPMXNWithdrawalDoc } from "../models/STP-mxn-withdrawal";
|
|
5
5
|
import { FintocMXNWithdrawalDoc } from "../models/fintoc-mxn-withdrawal";
|
|
6
6
|
export interface FintocTransfer {
|
|
@@ -343,6 +343,30 @@ declare class ClusterClient {
|
|
|
343
343
|
tradeKey: string;
|
|
344
344
|
}): Promise<StonexFXTrade>;
|
|
345
345
|
getStonexTrade(tradeId: string): Promise<StonexFXTrade>;
|
|
346
|
+
placeLmaxOrder(params: {
|
|
347
|
+
originCurrency: string;
|
|
348
|
+
destinationCurrency: string;
|
|
349
|
+
side: string;
|
|
350
|
+
orderType: string;
|
|
351
|
+
timeInForce?: string;
|
|
352
|
+
requestedOriginAmount?: number;
|
|
353
|
+
requestedDestinationAmount?: number;
|
|
354
|
+
price?: number;
|
|
355
|
+
stopPrice?: number;
|
|
356
|
+
tradeKey: string;
|
|
357
|
+
}): Promise<LmaxFXTrade>;
|
|
358
|
+
getLmaxTrade(tradeId: string): Promise<LmaxFXTrade>;
|
|
359
|
+
getLmaxTradeByKey(tradeKey: string): Promise<LmaxFXTrade | null>;
|
|
360
|
+
cancelLmaxTrade(tradeId: string): Promise<LmaxFXTrade>;
|
|
361
|
+
reportFXPriceObservation(params: {
|
|
362
|
+
fiat: Fiat;
|
|
363
|
+
source: FXPriceSource;
|
|
364
|
+
value: number;
|
|
365
|
+
bid?: number;
|
|
366
|
+
ask?: number;
|
|
367
|
+
tvProvider?: TVFXDataProvider;
|
|
368
|
+
bypassBasisPointsCheck?: boolean;
|
|
369
|
+
}): Promise<FXPriceObservationResult>;
|
|
346
370
|
getStonexTradeByKey(tradeKey: string): Promise<StonexFXTrade | null>;
|
|
347
371
|
getStonexTrades(offset: number, limit: number): Promise<{
|
|
348
372
|
data: StonexFXTrade[];
|
|
@@ -955,6 +955,68 @@ class ClusterClient {
|
|
|
955
955
|
return response.data;
|
|
956
956
|
});
|
|
957
957
|
}
|
|
958
|
+
placeLmaxOrder(params) {
|
|
959
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
960
|
+
const { tradeKey } = params;
|
|
961
|
+
const response = yield this.axios.post(`${this.baseUrl}/api/lmax/place-order`, params, {
|
|
962
|
+
headers: {
|
|
963
|
+
"x-cluster-api-key": this.clusterApiKey,
|
|
964
|
+
"X-Trade-Key": tradeKey,
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
return response.data;
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
getLmaxTrade(tradeId) {
|
|
971
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
972
|
+
const response = yield this.axios.get(`${this.baseUrl}/api/lmax/trades/${tradeId}`, { headers: { "x-cluster-api-key": this.clusterApiKey } });
|
|
973
|
+
return response.data;
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
getLmaxTradeByKey(tradeKey) {
|
|
977
|
+
var _a;
|
|
978
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
979
|
+
try {
|
|
980
|
+
const response = yield this.axios.get(`${this.baseUrl}/api/lmax/trades/by-trade-key/${encodeURIComponent(tradeKey)}`, { headers: { "x-cluster-api-key": this.clusterApiKey } });
|
|
981
|
+
return response.data;
|
|
982
|
+
}
|
|
983
|
+
catch (err) {
|
|
984
|
+
if (typeof err === "object" &&
|
|
985
|
+
err !== null &&
|
|
986
|
+
"response" in err &&
|
|
987
|
+
((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
throw err;
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
cancelLmaxTrade(tradeId) {
|
|
995
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
996
|
+
const response = yield this.axios.post(`${this.baseUrl}/api/lmax/cancel-trade/${tradeId}`, {}, { headers: { "x-cluster-api-key": this.clusterApiKey } });
|
|
997
|
+
return response.data;
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
// Report one source's latest spot reading to market-data, which decides
|
|
1001
|
+
// whether it becomes the canonical rate.
|
|
1002
|
+
//
|
|
1003
|
+
// `value` is always units of fiat per 1 USD, whichever way round the
|
|
1004
|
+
// source natively quotes, because market-data derives the inverse
|
|
1005
|
+
// itself and two numbers that have to agree is a bug waiting to
|
|
1006
|
+
// happen.
|
|
1007
|
+
//
|
|
1008
|
+
// Callers publish on every tick, so this is called several times a
|
|
1009
|
+
// second and every caller must attach a catch. An unhandled rejection
|
|
1010
|
+
// here would take the publisher down on the first blip, and a blip in
|
|
1011
|
+
// one feed is precisely when the other one needs to still be running.
|
|
1012
|
+
// Losing arbitration is not an error: a 200 with `accepted: false`
|
|
1013
|
+
// means the report was recorded while another source is active.
|
|
1014
|
+
reportFXPriceObservation(params) {
|
|
1015
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1016
|
+
const response = yield this.axios.post(`${this.baseUrl}/api/market/data/fx-observation`, params, { headers: { "x-cluster-api-key": this.clusterApiKey } });
|
|
1017
|
+
return response.data;
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
958
1020
|
// Reconcile helper: look up a Stonex trade by caller-supplied trade key.
|
|
959
1021
|
// Returns null when the key has no matching trade (the server either
|
|
960
1022
|
// never received the request or failed before persisting). External-trading
|
|
@@ -4,6 +4,18 @@ declare class CurrencyAPIClient {
|
|
|
4
4
|
private axiosClient;
|
|
5
5
|
constructor(apiKey: string);
|
|
6
6
|
convert(from: Fiat, to: Fiat): Promise<number>;
|
|
7
|
+
/**
|
|
8
|
+
* Fetch several rates against one base in a single request.
|
|
9
|
+
*
|
|
10
|
+
* `convert` issues one request per pair, so checking three currencies
|
|
11
|
+
* through it costs three calls against a metered quota. The `currencies`
|
|
12
|
+
* parameter takes a comma-separated list, so the same answer costs one.
|
|
13
|
+
*
|
|
14
|
+
* Intentionally not cached: the only caller is a health probe, and a
|
|
15
|
+
* cached hit would report the fallback as reachable when the last real
|
|
16
|
+
* request may have failed.
|
|
17
|
+
*/
|
|
18
|
+
getLatestRates(baseCurrency: Fiat, currencies: Fiat[]): Promise<Partial<Record<Fiat, number>>>;
|
|
7
19
|
getHistoricalExchangeRates(from: Fiat, to: Fiat, start: string, end: string): Promise<any>;
|
|
8
20
|
}
|
|
9
21
|
export declare const buildCurrencyAPIClient: () => Promise<CurrencyAPIClient>;
|
|
@@ -53,6 +53,35 @@ class CurrencyAPIClient {
|
|
|
53
53
|
return exchangeRate;
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Fetch several rates against one base in a single request.
|
|
58
|
+
*
|
|
59
|
+
* `convert` issues one request per pair, so checking three currencies
|
|
60
|
+
* through it costs three calls against a metered quota. The `currencies`
|
|
61
|
+
* parameter takes a comma-separated list, so the same answer costs one.
|
|
62
|
+
*
|
|
63
|
+
* Intentionally not cached: the only caller is a health probe, and a
|
|
64
|
+
* cached hit would report the fallback as reachable when the last real
|
|
65
|
+
* request may have failed.
|
|
66
|
+
*/
|
|
67
|
+
getLatestRates(baseCurrency, currencies) {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
const response = yield this.axiosClient.get(`https://api.currencyapi.com/v3/latest?apikey=${this.apiKey}&base_currency=${baseCurrency}¤cies=${currencies.join(",")}`);
|
|
71
|
+
const data = (_a = response.data) === null || _a === void 0 ? void 0 : _a.data;
|
|
72
|
+
if (!data) {
|
|
73
|
+
throw new common_1.GenericInternalError(`Unable to get latest exchange rates from currencyAPI for ${baseCurrency}`);
|
|
74
|
+
}
|
|
75
|
+
const rates = {};
|
|
76
|
+
for (const currency of currencies) {
|
|
77
|
+
const value = (_b = data[currency]) === null || _b === void 0 ? void 0 : _b.value;
|
|
78
|
+
if (typeof value === "number" && value > 0) {
|
|
79
|
+
rates[currency] = value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return rates;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
56
85
|
getHistoricalExchangeRates(from, to, start, end) {
|
|
57
86
|
return __awaiter(this, void 0, void 0, function* () {
|
|
58
87
|
try {
|
|
@@ -5,6 +5,18 @@ import { Fiat, FXTradingSidePolicy, RioSettings, ResolvedFXPlacement, Side } fro
|
|
|
5
5
|
* provider, not a per-policy admin setting.
|
|
6
6
|
*/
|
|
7
7
|
export declare const TRANSNETWORK_MIN_MXN_AMOUNT = 1000000;
|
|
8
|
+
/**
|
|
9
|
+
* LMAX quotes USD/MXN in whole 1,000-USD increments and the lmax service
|
|
10
|
+
* rounds the requested size onto the nearest one. A hedge can therefore
|
|
11
|
+
* come back either short or long of the FX trade's fiat amount by up to
|
|
12
|
+
* half an increment, which callers reconciling `amountTraded` against
|
|
13
|
+
* `amount` have to tolerate in both directions. Bands smaller than one
|
|
14
|
+
* increment cannot be hedged on LMAX at all - the placement is rejected
|
|
15
|
+
* outright rather than rounded up - and operators express that with the
|
|
16
|
+
* band's own `minAmount` rather than a hardcoded fiat floor here, since
|
|
17
|
+
* the USD/MXN rate that would convert it moves.
|
|
18
|
+
*/
|
|
19
|
+
export declare const LMAX_QUANTITY_INCREMENT = 1000;
|
|
8
20
|
type RioSettingsForPolicy = Pick<RioSettings, "fxTradingPolicies">;
|
|
9
21
|
/**
|
|
10
22
|
* Resolve the active FX trading side-policy for a given (fiat, side, offset).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveFXProviderForAmount = exports.getFXTradingPolicy = exports.TRANSNETWORK_MIN_MXN_AMOUNT = void 0;
|
|
3
|
+
exports.resolveFXProviderForAmount = exports.getFXTradingPolicy = exports.LMAX_QUANTITY_INCREMENT = exports.TRANSNETWORK_MIN_MXN_AMOUNT = void 0;
|
|
4
4
|
const common_1 = require("@riocrypto/common");
|
|
5
5
|
/**
|
|
6
6
|
* Minimum trade size (in MXN) below which the Transnetwork bot will reject
|
|
@@ -12,13 +12,29 @@ exports.TRANSNETWORK_MIN_MXN_AMOUNT = 1000000;
|
|
|
12
12
|
* Settlement-offset constraints baked into provider eligibility. The
|
|
13
13
|
* Emarkets and Transnetwork bots only quote spot / very-short-dated MXN
|
|
14
14
|
* (T+0..T+2), and StoneX is RFQ/RFS for forward MXN (T+1+) and refuses
|
|
15
|
-
* same-day (T+0).
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* same-day (T+0). LMAX is a spot-only order-book venue: external-trading
|
|
16
|
+
* places LMAX orders with no settlement offset at all, so it is only
|
|
17
|
+
* eligible for T+0. Operators that want to hedge a longer-dated band on
|
|
18
|
+
* LMAX do so explicitly via `tradeAsTwoWaySettlementDateOffset: 0`.
|
|
19
|
+
* Keep these in sync with `isProviderSelectableForFiatAndOffset` in the
|
|
20
|
+
* dashboard's fx-trading-policy-card so saved policies match what the
|
|
21
|
+
* runtime will actually pick.
|
|
19
22
|
*/
|
|
20
23
|
const SHORT_DATED_BOT_MAX_OFFSET = 2;
|
|
21
24
|
const STONEX_MIN_OFFSET = 1;
|
|
25
|
+
const LMAX_MAX_OFFSET = 0;
|
|
26
|
+
/**
|
|
27
|
+
* LMAX quotes USD/MXN in whole 1,000-USD increments and the lmax service
|
|
28
|
+
* rounds the requested size onto the nearest one. A hedge can therefore
|
|
29
|
+
* come back either short or long of the FX trade's fiat amount by up to
|
|
30
|
+
* half an increment, which callers reconciling `amountTraded` against
|
|
31
|
+
* `amount` have to tolerate in both directions. Bands smaller than one
|
|
32
|
+
* increment cannot be hedged on LMAX at all - the placement is rejected
|
|
33
|
+
* outright rather than rounded up - and operators express that with the
|
|
34
|
+
* band's own `minAmount` rather than a hardcoded fiat floor here, since
|
|
35
|
+
* the USD/MXN rate that would convert it moves.
|
|
36
|
+
*/
|
|
37
|
+
exports.LMAX_QUANTITY_INCREMENT = 1000;
|
|
22
38
|
/**
|
|
23
39
|
* Providers that do not support a cosigner approval workflow. For these
|
|
24
40
|
* providers the `automatic` flag is meaningless and the runtime always
|
|
@@ -145,13 +161,15 @@ exports.getFXTradingPolicy = getFXTradingPolicy;
|
|
|
145
161
|
*
|
|
146
162
|
* `offset === undefined` is interpreted as T+0 (the system convention:
|
|
147
163
|
* "no settlement type specified" == "default" == spot), matching
|
|
148
|
-
* `getFXTradingPolicy`. As a result Emarkets / Transnetwork stay
|
|
149
|
-
* for undefined-offset callers but StoneX (which refuses T+0)
|
|
164
|
+
* `getFXTradingPolicy`. As a result Emarkets / Transnetwork / LMAX stay
|
|
165
|
+
* eligible for undefined-offset callers but StoneX (which refuses T+0)
|
|
166
|
+
* does not.
|
|
150
167
|
*/
|
|
151
168
|
const isProviderEligible = (provider, fiat, amountFiat, offset) => {
|
|
152
169
|
if (provider === common_1.FXProvider.StoneX ||
|
|
153
170
|
provider === common_1.FXProvider.Emarkets ||
|
|
154
|
-
provider === common_1.FXProvider.Transnetwork
|
|
171
|
+
provider === common_1.FXProvider.Transnetwork ||
|
|
172
|
+
provider === common_1.FXProvider.Lmax) {
|
|
155
173
|
if (fiat !== common_1.Fiat.MXN)
|
|
156
174
|
return false;
|
|
157
175
|
}
|
|
@@ -169,6 +187,9 @@ const isProviderEligible = (provider, fiat, amountFiat, offset) => {
|
|
|
169
187
|
if (provider === common_1.FXProvider.StoneX && effectiveOffset < STONEX_MIN_OFFSET) {
|
|
170
188
|
return false;
|
|
171
189
|
}
|
|
190
|
+
if (provider === common_1.FXProvider.Lmax && effectiveOffset > LMAX_MAX_OFFSET) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
172
193
|
return true;
|
|
173
194
|
};
|
|
174
195
|
/**
|
package/build/index.d.ts
CHANGED
|
@@ -50,6 +50,7 @@ export * from "./models/internal-swap";
|
|
|
50
50
|
export * from "./models/bitso-mxn-withdrawal";
|
|
51
51
|
export * from "./models/circle-address-book-recipient";
|
|
52
52
|
export * from "./models/circle-payout";
|
|
53
|
+
export * from "./models/fx-price-observation";
|
|
53
54
|
export * from "./models/market-data";
|
|
54
55
|
export * from "./models/market-price";
|
|
55
56
|
export * from "./models/telegram-session";
|
|
@@ -118,6 +119,7 @@ export * from "./models/twap-settlement";
|
|
|
118
119
|
export * from "./models/transnetwork-fx-trade";
|
|
119
120
|
export * from "./models/stonex-fx-trade";
|
|
120
121
|
export * from "./models/stonex-log";
|
|
122
|
+
export * from "./models/lmax-fx-trade";
|
|
121
123
|
export * from "./models/inbound-bank-deposit";
|
|
122
124
|
export * from "./models/inbound-crypto-deposit";
|
|
123
125
|
export * from "./models/order-log";
|
package/build/index.js
CHANGED
|
@@ -66,6 +66,7 @@ __exportStar(require("./models/internal-swap"), exports);
|
|
|
66
66
|
__exportStar(require("./models/bitso-mxn-withdrawal"), exports);
|
|
67
67
|
__exportStar(require("./models/circle-address-book-recipient"), exports);
|
|
68
68
|
__exportStar(require("./models/circle-payout"), exports);
|
|
69
|
+
__exportStar(require("./models/fx-price-observation"), exports);
|
|
69
70
|
__exportStar(require("./models/market-data"), exports);
|
|
70
71
|
__exportStar(require("./models/market-price"), exports);
|
|
71
72
|
__exportStar(require("./models/telegram-session"), exports);
|
|
@@ -134,6 +135,7 @@ __exportStar(require("./models/twap-settlement"), exports);
|
|
|
134
135
|
__exportStar(require("./models/transnetwork-fx-trade"), exports);
|
|
135
136
|
__exportStar(require("./models/stonex-fx-trade"), exports);
|
|
136
137
|
__exportStar(require("./models/stonex-log"), exports);
|
|
138
|
+
__exportStar(require("./models/lmax-fx-trade"), exports);
|
|
137
139
|
__exportStar(require("./models/inbound-bank-deposit"), exports);
|
|
138
140
|
__exportStar(require("./models/inbound-crypto-deposit"), exports);
|
|
139
141
|
__exportStar(require("./models/order-log"), exports);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fiat, Side, Crypto, ExternalTradeType, ExternalTradingProvider, ExternalTradeStatus, ExternalTradeAmountType, EmarketsSettlementType } from "@riocrypto/common";
|
|
1
|
+
import { Fiat, Side, Crypto, ExternalTradeType, ExternalTradingProvider, ExternalTradeStatus, ExternalTradeAmountType, EmarketsSettlementType, LmaxOrderType, LmaxSide, LmaxTimeInForce } from "@riocrypto/common";
|
|
2
2
|
import { Mongoose, Model, Document, HydratedDocument } from "mongoose";
|
|
3
3
|
interface ExternalTradeAttrs {
|
|
4
4
|
createdAt: Date;
|
|
@@ -49,6 +49,25 @@ interface ExternalTradeAttrs {
|
|
|
49
49
|
rate?: number;
|
|
50
50
|
takeQuoteTradeKey?: string;
|
|
51
51
|
};
|
|
52
|
+
lmax?: {
|
|
53
|
+
instrumentId?: string;
|
|
54
|
+
symbol?: string;
|
|
55
|
+
lmaxSide?: LmaxSide;
|
|
56
|
+
quantity?: number;
|
|
57
|
+
orderType?: LmaxOrderType;
|
|
58
|
+
timeInForce?: LmaxTimeInForce;
|
|
59
|
+
price?: number;
|
|
60
|
+
stopPrice?: number;
|
|
61
|
+
instructionId?: string;
|
|
62
|
+
orderId?: string;
|
|
63
|
+
matchedQuantity?: number;
|
|
64
|
+
unfilledQuantity?: number;
|
|
65
|
+
cumulativeMatchedQuantity?: number;
|
|
66
|
+
cancelledQuantity?: number;
|
|
67
|
+
matchedCost?: number;
|
|
68
|
+
commission?: number;
|
|
69
|
+
text?: string;
|
|
70
|
+
};
|
|
52
71
|
};
|
|
53
72
|
externalTradingAlgorithmId?: string;
|
|
54
73
|
arbitrageSessionId?: string;
|
|
@@ -104,6 +123,25 @@ interface ExternalTradeDoc extends Document {
|
|
|
104
123
|
rate?: number;
|
|
105
124
|
takeQuoteTradeKey?: string;
|
|
106
125
|
};
|
|
126
|
+
lmax?: {
|
|
127
|
+
instrumentId?: string;
|
|
128
|
+
symbol?: string;
|
|
129
|
+
lmaxSide?: LmaxSide;
|
|
130
|
+
quantity?: number;
|
|
131
|
+
orderType?: LmaxOrderType;
|
|
132
|
+
timeInForce?: LmaxTimeInForce;
|
|
133
|
+
price?: number;
|
|
134
|
+
stopPrice?: number;
|
|
135
|
+
instructionId?: string;
|
|
136
|
+
orderId?: string;
|
|
137
|
+
matchedQuantity?: number;
|
|
138
|
+
unfilledQuantity?: number;
|
|
139
|
+
cumulativeMatchedQuantity?: number;
|
|
140
|
+
cancelledQuantity?: number;
|
|
141
|
+
matchedCost?: number;
|
|
142
|
+
commission?: number;
|
|
143
|
+
text?: string;
|
|
144
|
+
};
|
|
107
145
|
};
|
|
108
146
|
externalTradingAlgorithmId?: string;
|
|
109
147
|
arbitrageSessionId?: string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Fiat, FXPriceObservation, FXPriceSource } from "@riocrypto/common";
|
|
2
|
+
import { Mongoose, Model, Document, HydratedDocument } from "mongoose";
|
|
3
|
+
/**
|
|
4
|
+
* One source's latest spot reading for one fiat.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately its own collection rather than a field on `MarketData`.
|
|
7
|
+
* `MarketData` is a single document that roughly a dozen services read
|
|
8
|
+
* on their hot paths, and observations are written several times a
|
|
9
|
+
* second by each publisher independently. Keeping them apart means feed
|
|
10
|
+
* churn never contends with a quote read, and a bug in the ingest path
|
|
11
|
+
* cannot bloat or corrupt the one document the whole platform prices
|
|
12
|
+
* from.
|
|
13
|
+
*
|
|
14
|
+
* One document per (fiat, source) rather than a single document holding
|
|
15
|
+
* a map of all of them, so two publishers reporting different sources
|
|
16
|
+
* never take a lock on the same document. That is the entire reason the
|
|
17
|
+
* collection was split out, and a shared singleton would have given most
|
|
18
|
+
* of it back.
|
|
19
|
+
*
|
|
20
|
+
* The cost of splitting is that an observation and the canonical rate it
|
|
21
|
+
* produced are no longer written atomically. The arbiter therefore
|
|
22
|
+
* writes the canonical rate first and the observation second: a crash
|
|
23
|
+
* between the two leaves the observation looking stale, which trips the
|
|
24
|
+
* health alert, rather than leaving it looking fresh while the rate
|
|
25
|
+
* silently stopped moving. Fail loud, in the direction that gets
|
|
26
|
+
* noticed.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* The reading itself is `FXPriceObservation` from `common`, which is
|
|
30
|
+
* also the shape the ingest route accepts and the dashboard renders.
|
|
31
|
+
* Only the identifying keys are added here, so there is one definition
|
|
32
|
+
* of what a reading contains.
|
|
33
|
+
*/
|
|
34
|
+
interface FXPriceObservationAttrs extends FXPriceObservation {
|
|
35
|
+
fiat: Fiat;
|
|
36
|
+
source: FXPriceSource;
|
|
37
|
+
}
|
|
38
|
+
interface FXPriceObservationModel extends Model<FXPriceObservationDoc> {
|
|
39
|
+
build(attrs: FXPriceObservationAttrs): HydratedDocument<FXPriceObservationDoc>;
|
|
40
|
+
}
|
|
41
|
+
interface FXPriceObservationDoc extends Document, FXPriceObservation {
|
|
42
|
+
fiat: Fiat;
|
|
43
|
+
source: FXPriceSource;
|
|
44
|
+
}
|
|
45
|
+
declare const buildFXPriceObservation: (mongoose: Mongoose) => FXPriceObservationModel;
|
|
46
|
+
export { buildFXPriceObservation, FXPriceObservationDoc, FXPriceObservationAttrs, };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildFXPriceObservation = void 0;
|
|
4
|
+
const buildFXPriceObservation = (mongoose) => {
|
|
5
|
+
if (mongoose.models.FXPriceObservation) {
|
|
6
|
+
return mongoose.model("FXPriceObservation");
|
|
7
|
+
}
|
|
8
|
+
const FXPriceObservationSchema = new mongoose.Schema({
|
|
9
|
+
fiat: {
|
|
10
|
+
type: String,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
source: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
value: {
|
|
18
|
+
type: Number,
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
lastUpdated: {
|
|
22
|
+
type: Date,
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
lastPriceChangedAt: {
|
|
26
|
+
type: Date,
|
|
27
|
+
},
|
|
28
|
+
bid: {
|
|
29
|
+
type: Number,
|
|
30
|
+
},
|
|
31
|
+
ask: {
|
|
32
|
+
type: Number,
|
|
33
|
+
},
|
|
34
|
+
tvProvider: {
|
|
35
|
+
type: String,
|
|
36
|
+
},
|
|
37
|
+
}, {
|
|
38
|
+
toJSON: {
|
|
39
|
+
transform(doc, ret) {
|
|
40
|
+
ret.id = ret._id.valueOf();
|
|
41
|
+
delete ret._id;
|
|
42
|
+
delete ret.__v;
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
// At most one row per (fiat, source). The ingest path upserts on this
|
|
47
|
+
// key, so the uniqueness is what stops a race on the very first report
|
|
48
|
+
// for a pair from leaving two rows that then take turns being read.
|
|
49
|
+
FXPriceObservationSchema.index({ fiat: 1, source: 1 }, { unique: true });
|
|
50
|
+
FXPriceObservationSchema.statics.build = (attrs) => {
|
|
51
|
+
return new FXPriceObservation(attrs);
|
|
52
|
+
};
|
|
53
|
+
const FXPriceObservation = mongoose.model("FXPriceObservation", FXPriceObservationSchema);
|
|
54
|
+
return FXPriceObservation;
|
|
55
|
+
};
|
|
56
|
+
exports.buildFXPriceObservation = buildFXPriceObservation;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { LmaxFXTradeStatus, LmaxOrderType, LmaxSide, LmaxTimeInForce, Side } from "@riocrypto/common";
|
|
2
|
+
import { Mongoose, Model, Document } from "mongoose";
|
|
3
|
+
interface LmaxFXTradeAttrs {
|
|
4
|
+
createdAt: Date;
|
|
5
|
+
status: LmaxFXTradeStatus;
|
|
6
|
+
side: Side;
|
|
7
|
+
lmaxSide: LmaxSide;
|
|
8
|
+
instrumentId: string;
|
|
9
|
+
symbol: string;
|
|
10
|
+
originCurrency: string;
|
|
11
|
+
destinationCurrency: string;
|
|
12
|
+
quantity: number;
|
|
13
|
+
orderType: LmaxOrderType;
|
|
14
|
+
timeInForce: LmaxTimeInForce;
|
|
15
|
+
price?: number;
|
|
16
|
+
stopPrice?: number;
|
|
17
|
+
instructionId: string;
|
|
18
|
+
cancelInstructionId?: string;
|
|
19
|
+
orderId?: string;
|
|
20
|
+
tradeKey?: string;
|
|
21
|
+
matchedQuantity?: number;
|
|
22
|
+
unfilledQuantity?: number;
|
|
23
|
+
cumulativeMatchedQuantity?: number;
|
|
24
|
+
cancelledQuantity?: number;
|
|
25
|
+
matchedCost?: number;
|
|
26
|
+
commission?: number;
|
|
27
|
+
avgPrice?: number;
|
|
28
|
+
text?: string;
|
|
29
|
+
submitted?: boolean;
|
|
30
|
+
submitAttempts?: number;
|
|
31
|
+
}
|
|
32
|
+
interface LmaxFXTradeDoc extends Document {
|
|
33
|
+
id: string;
|
|
34
|
+
createdAt: Date;
|
|
35
|
+
status: LmaxFXTradeStatus;
|
|
36
|
+
side: Side;
|
|
37
|
+
lmaxSide: LmaxSide;
|
|
38
|
+
instrumentId: string;
|
|
39
|
+
symbol: string;
|
|
40
|
+
originCurrency: string;
|
|
41
|
+
destinationCurrency: string;
|
|
42
|
+
quantity: number;
|
|
43
|
+
orderType: LmaxOrderType;
|
|
44
|
+
timeInForce: LmaxTimeInForce;
|
|
45
|
+
price?: number;
|
|
46
|
+
stopPrice?: number;
|
|
47
|
+
instructionId: string;
|
|
48
|
+
cancelInstructionId?: string;
|
|
49
|
+
orderId?: string;
|
|
50
|
+
tradeKey?: string;
|
|
51
|
+
matchedQuantity?: number;
|
|
52
|
+
unfilledQuantity?: number;
|
|
53
|
+
cumulativeMatchedQuantity?: number;
|
|
54
|
+
cancelledQuantity?: number;
|
|
55
|
+
matchedCost?: number;
|
|
56
|
+
commission?: number;
|
|
57
|
+
avgPrice?: number;
|
|
58
|
+
text?: string;
|
|
59
|
+
submitted?: boolean;
|
|
60
|
+
submitAttempts?: number;
|
|
61
|
+
}
|
|
62
|
+
interface LmaxFXTradeModel extends Model<LmaxFXTradeDoc> {
|
|
63
|
+
build(attrs: LmaxFXTradeAttrs): LmaxFXTradeDoc;
|
|
64
|
+
}
|
|
65
|
+
declare const buildLmaxFXTrade: (mongoose: Mongoose) => LmaxFXTradeModel;
|
|
66
|
+
export { buildLmaxFXTrade, LmaxFXTradeDoc, LmaxFXTradeAttrs };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildLmaxFXTrade = void 0;
|
|
4
|
+
const buildLmaxFXTrade = (mongoose) => {
|
|
5
|
+
if (mongoose.models.LmaxFXTrade) {
|
|
6
|
+
return mongoose.model("LmaxFXTrade");
|
|
7
|
+
}
|
|
8
|
+
const LmaxFXTradeSchema = new mongoose.Schema({
|
|
9
|
+
createdAt: {
|
|
10
|
+
type: Date,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
status: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
side: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
lmaxSide: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
instrumentId: {
|
|
26
|
+
type: String,
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
symbol: {
|
|
30
|
+
type: String,
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
originCurrency: {
|
|
34
|
+
type: String,
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
destinationCurrency: {
|
|
38
|
+
type: String,
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
quantity: {
|
|
42
|
+
type: Number,
|
|
43
|
+
required: true,
|
|
44
|
+
},
|
|
45
|
+
orderType: {
|
|
46
|
+
type: String,
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
timeInForce: {
|
|
50
|
+
type: String,
|
|
51
|
+
required: true,
|
|
52
|
+
},
|
|
53
|
+
price: {
|
|
54
|
+
type: Number,
|
|
55
|
+
},
|
|
56
|
+
stopPrice: {
|
|
57
|
+
type: Number,
|
|
58
|
+
},
|
|
59
|
+
instructionId: {
|
|
60
|
+
type: String,
|
|
61
|
+
required: true,
|
|
62
|
+
},
|
|
63
|
+
cancelInstructionId: {
|
|
64
|
+
type: String,
|
|
65
|
+
},
|
|
66
|
+
orderId: {
|
|
67
|
+
type: String,
|
|
68
|
+
},
|
|
69
|
+
tradeKey: {
|
|
70
|
+
type: String,
|
|
71
|
+
},
|
|
72
|
+
matchedQuantity: {
|
|
73
|
+
type: Number,
|
|
74
|
+
},
|
|
75
|
+
unfilledQuantity: {
|
|
76
|
+
type: Number,
|
|
77
|
+
},
|
|
78
|
+
cumulativeMatchedQuantity: {
|
|
79
|
+
type: Number,
|
|
80
|
+
},
|
|
81
|
+
cancelledQuantity: {
|
|
82
|
+
type: Number,
|
|
83
|
+
},
|
|
84
|
+
matchedCost: {
|
|
85
|
+
type: Number,
|
|
86
|
+
},
|
|
87
|
+
commission: {
|
|
88
|
+
type: Number,
|
|
89
|
+
},
|
|
90
|
+
avgPrice: {
|
|
91
|
+
type: Number,
|
|
92
|
+
},
|
|
93
|
+
text: {
|
|
94
|
+
type: String,
|
|
95
|
+
},
|
|
96
|
+
submitted: {
|
|
97
|
+
type: Boolean,
|
|
98
|
+
default: false,
|
|
99
|
+
},
|
|
100
|
+
submitAttempts: {
|
|
101
|
+
type: Number,
|
|
102
|
+
default: 0,
|
|
103
|
+
},
|
|
104
|
+
}, {
|
|
105
|
+
toJSON: {
|
|
106
|
+
transform(doc, ret) {
|
|
107
|
+
ret.id = ret._id.valueOf();
|
|
108
|
+
delete ret._id;
|
|
109
|
+
delete ret.__v;
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
// Partial unique index: only docs with a string tradeKey are indexed,
|
|
114
|
+
// so existing docs (no field) don't collide. Guarantees at most one
|
|
115
|
+
// LmaxFXTrade per caller-supplied trade key.
|
|
116
|
+
LmaxFXTradeSchema.index({ tradeKey: 1 }, {
|
|
117
|
+
unique: true,
|
|
118
|
+
partialFilterExpression: { tradeKey: { $type: "string" } },
|
|
119
|
+
});
|
|
120
|
+
LmaxFXTradeSchema.statics.build = (attrs) => {
|
|
121
|
+
return new LmaxFXTrade(attrs);
|
|
122
|
+
};
|
|
123
|
+
const LmaxFXTrade = mongoose.model("LmaxFXTrade", LmaxFXTradeSchema);
|
|
124
|
+
return LmaxFXTrade;
|
|
125
|
+
};
|
|
126
|
+
exports.buildLmaxFXTrade = buildLmaxFXTrade;
|
|
@@ -1,25 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConversionRate } from "@riocrypto/common";
|
|
2
2
|
import { Mongoose, Model, Document, HydratedDocument } from "mongoose";
|
|
3
3
|
interface MarketDataAttrs {
|
|
4
4
|
conversionRatesToUSD: {
|
|
5
|
-
[key: string]:
|
|
6
|
-
value: number;
|
|
7
|
-
lastUpdated: Date;
|
|
8
|
-
lastPriceChangedAt?: Date;
|
|
9
|
-
provider?: TVFXDataProvider;
|
|
10
|
-
bot?: boolean;
|
|
11
|
-
frozen?: boolean;
|
|
12
|
-
};
|
|
5
|
+
[key: string]: ConversionRate;
|
|
13
6
|
};
|
|
14
7
|
conversionRatesFromUSD: {
|
|
15
|
-
[key: string]:
|
|
16
|
-
value: number;
|
|
17
|
-
lastUpdated: Date;
|
|
18
|
-
lastPriceChangedAt?: Date;
|
|
19
|
-
provider?: TVFXDataProvider;
|
|
20
|
-
bot?: boolean;
|
|
21
|
-
frozen?: boolean;
|
|
22
|
-
};
|
|
8
|
+
[key: string]: ConversionRate;
|
|
23
9
|
};
|
|
24
10
|
assetPricesInUSD: {
|
|
25
11
|
[key: string]: {
|
|
@@ -64,24 +50,10 @@ interface MarketDataModel extends Model<MarketDataDoc> {
|
|
|
64
50
|
}
|
|
65
51
|
interface MarketDataDoc extends Document {
|
|
66
52
|
conversionRatesToUSD: {
|
|
67
|
-
[key: string]:
|
|
68
|
-
value: number;
|
|
69
|
-
lastUpdated: Date;
|
|
70
|
-
lastPriceChangedAt?: Date;
|
|
71
|
-
provider?: TVFXDataProvider;
|
|
72
|
-
bot?: boolean;
|
|
73
|
-
frozen?: boolean;
|
|
74
|
-
};
|
|
53
|
+
[key: string]: ConversionRate;
|
|
75
54
|
};
|
|
76
55
|
conversionRatesFromUSD: {
|
|
77
|
-
[key: string]:
|
|
78
|
-
value: number;
|
|
79
|
-
lastUpdated: Date;
|
|
80
|
-
lastPriceChangedAt?: Date;
|
|
81
|
-
provider?: TVFXDataProvider;
|
|
82
|
-
bot?: boolean;
|
|
83
|
-
frozen?: boolean;
|
|
84
|
-
};
|
|
56
|
+
[key: string]: ConversionRate;
|
|
85
57
|
};
|
|
86
58
|
assetPricesInUSD: {
|
|
87
59
|
[key: string]: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Country, Crypto, DeferredPaymentType, Fiat, FXProvider, FXTradingPolicies, ProcessorRoutingConfig, Side } from "@riocrypto/common";
|
|
1
|
+
import { Country, Crypto, DeferredPaymentType, Fiat, FXPriceSourcePolicies, FXProvider, FXTradingPolicies, ProcessorRoutingConfig, Side } from "@riocrypto/common";
|
|
2
2
|
import { TVFXDataProvider } from "@riocrypto/common";
|
|
3
3
|
import mongoose, { HydratedDocument } from "mongoose";
|
|
4
4
|
interface RioSettingsAttrs {
|
|
@@ -60,6 +60,7 @@ interface RioSettingsAttrs {
|
|
|
60
60
|
TVFXDataProvider: {
|
|
61
61
|
[key in Fiat]: TVFXDataProvider;
|
|
62
62
|
};
|
|
63
|
+
fxPriceSources?: FXPriceSourcePolicies;
|
|
63
64
|
defaultTwoWaySettlementDateOffsetFee?: {
|
|
64
65
|
[key in Country]?: {
|
|
65
66
|
[key in Side]?: {
|
|
@@ -156,6 +157,7 @@ interface RioSettingsDoc extends mongoose.Document {
|
|
|
156
157
|
TVFXDataProvider: {
|
|
157
158
|
[key in Fiat]: TVFXDataProvider;
|
|
158
159
|
};
|
|
160
|
+
fxPriceSources?: FXPriceSourcePolicies;
|
|
159
161
|
defaultTwoWaySettlementDateOffsetFee?: {
|
|
160
162
|
[key in Country]?: {
|
|
161
163
|
[key in Side]?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riocrypto/common-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2910",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@google-cloud/secret-manager": "^5.6.0",
|
|
29
29
|
"@google-cloud/storage": "^7.19.0",
|
|
30
30
|
"@hyperdx/node-opentelemetry": "^0.10.3",
|
|
31
|
-
"@riocrypto/common": "1.0.
|
|
31
|
+
"@riocrypto/common": "1.0.2720",
|
|
32
32
|
"@slack/web-api": "^7.15.0",
|
|
33
33
|
"@types/express": "^4.17.25",
|
|
34
34
|
"axios": "1.18.1",
|