gloomberb 0.18.0 → 0.18.1
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/package.json +1 -1
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/types.ts +9 -0
- package/src/cli/commands/fred-series.ts +15 -5
- package/src/cli/commands/market.ts +9 -5
- package/src/cli/commands/overview.ts +31 -6
- package/src/cli/commands/ticker.ts +5 -2
- package/src/cli/pane-functions/function-help.ts +4 -4
- package/src/cli/symbol-list.ts +51 -16
- package/src/components/chart/composite/composite-chart.tsx +11 -5
- package/src/components/chart/composite/panel-surface.tsx +6 -8
- package/src/components/chart/composite/price-axis-labels.tsx +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/workflow/collection-actions.ts +4 -0
- package/src/components/onboarding/wizard-positions.ts +8 -0
- package/src/docs/snapshot.json +52 -17
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/analytics/risk-client.ts +44 -11
- package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
- package/src/plugins/builtin/analytics/risk-model.ts +79 -16
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -3
- package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
- package/src/plugins/builtin/custom-view/loader.ts +3 -2
- package/src/plugins/builtin/custom-view/pane.tsx +5 -4
- package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
- package/src/plugins/builtin/econ/calendar-model.ts +3 -2
- package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
- package/src/plugins/builtin/executives/data.ts +24 -8
- package/src/plugins/builtin/executives/index.tsx +2 -18
- package/src/plugins/builtin/executives/pane.tsx +34 -10
- package/src/plugins/builtin/market-movers/client.ts +2 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -3
- package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
- package/src/plugins/builtin/market-movers/session.ts +25 -10
- package/src/plugins/builtin/news/wire/feed-config.ts +4 -1
- package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
- package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
- package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
- package/src/plugins/builtin/notes/cli.ts +33 -8
- package/src/plugins/builtin/notes/export.ts +49 -3
- package/src/plugins/builtin/notes/files.ts +14 -3
- package/src/plugins/builtin/notes/index.tsx +3 -3
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
- package/src/plugins/builtin/notes/store.ts +13 -3
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +35 -5
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +2 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -3
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +10 -1
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
- package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
- package/src/plugins/builtin/shared/pro-feature-wall.tsx +9 -4
- package/src/plugins/builtin/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/data.ts +5 -1
- package/src/plugins/builtin/thirteenf/headless.ts +3 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
- package/src/portfolio/cost-fx.ts +84 -0
- package/src/portfolio/mutations.ts +16 -2
- package/src/portfolio/position-metrics.ts +45 -4
- package/src/portfolio/position-quantity.ts +43 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
- package/src/sync/core-contributors.ts +1 -0
- package/src/team/collections.ts +2 -0
- package/src/tickers/metadata.ts +10 -0
- package/src/types/ticker.ts +9 -0
- package/src/utils/currency-units.ts +9 -0
|
@@ -39,6 +39,13 @@ export interface SetManualPortfolioPositionInput {
|
|
|
39
39
|
* read does not unsize it.
|
|
40
40
|
*/
|
|
41
41
|
multiplier?: number;
|
|
42
|
+
/** The purchase date, YYYY-MM-DD. Left out, a single manual lot keeps the one it had. */
|
|
43
|
+
dateAcquired?: string;
|
|
44
|
+
/**
|
|
45
|
+
* The rate the cost was bought at: one unit of `currency` in `costFx.currency`.
|
|
46
|
+
* Null clears it; left out, a single manual lot in the same currency keeps the one it had.
|
|
47
|
+
*/
|
|
48
|
+
costFx?: { rate: number; currency: string } | null;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
/** What a position's size counts, as its field and messages name it. */
|
|
@@ -406,8 +413,14 @@ export function setManualPortfolioPosition(
|
|
|
406
413
|
const replacedPositionCount = replacedPositions.length;
|
|
407
414
|
// Editing a single snapshot (for example after a split) does not change its
|
|
408
415
|
// acquisition date. A consolidated replacement of several lots has no one date.
|
|
409
|
-
const
|
|
410
|
-
|
|
416
|
+
const single = replacedPositions.length === 1 && replacedPositions[0]?.broker === "manual" ? replacedPositions[0] : null;
|
|
417
|
+
const dateAcquired = input.dateAcquired ?? single?.dateAcquired;
|
|
418
|
+
// The rate the cost was bought at goes with its cost: a lot kept in its currency keeps it unless it is replaced or cleared.
|
|
419
|
+
const keptCostFx = single && single.currency?.trim().toUpperCase() === nextCurrency
|
|
420
|
+
&& typeof single.costFxRate === "number" && single.costFxCurrency
|
|
421
|
+
? { rate: single.costFxRate, currency: single.costFxCurrency } : null;
|
|
422
|
+
const costFx = input.costFx === undefined ? keptCostFx : input.costFx;
|
|
423
|
+
if (costFx && !(Number.isFinite(costFx.rate) && costFx.rate > 0)) throw new Error("The cost FX rate must be a positive number.");
|
|
411
424
|
// A contract's size does not change with the count held; one the lots agree on carries over.
|
|
412
425
|
const keptMultipliers = new Set(replacedPositions.map((position) => position.multiplier));
|
|
413
426
|
const keptMultiplier = keptMultipliers.size === 1 ? [...keptMultipliers][0] : undefined;
|
|
@@ -422,6 +435,7 @@ export function setManualPortfolioPosition(
|
|
|
422
435
|
currency: nextCurrency,
|
|
423
436
|
broker: "manual",
|
|
424
437
|
...(dateAcquired ? { dateAcquired } : {}),
|
|
438
|
+
...(costFx ? { costFxRate: costFx.rate, costFxCurrency: costFx.currency.trim().toUpperCase() } : {}),
|
|
425
439
|
...(multiplier !== undefined ? { multiplier } : {}),
|
|
426
440
|
});
|
|
427
441
|
|
|
@@ -3,7 +3,7 @@ import type { Quote } from "../types/financials";
|
|
|
3
3
|
import type { PriceBasis } from "../types/instrument";
|
|
4
4
|
import { resolvePriceBasis } from "../market-data/market/price-basis";
|
|
5
5
|
import { getActiveQuoteDisplay, type ActiveQuoteDisplay } from "../market-data/market/status";
|
|
6
|
-
import { resolveCurrencyUnit } from "../utils/currency-units";
|
|
6
|
+
import { heldCurrencyUnit, resolveCurrencyUnit } from "../utils/currency-units";
|
|
7
7
|
import { isFuturesTicker } from "./futures-positions";
|
|
8
8
|
|
|
9
9
|
export interface PortfolioPositionMetrics {
|
|
@@ -103,6 +103,42 @@ function resolvePositionCostMultiplier(position: TickerRecord["metadata"]["posit
|
|
|
103
103
|
return withoutMultiplierError < withMultiplierError ? 1 : priceMultiplier;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* The rate a lot's cost converts at when valued in `valuationCurrency`: the
|
|
108
|
+
* one it was bought at (`costFxRate`), when that rate converts into this very
|
|
109
|
+
* currency and the lot is held in another. Null otherwise, and the cost
|
|
110
|
+
* converts at today's rate as its value does.
|
|
111
|
+
*/
|
|
112
|
+
export function lotCostFxRate(
|
|
113
|
+
position: Pick<TickerPosition, "costFxRate" | "costFxCurrency">,
|
|
114
|
+
positionCurrency: string,
|
|
115
|
+
valuationCurrency: string,
|
|
116
|
+
): number | null {
|
|
117
|
+
const rate = position.costFxRate;
|
|
118
|
+
const into = position.costFxCurrency?.trim();
|
|
119
|
+
if (typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0 || !into) return null;
|
|
120
|
+
return into === valuationCurrency.trim() && !sameCurrency(positionCurrency, into) ? rate : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Two currency codes in one currency, its minor units included: GBp is GBP, USX is USD. */
|
|
124
|
+
export function sameCurrency(left: string, right: string): boolean {
|
|
125
|
+
return heldCurrencyUnit(left).currency === heldCurrencyUnit(right).currency;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Whether a lot's cost, valued in `valuationCurrency`, converts at today's
|
|
130
|
+
* rate: it is held in another currency and no rate from when it was bought
|
|
131
|
+
* applies, so its P&L leaves out the currency's move since.
|
|
132
|
+
*/
|
|
133
|
+
export function lotCostAtTodaysRate(
|
|
134
|
+
position: Pick<TickerPosition, "costFxRate" | "costFxCurrency">,
|
|
135
|
+
positionCurrency: string,
|
|
136
|
+
valuationCurrency: string,
|
|
137
|
+
): boolean {
|
|
138
|
+
return !!positionCurrency.trim() && !sameCurrency(positionCurrency, valuationCurrency)
|
|
139
|
+
&& lotCostFxRate(position, positionCurrency, valuationCurrency) === null;
|
|
140
|
+
}
|
|
141
|
+
|
|
106
142
|
export function getPortfolioPositionMetrics(
|
|
107
143
|
ticker: TickerRecord,
|
|
108
144
|
activeTab: string | undefined,
|
|
@@ -128,6 +164,11 @@ export function getPortfolioPositionMetrics(
|
|
|
128
164
|
if (!currency) return Number.NaN;
|
|
129
165
|
return valuation ? valuation.convert(value, currency) : currencies.size > 1 ? Number.NaN : value;
|
|
130
166
|
};
|
|
167
|
+
// A cost converts at the rate the lot was bought at, when it has one into the valuation's currency.
|
|
168
|
+
const convertCost = (value: number, position: typeof positions[number]) => {
|
|
169
|
+
const bought = valuation ? lotCostFxRate(position, currencyFor(position), valuation.currency) : null;
|
|
170
|
+
return bought !== null ? value * bought : convert(value, position);
|
|
171
|
+
};
|
|
131
172
|
const metrics: PortfolioPositionMetrics = {
|
|
132
173
|
positionCurrency, positionCount: positions.length, hasShorts: false,
|
|
133
174
|
totalShares: 0, totalCost: 0, hasCostBasis: positions.length > 0, priceBasis, signedCost: 0, totalCostUnits: 0,
|
|
@@ -169,8 +210,8 @@ export function getPortfolioPositionMetrics(
|
|
|
169
210
|
metrics.unknownContractSize ||= unknownSize && !Number.isFinite(position.marketValue);
|
|
170
211
|
if (Number.isFinite(contractMultiplier)) metrics.multiplierHint = Math.max(metrics.multiplierHint, contractMultiplier);
|
|
171
212
|
metrics.totalShares += magnitude * direction;
|
|
172
|
-
metrics.totalCost +=
|
|
173
|
-
metrics.signedCost += direction *
|
|
213
|
+
metrics.totalCost += convertCost(cost, position);
|
|
214
|
+
metrics.signedCost += direction * convertCost(cost, position);
|
|
174
215
|
metrics.totalCostUnits += magnitude * costMultiplier;
|
|
175
216
|
metrics.totalPriceUnits += magnitude * quoteMultiplier * direction;
|
|
176
217
|
metrics.grossPriceUnits += magnitude * quoteMultiplier;
|
|
@@ -191,7 +232,7 @@ export function getPortfolioPositionMetrics(
|
|
|
191
232
|
if (pnl == null || !Number.isFinite(pnl)) metrics.hasBrokerPnl = false;
|
|
192
233
|
else metrics.brokerPnl += convert(pnl, position);
|
|
193
234
|
metrics.pnlLots.push({
|
|
194
|
-
signedCost: direction *
|
|
235
|
+
signedCost: direction * convertCost(cost, position),
|
|
195
236
|
priceUnits: magnitude * quoteMultiplier * direction,
|
|
196
237
|
direction,
|
|
197
238
|
brokerMarketValue: marketValue != null && Number.isFinite(marketValue) ? convert(marketValue, position) : null,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { resolveAssetDisplayKind } from "../market-data/market/format";
|
|
2
|
+
import type { TickerRecord } from "../types/ticker";
|
|
3
|
+
import { ambiguousNumberMessage, thousandsDotReadings } from "../utils/decimal-comma";
|
|
4
|
+
import { isKnownNonUsListing } from "../utils/sec";
|
|
5
|
+
import { isFuturesTicker } from "./futures-positions";
|
|
6
|
+
import { positionQuantityLabel, type PositionQuantityLabel } from "./mutations";
|
|
7
|
+
|
|
8
|
+
/** Fund units are issued in fractions wherever the fund is listed. */
|
|
9
|
+
const FRACTIONAL_FUND_TYPES = new Set(["FUND", "MUTUALFUND"]);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Whether a listing is held in whole units: a future's or an option's
|
|
13
|
+
* contracts, and shares listed outside the US, which trade in whole lots
|
|
14
|
+
* (THYAO.IS, SAP:XETRA). US shares, which brokers split into fractions,
|
|
15
|
+
* mutual funds, coins, currencies and bonds may be held in fractions.
|
|
16
|
+
*/
|
|
17
|
+
function tradesInWholeUnits(ticker: TickerRecord): boolean {
|
|
18
|
+
const kind = resolveAssetDisplayKind({ assetCategory: ticker.metadata.assetCategory });
|
|
19
|
+
if (isFuturesTicker(ticker) || kind === "contract") return true;
|
|
20
|
+
const category = ticker.metadata.assetCategory?.trim().toUpperCase().replace(/[\s_-]/g, "") ?? "";
|
|
21
|
+
const shares = kind === "equity" ? !FRACTIONAL_FUND_TYPES.has(category) : kind === "other" && !category;
|
|
22
|
+
return shares && isKnownNonUsListing(ticker);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Why a typed size cannot be stored as read: a dot and three digits (`1.250`)
|
|
27
|
+
* on a listing held in whole units, where it is far likelier the thousands
|
|
28
|
+
* grouping many locales write (1,250 shares) than a quarter of a share, so the
|
|
29
|
+
* command asks which, as it does for `1,250`. Null for any other size, and
|
|
30
|
+
* for a listing that may be held in fractions, where `1.250` is 1.25.
|
|
31
|
+
*/
|
|
32
|
+
export function wholeUnitQuantityProblem(
|
|
33
|
+
typed: string,
|
|
34
|
+
ticker: TickerRecord,
|
|
35
|
+
options: { label?: PositionQuantityLabel; listing?: string } = {},
|
|
36
|
+
): string | null {
|
|
37
|
+
const reading = thousandsDotReadings(typed);
|
|
38
|
+
if (!reading || !tradesInWholeUnits(ticker)) return null;
|
|
39
|
+
const label = options.label ?? positionQuantityLabel(ticker);
|
|
40
|
+
const listing = options.listing ?? ticker.metadata.ticker;
|
|
41
|
+
const because = `${listing} trades in whole ${label === "Contracts" ? "contracts" : "shares"}`;
|
|
42
|
+
return `${label} ${ambiguousNumberMessage(typed, reading, { because })}.`;
|
|
43
|
+
}
|
|
@@ -53,9 +53,34 @@ export function resetFredSeriesPersistence(): void {
|
|
|
53
53
|
cache.reset();
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
/**
|
|
56
|
+
/** Who publishes a series Gloom Cloud serves in FRED's shape that FRED does not carry, and its page. */
|
|
57
|
+
interface SeriesNotOnFred {
|
|
58
|
+
/** Who compiles the figures (TurkStat). */
|
|
59
|
+
publisher: string;
|
|
60
|
+
/** Whose publication they are read from (the CBRT). */
|
|
61
|
+
via: string;
|
|
62
|
+
url: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const TURKISH_CPI: SeriesNotOnFred = {
|
|
66
|
+
publisher: "TurkStat",
|
|
67
|
+
via: "the CBRT",
|
|
68
|
+
url: "https://www.tcmb.gov.tr/wps/wcm/connect/EN/TCMB+EN/Main+Menu/Statistics/Inflation+Data/Consumer+Prices",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const NOT_ON_FRED: Readonly<Record<string, SeriesNotOnFred>> = {
|
|
72
|
+
TURCPIYOY: TURKISH_CPI,
|
|
73
|
+
TURCPIMOM: TURKISH_CPI,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** The publisher of a served series FRED does not carry; null for FRED's own. */
|
|
77
|
+
export function seriesNotOnFred(seriesId: string): SeriesNotOnFred | null {
|
|
78
|
+
return NOT_ON_FRED[seriesId.trim().toUpperCase()] ?? null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The series' page on the FRED site, where its notes and release schedule live, or its publisher's page when FRED does not carry it. */
|
|
57
82
|
export function fredSeriesUrl(seriesId: string): string {
|
|
58
|
-
return `https://fred.stlouisfed.org/series/${seriesId}`;
|
|
83
|
+
return seriesNotOnFred(seriesId)?.url ?? `https://fred.stlouisfed.org/series/${seriesId}`;
|
|
59
84
|
}
|
|
60
85
|
|
|
61
86
|
function cacheKey(request: FredSeriesRequest): string {
|
|
@@ -264,6 +264,7 @@ function sanitizePosition(position: TickerPosition): TickerPosition {
|
|
|
264
264
|
avgCost: position.avgCost,
|
|
265
265
|
currency: position.currency,
|
|
266
266
|
dateAcquired: position.dateAcquired,
|
|
267
|
+
...(position.costFxRate != null && position.costFxCurrency ? { costFxRate: position.costFxRate, costFxCurrency: position.costFxCurrency } : {}),
|
|
267
268
|
broker: position.broker,
|
|
268
269
|
side: position.side,
|
|
269
270
|
marketValue: position.marketValue,
|
package/src/team/collections.ts
CHANGED
|
@@ -174,6 +174,8 @@ export function applyMembership(
|
|
|
174
174
|
currency: position?.currency ?? currency,
|
|
175
175
|
broker: "manual",
|
|
176
176
|
...(!flipped && position?.dateAcquired ? { dateAcquired: position.dateAcquired } : {}),
|
|
177
|
+
// So does the rate its cost was bought at, with its cost.
|
|
178
|
+
...(!flipped && position?.costFxRate != null && position.costFxCurrency ? { costFxRate: position.costFxRate, costFxCurrency: position.costFxCurrency } : {}),
|
|
177
179
|
// A future's contract size stays with the holding when the team changes its count.
|
|
178
180
|
...(position?.multiplier ? { multiplier: position.multiplier } : {}),
|
|
179
181
|
});
|
package/src/tickers/metadata.ts
CHANGED
|
@@ -9,6 +9,15 @@ const DEFAULT_TICKER_METADATA: Omit<TickerMetadata, "ticker" | "exchange" | "cur
|
|
|
9
9
|
tags: [],
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
/** A lot's cost FX rate, kept only whole: a positive rate and the currency it converts into. */
|
|
13
|
+
function hydrateCostFx(raw: Record<string, unknown>): Pick<TickerPosition, "costFxRate" | "costFxCurrency"> {
|
|
14
|
+
const rate = raw.costFxRate ?? raw.cost_fx_rate;
|
|
15
|
+
const currency = raw.costFxCurrency ?? raw.cost_fx_currency;
|
|
16
|
+
return typeof rate === "number" && Number.isFinite(rate) && rate > 0 && typeof currency === "string" && currency.trim()
|
|
17
|
+
? { costFxRate: rate, costFxCurrency: currency.trim() }
|
|
18
|
+
: {};
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
function hydratePosition(raw: Record<string, unknown>): TickerPosition {
|
|
13
22
|
const avgCost = Object.hasOwn(raw, "avgCost") ? raw.avgCost : raw.avg_cost;
|
|
14
23
|
return {
|
|
@@ -18,6 +27,7 @@ function hydratePosition(raw: Record<string, unknown>): TickerPosition {
|
|
|
18
27
|
priceBasis: raw.priceBasis === "per-unit" || raw.priceBasis === "percent-of-par" ? raw.priceBasis : undefined,
|
|
19
28
|
currency: (raw.currency as string) ?? undefined,
|
|
20
29
|
dateAcquired: (raw.dateAcquired ?? raw.date_acquired) as string | undefined,
|
|
30
|
+
...hydrateCostFx(raw),
|
|
21
31
|
broker: (raw.broker as string) ?? "manual",
|
|
22
32
|
side: (raw.side as "long" | "short") ?? undefined,
|
|
23
33
|
marketValue: (raw.marketValue ?? raw.market_value) as number | undefined,
|
package/src/types/ticker.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface TickerPosition {
|
|
|
8
8
|
priceBasis?: PriceBasis;
|
|
9
9
|
currency?: string;
|
|
10
10
|
dateAcquired?: string;
|
|
11
|
+
/**
|
|
12
|
+
* What one unit of the lot's `currency` cost in `costFxCurrency` when it was
|
|
13
|
+
* bought: entered with the position, or the FX close of its purchase date.
|
|
14
|
+
* Valued in that currency, the lot's cost converts at this rate rather than
|
|
15
|
+
* today's, so its P&L carries the currency's move since.
|
|
16
|
+
*/
|
|
17
|
+
costFxRate?: number;
|
|
18
|
+
/** The currency `costFxRate` converts the cost into: the portfolio's when it was entered. */
|
|
19
|
+
costFxCurrency?: string;
|
|
11
20
|
broker: string; // "manual" | future broker plugin IDs
|
|
12
21
|
side?: "long" | "short";
|
|
13
22
|
marketValue?: number;
|
|
@@ -64,6 +64,15 @@ export interface CurrencyUnitInfo {
|
|
|
64
64
|
divisor: number;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* The currency money held in a code is counted in, and how many of the code
|
|
69
|
+
* make one of it: resolveCurrencyUnit's, and US cents as a hundredth of a
|
|
70
|
+
* dollar, which a lot's cost in USX is.
|
|
71
|
+
*/
|
|
72
|
+
export function heldCurrencyUnit(currency?: string | null): CurrencyUnitInfo {
|
|
73
|
+
return currency?.trim().toUpperCase() === US_CENTS ? { currency: "USD", divisor: 100 } : resolveCurrencyUnit(currency);
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
interface ExchangeSubUnitOptions {
|
|
68
77
|
allowDefaultCurrency?: boolean;
|
|
69
78
|
allowMissingCurrency?: boolean;
|