@adaptic/utils 0.0.1011 → 0.0.1013

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/index.cjs +210 -39
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.mjs +210 -39
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/test.js +8 -0
  6. package/dist/test.js.map +1 -1
  7. package/dist/types/__tests__/metrics-calcs-direction.test.d.ts +2 -0
  8. package/dist/types/__tests__/metrics-calcs-direction.test.d.ts.map +1 -0
  9. package/dist/types/__tests__/protective-order-sides.test.d.ts +2 -0
  10. package/dist/types/__tests__/protective-order-sides.test.d.ts.map +1 -0
  11. package/dist/types/__tests__/trailing-stops-portfolio.test.d.ts +2 -0
  12. package/dist/types/__tests__/trailing-stops-portfolio.test.d.ts.map +1 -0
  13. package/dist/types/alpaca/index.d.ts +1 -1
  14. package/dist/types/alpaca/index.d.ts.map +1 -1
  15. package/dist/types/alpaca/trading/bracket-orders.d.ts +25 -3
  16. package/dist/types/alpaca/trading/bracket-orders.d.ts.map +1 -1
  17. package/dist/types/alpaca/trading/oto-orders.d.ts +8 -2
  18. package/dist/types/alpaca/trading/oto-orders.d.ts.map +1 -1
  19. package/dist/types/alpaca/trading/trailing-stops.d.ts +6 -3
  20. package/dist/types/alpaca/trading/trailing-stops.d.ts.map +1 -1
  21. package/dist/types/asset-allocation-algorithm.d.ts.map +1 -1
  22. package/dist/types/index.d.ts +2 -2
  23. package/dist/types/index.d.ts.map +1 -1
  24. package/dist/types/metrics-calcs.d.ts +6 -0
  25. package/dist/types/metrics-calcs.d.ts.map +1 -1
  26. package/dist/types/technical-analysis.d.ts +1 -1
  27. package/dist/types/technical-analysis.d.ts.map +1 -1
  28. package/dist/types/trading-policy/defaults/default-trading-policy.d.ts.map +1 -1
  29. package/dist/types/types/metrics-types.d.ts +8 -1
  30. package/dist/types/types/metrics-types.d.ts.map +1 -1
  31. package/dist/types/types/ta-types.d.ts +10 -1
  32. package/dist/types/types/ta-types.d.ts.map +1 -1
  33. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -11413,7 +11413,13 @@ async function calculateMaxDrawdown$1(tradeBars, isShort) {
11413
11413
  peak = positionAwareEquity[i];
11414
11414
  }
