@riocrypto/common-server 1.0.2909 → 1.0.2911

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.
@@ -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[];
@@ -17,6 +17,16 @@ const common_1 = require("@riocrypto/common");
17
17
  const https_1 = __importDefault(require("https"));
18
18
  const axios_with_logging_1 = require("./axios-with-logging");
19
19
  const secret_manager_client_1 = require("./secret-manager-client");
20
+ /**
21
+ * Deadline for one FX price report.
22
+ *
23
+ * Generous enough for a round trip through the public ingress, and short
24
+ * enough that a stalled path costs a gap in reports rather than a
25
+ * publisher that never sends again. Comfortably inside the staleness
26
+ * thresholds market-data judges these feeds by, so an abandoned report
27
+ * does not by itself make a source look dead.
28
+ */
29
+ const FX_OBSERVATION_TIMEOUT_MS = 5000;
20
30
  class ClusterClient {
21
31
  constructor(baseUrl, clusterApiKey) {
22
32
  this.baseUrl = baseUrl;
@@ -955,6 +965,78 @@ class ClusterClient {
955
965
  return response.data;
956
966
  });
957
967
  }
968
+ placeLmaxOrder(params) {
969
+ return __awaiter(this, void 0, void 0, function* () {
970
+ const { tradeKey } = params;
971
+ const response = yield this.axios.post(`${this.baseUrl}/api/lmax/place-order`, params, {
972
+ headers: {
973
+ "x-cluster-api-key": this.clusterApiKey,
974
+ "X-Trade-Key": tradeKey,
975
+ },
976
+ });
977
+ return response.data;
978
+ });
979
+ }
980
+ getLmaxTrade(tradeId) {
981
+ return __awaiter(this, void 0, void 0, function* () {
982
+ const response = yield this.axios.get(`${this.baseUrl}/api/lmax/trades/${tradeId}`, { headers: { "x-cluster-api-key": this.clusterApiKey } });
983
+ return response.data;
984
+ });
985
+ }
986
+ getLmaxTradeByKey(tradeKey) {
987
+ var _a;
988
+ return __awaiter(this, void 0, void 0, function* () {
989
+ try {
990
+ const response = yield this.axios.get(`${this.baseUrl}/api/lmax/trades/by-trade-key/${encodeURIComponent(tradeKey)}`, { headers: { "x-cluster-api-key": this.clusterApiKey } });
991
+ return response.data;
992
+ }
993
+ catch (err) {
994
+ if (typeof err === "object" &&
995
+ err !== null &&
996
+ "response" in err &&
997
+ ((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
998
+ return null;
999
+ }
1000
+ throw err;
1001
+ }
1002
+ });
1003
+ }
1004
+ cancelLmaxTrade(tradeId) {
1005
+ return __awaiter(this, void 0, void 0, function* () {
1006
+ const response = yield this.axios.post(`${this.baseUrl}/api/lmax/cancel-trade/${tradeId}`, {}, { headers: { "x-cluster-api-key": this.clusterApiKey } });
1007
+ return response.data;
1008
+ });
1009
+ }
1010
+ // Report one source's latest spot reading to market-data, which decides
1011
+ // whether it becomes the canonical rate.
1012
+ //
1013
+ // `value` is always units of fiat per 1 USD, whichever way round the
1014
+ // source natively quotes, because market-data derives the inverse
1015
+ // itself and two numbers that have to agree is a bug waiting to
1016
+ // happen.
1017
+ //
1018
+ // Callers publish on every tick, so this is called several times a
1019
+ // second and every caller must attach a catch. An unhandled rejection
1020
+ // here would take the publisher down on the first blip, and a blip in
1021
+ // one feed is precisely when the other one needs to still be running.
1022
+ // Losing arbitration is not an error: a 200 with `accepted: false`
1023
+ // means the report was recorded while another source is active.
1024
+ reportFXPriceObservation(params) {
1025
+ return __awaiter(this, void 0, void 0, function* () {
1026
+ const response = yield this.axios.post(`${this.baseUrl}/api/market/data/fx-observation`, params, {
1027
+ headers: { "x-cluster-api-key": this.clusterApiKey },
1028
+ // The shared axios instance has no default timeout, which is fine
1029
+ // for the request/response calls on this client but not for one
1030
+ // published on a fixed interval. Without a deadline a stalled
1031
+ // ingest path leaves requests open indefinitely and the publisher
1032
+ // accumulates them. Scoped to this call rather than set as a
1033
+ // client default, because several endpoints here are legitimately
1034
+ // slow and would start failing.
1035
+ timeout: FX_OBSERVATION_TIMEOUT_MS,
1036
+ });
1037
+ return response.data;
1038
+ });
1039
+ }
958
1040
  // Reconcile helper: look up a Stonex trade by caller-supplied trade key.
959
1041
  // Returns null when the key has no matching trade (the server either
960
1042
  // 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>;
@@ -44,7 +44,11 @@ class CurrencyAPIClient {
44
44
  if (!cachedResponse) {
45
45
  throw new common_1.GenericInternalError(`Unable to get exchange rate from currencyAPI for ${from} to ${to}`);
46
46
  }
47
- const exchangeRate = cachedResponse.data[to].value;
47
+ // Assigns the outer binding deliberately. Declaring a local here
48
+ // shadowed it, so the cached rate was computed and thrown away and
49
+ // the zero check below then threw, which made this whole fallback
50
+ // unreachable on the one path it exists for.
51
+ exchangeRate = cachedResponse.data[to].value;
48
52
  }
49
53
  if (!exchangeRate) {
50
54
  (_a = logger_1.default.getLogger()) === null || _a === void 0 ? void 0 : _a.error("Unable to get exchange rate");
@@ -53,6 +57,35 @@ class CurrencyAPIClient {
53
57
  return exchangeRate;
54
58
  });
55
59
  }
60
+ /**
61
+ * Fetch several rates against one base in a single request.
62
+ *
63
+ * `convert` issues one request per pair, so checking three currencies
64
+ * through it costs three calls against a metered quota. The `currencies`
65
+ * parameter takes a comma-separated list, so the same answer costs one.
66
+ *
67
+ * Intentionally not cached: the only caller is a health probe, and a
68
+ * cached hit would report the fallback as reachable when the last real
69
+ * request may have failed.
70
+ */
71
+ getLatestRates(baseCurrency, currencies) {
72
+ var _a, _b;
73
+ return __awaiter(this, void 0, void 0, function* () {
74
+ const response = yield this.axiosClient.get(`https://api.currencyapi.com/v3/latest?apikey=${this.apiKey}&base_currency=${baseCurrency}&currencies=${currencies.join(",")}`);
75
+ const data = (_a = response.data) === null || _a === void 0 ? void 0 : _a.data;
76
+ if (!data) {
77
+ throw new common_1.GenericInternalError(`Unable to get latest exchange rates from currencyAPI for ${baseCurrency}`);
78
+ }
79
+ const rates = {};
80
+ for (const currency of currencies) {
81
+ const value = (_b = data[currency]) === null || _b === void 0 ? void 0 : _b.value;
82
+ if (typeof value === "number" && value > 0) {
83
+ rates[currency] = value;
84
+ }
85
+ }
86
+ return rates;
87
+ });
88
+ }
56
89
  getHistoricalExchangeRates(from, to, start, end) {
57
90
  return __awaiter(this, void 0, void 0, function* () {
58
91
  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). Keep these in sync with
16
- * `isProviderSelectableForFiatAndOffset` in the dashboard's
17
- * fx-trading-policy-card so saved policies match what the runtime will
18
- * actually pick.
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 eligible
149
- * for undefined-offset callers but StoneX (which refuses T+0) does not.
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 { TVFXDataProvider } from "@riocrypto/common";
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]?: {
@@ -68,6 +68,9 @@ const buildRioSettings = (mongoose) => {
68
68
  TVFXDataProvider: {
69
69
  type: Object,
70
70
  },
71
+ fxPriceSources: {
72
+ type: Object,
73
+ },
71
74
  defaultTwoWaySettlementDateOffsetFee: {
72
75
  type: Object,
73
76
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riocrypto/common-server",
3
- "version": "1.0.2909",
3
+ "version": "1.0.2911",
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.2715",
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",