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