11415
11415
  else {
11416
- const drawdown = peak <= 0 ? 0 : (peak - positionAwareEquity[i]) / Math.abs(peak);
11416
+ // The short branch negates equity, so its peak is legitimately negative.
11417
+ // Scale the decline by the peak's magnitude — a sign test on the peak
11418
+ // would discard every drawdown on one side of the book.
11419
+ const denominator = Math.abs(peak);
11420
+ const drawdown = denominator === 0
11421
+ ? 0
11422
+ : (peak - positionAwareEquity[i]) / denominator;
11417
11423
  if (drawdown > maxDrawdown) {
11418
11424
  maxDrawdown = drawdown;
11419
11425
  }
@@ -11426,17 +11432,71 @@ async function calculateExpenseRatio$1(trade) {
11426
11432
  const totalFees = await computeTotalFees(trade);
11427
11433
  return totalFees ? `${totalFees.toFixed(2)}%` : "N/A";
11428
11434
  }
11435
+ /**
11436
+ * Resolves whether a trade is short from its primary action.
11437
+ *
11438
+ * Only an outright BUY or SELL fixes whether the position's P&L runs with or
11439
+ * against the price series. Option legs, exercises, cancels, adjustments and
11440
+ * hedges do not, and `trade.actions` itself is curated by backend-legacy
11441
+ * selection-set directives, so its absence is routine. Every one of those
11442
+ * cases leaves the direction genuinely unknown, and unknown is returned as
11443
+ * such — inferring a side would silently invert every direction-aware metric
11444
+ * computed from it.
11445
+ *
11446
+ * @param trade - Trade whose direction is being resolved
11447
+ * @returns `true` for a short, `false` for a long, `null` when unresolvable
11448
+ */
11449
+ function resolveIsShort(trade) {
11450
+ const primaryAction = trade.actions?.find((action) => action.primary);
11451
+ if (!primaryAction) {
11452
+ getLogger().warn(`Trade ${trade.id} has no primary action; position direction is unresolved.`);
11453
+ return null;
11454
+ }
11455
+ switch (primaryAction.type) {
11456
+ case "SELL":
11457
+ return true;
11458
+ case "BUY":
11459
+ return false;
11460
+ default:
11461
+ getLogger().warn(`Trade ${trade.id} primary action type "${primaryAction.type}" does not determine a long/short direction.`);
11462
+ return null;
11463
+ }
11464
+ }
11429
11465
  // Main function to fetch and calculate all trade metrics for one trade object
11430
11466
  async function fetchTradeMetrics(trade, tradeBars, benchmarkBars) {
11431
- const isShort = trade.actions?.find((a) => a.primary)?.type === "SELL" ? true : false;
11467
+ const isShort = resolveIsShort(trade);
11468
+ // The Sharpe ratio and the expense ratio do not invert on direction, so they
11469
+ // are started immediately and stay concurrent with everything below.
11470
+ const riskAdjustedReturnPromise = calculateRiskAdjustedReturn$1(tradeBars);
11471
+ const expenseRatioPromise = calculateExpenseRatio$1(trade);
11472
+ if (isShort === null) {
11473
+ // Every other metric inverts on direction. With the direction unknown
11474
+ // there is no value to report — only a sign-ambiguous one — so they are
11475
+ // reported as unavailable rather than resolved by assumption.
11476
+ const [riskAdjustedReturn, expenseRatio] = await Promise.all([
11477
+ riskAdjustedReturnPromise,
11478
+ expenseRatioPromise,
11479
+ ]);
11480
+ return {
11481
+ totalReturnYTD: "N/A",
11482
+ alpha: "N/A",
11483
+ beta: "N/A",
11484
+ alphaAnnualized: "N/A",
11485
+ informationRatio: "N/A",
11486
+ riskAdjustedReturn,
11487
+ expenseRatio,
11488
+ maxDrawdown: "N/A",
11489
+ side: "N/A",
11490
+ };
11491
+ }
11432
11492
  // Calculate metrics concurrently
11433
- const [totalReturnYTD, { alpha, beta, alphaAnnualized }, informationRatio, riskAdjustedReturn, expenseRatio, maxDrawdown,] = await Promise.all([
11493
+ const [totalReturnYTD, { alpha, beta, alphaAnnualized }, informationRatio, maxDrawdown, riskAdjustedReturn, expenseRatio,] = await Promise.all([
11434
11494
  calculateProfitLoss(tradeBars, isShort),
11435
11495
  calculateAlphaAndBeta$1(tradeBars, benchmarkBars, isShort),
11436
11496
  calculateInformationRatio$1(tradeBars, benchmarkBars, isShort),
11437
- calculateRiskAdjustedReturn$1(tradeBars),
11438
- calculateExpenseRatio$1(trade),
11439
11497
  calculateMaxDrawdown$1(tradeBars, isShort),
11498
+ riskAdjustedReturnPromise,
11499
+ expenseRatioPromise,
11440
11500
  ]);
11441
11501
  return {
11442
11502
  totalReturnYTD,
@@ -12902,6 +12962,43 @@ function calculateEMA(priceData, { period = 20, period2 = 9 } = {}) {
12902
12962
  }
12903
12963
  return result;
12904
12964
  }
12965
+ /**
12966
+ * Locates a window's swing extremes and derives the direction of its most
12967
+ * recent leg from the order in which those extremes print.
12968
+ *
12969
+ * A Fibonacci construction is anchored to the latest leg: an up-leg runs swing
12970
+ * low to swing high, a down-leg swing high to swing low. Whichever extreme
12971
+ * prints last therefore identifies the leg, which makes the direction a
12972
+ * measurement of the window rather than a caller's assumption. When both
12973
+ * extremes land on the same bar the window contains no leg and the direction
12974
+ * is genuinely indeterminate.
12975
+ *
12976
+ * @param window - The lookback slice to analyse.
12977
+ * @returns The window's swing extremes and derived leg direction.
12978
+ */
12979
+ function analyzeSwingWindow(window) {
12980
+ let swingHigh = -Infinity;
12981
+ let swingLow = Infinity;
12982
+ let highIndex = -1;
12983
+ let lowIndex = -1;
12984
+ // `>=` / `<=` keep the most recent occurrence of each extreme, which is the
12985
+ // one the current leg is measured from.
12986
+ for (let i = 0; i < window.length; i++) {
12987
+ if (window[i].high >= swingHigh) {
12988
+ swingHigh = window[i].high;
12989
+ highIndex = i;
12990
+ }
12991
+ if (window[i].low <= swingLow) {
12992
+ swingLow = window[i].low;
12993
+ lowIndex = i;
12994
+ }
12995
+ }
12996
+ return {
12997
+ swingHigh,
12998
+ swingLow,
12999
+ trend: highIndex === lowIndex ? null : highIndex > lowIndex ? "uptrend" : "downtrend",
13000
+ };
13001
+ }
12905
13002
  /**
12906
13003
  * Calculates Fibonacci retracement and extension levels based on price data.
12907
13004
  * Fibonacci levels are used to identify potential support and resistance levels.
@@ -12911,22 +13008,28 @@ function calculateEMA(priceData, { period = 20, period2 = 9 } = {}) {
12911
13008
  * @param params.lookbackPeriod - The number of periods to look back for swing high/low (default is 20).
12912
13009
  * @param params.retracementLevels - An array of retracement levels to calculate (default is [0.236, 0.382, 0.5, 0.618, 0.786]).
12913
13010
  * @param params.extensionLevels - An array of extension levels to calculate (default is [1.272, 1.618, 2.618]).
12914
- * @param params.reverseDirection - A boolean indicating if the trend is reversed (default is false).
13011
+ * @param params.reverseDirection - Forces the leg direction: `true` for a downtrend, `false` for an uptrend. Omit it to derive the direction per bar from the swing window.
12915
13012
  * @returns An array of FibonacciData objects containing the calculated levels.
12916
13013
  */
12917
- function calculateFibonacciLevels(priceData, { lookbackPeriod = 20, retracementLevels = [0.236, 0.382, 0.5, 0.618, 0.786], extensionLevels = [1.272, 1.618, 2.618], reverseDirection = false, } = {}) {
13014
+ function calculateFibonacciLevels(priceData, { lookbackPeriod = 20, retracementLevels = [0.236, 0.382, 0.5, 0.618, 0.786], extensionLevels = [1.272, 1.618, 2.618], reverseDirection, } = {}) {
12918
13015
  const result = [];
12919
13016
  for (let i = 0; i < priceData.length; i++) {
12920
13017
  const periodSlice = priceData.slice(Math.max(0, i - lookbackPeriod + 1), i + 1);
12921
- const swingHigh = Math.max(...periodSlice.map((d) => d.high));
12922
- const swingLow = Math.min(...periodSlice.map((d) => d.low));
13018
+ const { swingHigh, swingLow, trend: derivedTrend } = analyzeSwingWindow(periodSlice);
12923
13019
  const priceRange = swingHigh - swingLow;
12924
- const trend = reverseDirection ? "downtrend" : "uptrend";
13020
+ // An explicit `reverseDirection` is the caller stating the leg it is
13021
+ // measuring; absent that, the leg is read off the window itself.
13022
+ const trend = reverseDirection === undefined
13023
+ ? derivedTrend
13024
+ : reverseDirection
13025
+ ? "downtrend"
13026
+ : "uptrend";
12925
13027
  const levels = [];
12926
- if (priceRange > 0) {
13028
+ if (priceRange > 0 && trend !== null) {
13029
+ const isDowntrend = trend === "downtrend";
12927
13030
  // Calculate retracement levels
12928
13031
  retracementLevels.forEach((level) => {
12929
- const price = reverseDirection
13032
+ const price = isDowntrend
12930
13033
  ? swingLow + priceRange * level
12931
13034
  : swingHigh - priceRange * level;
12932
13035
  levels.push({
@@ -12935,10 +13038,13 @@ function calculateFibonacciLevels(priceData, { lookbackPeriod = 20, retracementL
12935
13038
  type: "retracement",
12936
13039
  });
12937
13040
  });
12938
- // Calculate extension levels
13041
+ // Calculate extension levels — each is projected beyond the leg's
13042
+ // terminal extreme: past the swing low for a down-leg, past the swing
13043
+ // high for an up-leg. Anchoring both to the same extreme would place one
13044
+ // side's targets a full swing range away from where the leg is running.
12939
13045
  extensionLevels.forEach((level) => {
12940
- const price = reverseDirection
12941
- ? swingHigh - priceRange * (level - 1) // For downtrend
13046
+ const price = isDowntrend
13047
+ ? swingLow - priceRange * (level - 1) // For downtrend
12942
13048
  : swingHigh + priceRange * (level - 1); // For uptrend
12943
13049
  levels.push({
12944
13050
  level,
@@ -12947,7 +13053,10 @@ function calculateFibonacciLevels(priceData, { lookbackPeriod = 20, retracementL
12947
13053
  });
12948
13054
  });
12949
13055
  // Sort levels by price
12950
- levels.sort((a, b) => reverseDirection ? b.price - a.price : a.price - b.price);
13056
+ levels.sort((a, b) => isDowntrend ? b.price - a.price : a.price - b.price);
13057
+ }
13058
+ else if (trend === null) {
13059
+ logIfDebug(`Swing high and low fall on the same bar on date ${priceData[i].date}; trend is indeterminate and no levels calculated.`);
12951
13060
  }
12952
13061
  else {
12953
13062
  logIfDebug(`Price range is zero on date ${priceData[i].date}; no levels calculated.`);
@@ -51969,7 +52078,8 @@ async function createBracketOrder(executor, params) {
51969
52078
  *
51970
52079
  * @example
51971
52080
  * ```typescript
51972
- * // Add protection to an existing long position
52081
+ * // Add protection to an existing long position (sell to close):
52082
+ * // take profit above, stop below.
51973
52083
  * const result = await createProtectiveBracket(
51974
52084
  * executor,
51975
52085
  * {
@@ -51982,6 +52092,23 @@ async function createBracketOrder(executor, params) {
51982
52092
  * }
51983
52093
  * );
51984
52094
  * ```
52095
+ *
52096
+ * @example
52097
+ * ```typescript
52098
+ * // Add protection to an existing short position (buy to close):
52099
+ * // take profit below, stop above.
52100
+ * const result = await createProtectiveBracket(
52101
+ * executor,
52102
+ * {
52103
+ * symbol: 'TSLA',
52104
+ * qty: 50,
52105
+ * side: 'buy',
52106
+ * takeProfit: { limitPrice: 200.00 },
52107
+ * stopLoss: { stopPrice: 260.00 },
52108
+ * timeInForce: 'gtc',
52109
+ * }
52110
+ * );
52111
+ * ```
51985
52112
  */
51986
52113
  async function createProtectiveBracket(executor, params) {
51987
52114
  log$j(`Creating protective bracket for ${params.symbol}: ${params.qty} shares`, { type: "info" });
@@ -51994,15 +52121,28 @@ async function createProtectiveBracket(executor, params) {
51994
52121
  if (!params.qty || params.qty <= 0) {
51995
52122
  throw new Error("Quantity must be a positive number");
51996
52123
  }
52124
+ // The closing side determines which of the two exit prices is the profit
52125
+ // target, so it must be stated rather than inferred.
52126
+ if (params.side !== "buy" && params.side !== "sell") {
52127
+ throw new Error("Protective bracket requires a side of 'buy' or 'sell' matching the position being closed");
52128
+ }
51997
52129
  if (!params.takeProfit?.limitPrice || params.takeProfit.limitPrice <= 0) {
51998
52130
  throw new Error("Take profit limit price is required and must be positive");
51999
52131
  }
52000
52132
  if (!params.stopLoss?.stopPrice || params.stopLoss.stopPrice <= 0) {
52001
52133
  throw new Error("Stop loss stop price is required and must be positive");
52002
52134
  }
52003
- // For a protective sell bracket, take profit should be higher than stop loss
52004
- if (params.takeProfit.limitPrice <= params.stopLoss.stopPrice) {
52005
- log$j("Warning: Take profit price should be higher than stop loss price for protective sell bracket", { type: "warn" });
52135
+ // The take profit must sit on the profitable side of the position and the
52136
+ // stop on the losing side. Which price is the higher one therefore depends
52137
+ // on the closing side: selling to close a long takes profit above and stops
52138
+ // below; buying to close a short is the exact mirror.
52139
+ if (params.side === "sell") {
52140
+ if (params.takeProfit.limitPrice <= params.stopLoss.stopPrice) {
52141
+ log$j("Warning: Take profit price should be higher than stop loss price for protective sell bracket", { type: "warn" });
52142
+ }
52143
+ }
52144
+ else if (params.takeProfit.limitPrice >= params.stopLoss.stopPrice) {
52145
+ log$j("Warning: Take profit price should be lower than stop loss price for protective buy bracket", { type: "warn" });
52006
52146
  }
52007
52147
  try {
52008
52148
  // Build the OCO order parameters
@@ -53777,10 +53917,13 @@ async function cancelTrailingStop(client, orderId) {
53777
53917
  }
53778
53918
  }
53779
53919
  /**
53780
- * Create trailing stops for all positions in a portfolio
53920
+ * Create trailing stops for every position in a portfolio
53781
53921
  *
53782
- * This function creates trailing stop orders for all long positions in the portfolio,
53783
- * which is useful for applying blanket downside protection. Short positions are skipped.
53922
+ * Applies blanket adverse-move protection across the book. The protective side
53923
+ * is derived per position from the signed quantity reported by the broker — a
53924
+ * long is protected by a trailing sell, a short by a trailing buy — so a
53925
+ * position is never left unprotected because of the direction it happens to
53926
+ * hold.
53784
53927
  *
53785
53928
  * @param client - AlpacaClient instance
53786
53929
  * @param params - Configuration for portfolio-wide trailing stops
@@ -53805,8 +53948,11 @@ async function createPortfolioTrailingStops(client, params) {
53805
53948
  if (params.trailPercent <= 0) {
53806
53949
  throw new Error("trailPercent must be greater than 0");
53807
53950
  }
53808
- if (params.trailPercent > 100) {
53809
- throw new Error("trailPercent cannot exceed 100");
53951
+ // Reject against the broker's real ceiling up front. A looser outer bound
53952
+ // lets an out-of-range value reach the per-position loop, where every single
53953
+ // submission is rejected and the book silently ends up unprotected.
53954
+ if (params.trailPercent > ALPACA_MAX_TRAIL_PERCENT) {
53955
+ throw new Error(`trailPercent cannot exceed ${ALPACA_MAX_TRAIL_PERCENT} (Alpaca API limit)`);
53810
53956
  }
53811
53957
  const sdk = client.getSDK();
53812
53958
  const results = new Map();
@@ -53829,19 +53975,21 @@ async function createPortfolioTrailingStops(client, params) {
53829
53975
  log$g(`Skipping ${symbol} (excluded)`, { type: "debug" });
53830
53976
  continue;
53831
53977
  }
53832
- // Only create trailing stops for long positions
53978
+ // Derive the protective side from the broker's signed quantity: a long
53979
+ // (qty > 0) is closed by selling, a short (qty < 0) by buying. Direction
53980
+ // is read from the position, never assumed — a stop on the wrong side
53981
+ // doubles the exposure it was meant to cap.
53833
53982
  const qty = parseFloat(position.qty);
53834
- if (qty <= 0) {
53835
- log$g(`Skipping ${symbol} (not a long position, qty: ${qty})`, {
53836
- type: "debug",
53837
- });
53983
+ if (!Number.isFinite(qty) || qty === 0) {
53984
+ log$g(`Skipping ${symbol}: position qty "${position.qty}" is not a usable non-zero number`, { type: "warn" });
53838
53985
  continue;
53839
53986
  }
53987
+ const side = qty > 0 ? "sell" : "buy";
53840
53988
  try {
53841
53989
  const order = await createTrailingStop(client, {
53842
53990
  symbol,
53843
53991
  qty: Math.abs(qty),
53844
- side: "sell",
53992
+ side,
53845
53993
  trailPercent: params.trailPercent,
53846
53994
  timeInForce: params.timeInForce || "gtc",
53847
53995
  });
@@ -54779,13 +54927,25 @@ async function shortWithStopLoss(client, symbol, qty, entryPrice, stopLossPrice)
54779
54927
  * @param qty - Number of shares
54780
54928
  * @param entryPrice - Limit price for entry (null for market)
54781
54929
  * @param stopLossPercent - Stop loss percentage (e.g., 5 for 5%)
54782
- * @param side - Order side ('buy' or 'sell')
54930
+ * @param side - Order side ('buy' or 'sell'). Required: the entry direction is
54931
+ * the caller's decision, and a default would open a position in a direction
54932
+ * nobody chose.
54783
54933
  *
54784
54934
  * @example
54785
54935
  * // Buy AAPL at $150 with 3% stop loss (stop at $145.50)
54786
54936
  * const result = await entryWithPercentStopLoss(client, 'AAPL', 100, 150.00, 3, 'buy');
54937
+ *
54938
+ * @example
54939
+ * // Short GOOGL at $140 with 3% stop loss (stop at $144.20)
54940
+ * const result = await entryWithPercentStopLoss(client, 'GOOGL', 10, 140.00, 3, 'sell');
54787
54941
  */
54788
- async function entryWithPercentStopLoss(client, symbol, qty, entryPrice, stopLossPercent, side = "buy") {
54942
+ async function entryWithPercentStopLoss(client, symbol, qty, entryPrice, stopLossPercent, side) {
54943
+ // Guard the direction at runtime as well as in the signature: every price
54944
+ // below is computed off `side`, so an unsupplied one would silently place
54945
+ // the stop on the wrong side of the entry.
54946
+ if (side !== "buy" && side !== "sell") {
54947
+ throw new Error("entryWithPercentStopLoss requires an explicit side of 'buy' or 'sell'; the entry direction cannot be inferred");
54948
+ }
54789
54949
  if (stopLossPercent <= 0 || stopLossPercent >= 100) {
54790
54950
  throw new Error("stopLossPercent must be between 0 and 100");
54791
54951
  }
@@ -64849,6 +65009,15 @@ class AssetAllocationEngine {
64849
65009
  * Assess current market condition
64850
65010
  */
64851
65011
  assessMarketCondition(metrics) {
65012
+ // Crisis detection runs first: it is the strictly more severe reading and
65013
+ // its volatility threshold sits above the high-volatility one, so testing
65014
+ // volatility first would classify every crisis-level VIX as merely high
65015
+ // and never reach this branch at all.
65016
+ if (metrics.volatilityIndex > 40 ||
65017
+ metrics.sentimentScore < 20 ||
65018
+ metrics.creditSpread > 500) {
65019
+ return "CRISIS";
65020
+ }
64852
65021
  // High volatility check
64853
65022
  if (metrics.volatilityIndex > 30) {
64854
65023
  return "HIGH_VOLATILITY";
@@ -64857,12 +65026,6 @@ class AssetAllocationEngine {
64857
65026
  if (metrics.volatilityIndex < 12) {
64858
65027
  return "LOW_VOLATILITY";
64859
65028
  }
64860
- // Crisis detection
64861
- if (metrics.volatilityIndex > 40 ||
64862
- metrics.sentimentScore < 20 ||
64863
- metrics.creditSpread > 500) {
64864
- return "CRISIS";
64865
- }
64866
65029
  // Bull market
64867
65030
  if (metrics.trendDirection === "UP" &&
64868
65031
  metrics.marketStrength > 60 &&
@@ -71158,6 +71321,14 @@ const DEFAULT_TRADING_POLICY = EffectiveTradingPolicySchema.parse({
71158
71321
  optionsEnabled: true,
71159
71322
  futuresEnabled: true,
71160
71323
  forexEnabled: true,
71324
+ // Shorting and margin are capability opt-ins, not a directional stance.
71325
+ // Both require a margin agreement and locate/borrow availability the
71326
+ // package cannot verify, so an account that has not asserted the
71327
+ // capability defaults to the one it is known to have. This is a statement
71328
+ // about account permissions, never a preference for long over short — the
71329
+ // side a strategy takes is derived from live data once the capability is
71330
+ // enabled. Resolve these from the broker account's actual margin and
71331
+ // shorting entitlements wherever those are available.
71161
71332
  shortingEnabled: false,
71162
71333
  marginEnabled: false,
71163
71334
  fractionalSharesEnabled: true,