@adaptic/utils 0.0.370 → 0.0.374

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/dist/index.cjs CHANGED
@@ -1408,11 +1408,16 @@ async function getConfiguration(account) {
1408
1408
  throw new Error('Failed to get Alpaca Account from @adaptic/backend-legacy.');
1409
1409
  }
1410
1410
  const dataFromAlpaca = (await alpacaResponse.json());
1411
- // Fetch allocation data if it exists
1412
- const allocationData = freshAlpacaAccount.allocation || {
1411
+ // Fetch allocation data with expanded asset types and defaults
1412
+ // Type assertion to handle fields that may not exist in backend-legacy yet
1413
+ const accountWithAllocation = freshAlpacaAccount;
1414
+ const allocationData = accountWithAllocation.allocation || {
1413
1415
  stocks: 70,
1414
- crypto: 30,
1415
- etfs: 10
1416
+ options: 0,
1417
+ futures: 0,
1418
+ etfs: 10,
1419
+ forex: 0,
1420
+ crypto: 20
1416
1421
  };
1417
1422
  // Merge DB fields onto the returned object
1418
1423
  // (These are not part of Alpaca's config, but are stored in our DB)
@@ -1427,6 +1432,7 @@ async function getConfiguration(account) {
1427
1432
  cryptoTradingEnabled: freshAlpacaAccount.cryptoTradingEnabled ?? false,
1428
1433
  cryptoTradingPairs: freshAlpacaAccount.cryptoTradingPairs ?? [],
1429
1434
  cryptoTradeAllocationPct: freshAlpacaAccount.cryptoTradeAllocationPct ?? 5.0,
1435
+ autoAllocation: accountWithAllocation.autoAllocation ?? false,
1430
1436
  allocation: allocationData,
1431
1437
  enablePortfolioTrailingStop: freshAlpacaAccount.enablePortfolioTrailingStop,
1432
1438
  portfolioTrailPercent: freshAlpacaAccount.portfolioTrailPercent,
@@ -1476,6 +1482,7 @@ async function updateConfiguration(user, account, updatedConfig) {
1476
1482
  delete configForAlpaca.cryptoTradingEnabled;
1477
1483
  delete configForAlpaca.cryptoTradingPairs;
1478
1484
  delete configForAlpaca.cryptoTradeAllocationPct;
1485
+ delete configForAlpaca.autoAllocation;
1479
1486
  delete configForAlpaca.allocation;
1480
1487
  delete configForAlpaca.enablePortfolioTrailingStop;
1481
1488
  delete configForAlpaca.portfolioTrailPercent;
@@ -1504,6 +1511,16 @@ async function updateConfiguration(user, account, updatedConfig) {
1504
1511
  // Check if we need to update allocation
1505
1512
  let allocUpdatePromise = Promise.resolve(null);
1506
1513
  if (updatedConfig.allocation) {
1514
+ // Validate allocation percentages sum to 100%
1515
+ const totalAllocation = (updatedConfig.allocation.stocks ?? 0) +
1516
+ (updatedConfig.allocation.options ?? 0) +
1517
+ (updatedConfig.allocation.futures ?? 0) +
1518
+ (updatedConfig.allocation.etfs ?? 0) +
1519
+ (updatedConfig.allocation.forex ?? 0) +
1520
+ (updatedConfig.allocation.crypto ?? 0);
1521
+ if (Math.abs(totalAllocation - 100) > 0.01) {
1522
+ throw new Error(`Allocation percentages must sum to 100%. Current total: ${totalAllocation}%`);
1523
+ }
1507
1524
  // If account already has an allocation, update it, otherwise create one
1508
1525
  if (account.allocation) {
1509
1526
  allocUpdatePromise = adaptic$1.allocation.update({
@@ -1512,16 +1529,22 @@ async function updateConfiguration(user, account, updatedConfig) {
1512
1529
  id: account.id,
1513
1530
  },
1514
1531
  alpacaAccountId: account.id,
1515
- stocks: updatedConfig.allocation.stocks,
1516
- crypto: updatedConfig.allocation.crypto,
1517
- etfs: updatedConfig.allocation.etfs,
1532
+ stocks: updatedConfig.allocation.stocks ?? 0,
1533
+ options: updatedConfig.allocation.options ?? 0,
1534
+ futures: updatedConfig.allocation.futures ?? 0,
1535
+ etfs: updatedConfig.allocation.etfs ?? 0,
1536
+ forex: updatedConfig.allocation.forex ?? 0,
1537
+ crypto: updatedConfig.allocation.crypto ?? 0,
1518
1538
  }, client);
1519
1539
  }
1520
1540
  else {
1521
1541
  allocUpdatePromise = adaptic$1.allocation.create({
1522
- stocks: updatedConfig.allocation.stocks,
1523
- crypto: updatedConfig.allocation.crypto,
1524
- etfs: updatedConfig.allocation.etfs,
1542
+ stocks: updatedConfig.allocation.stocks ?? 0,
1543
+ options: updatedConfig.allocation.options ?? 0,
1544
+ futures: updatedConfig.allocation.futures ?? 0,
1545
+ etfs: updatedConfig.allocation.etfs ?? 0,
1546
+ forex: updatedConfig.allocation.forex ?? 0,
1547
+ crypto: updatedConfig.allocation.crypto ?? 0,
1525
1548
  alpacaAccount: {
1526
1549
  id: account.id,
1527
1550
  },
@@ -1530,6 +1553,7 @@ async function updateConfiguration(user, account, updatedConfig) {
1530
1553
  }
1531
1554
  }
1532
1555
  // Meanwhile, update the DB-based fields in @adaptic/backend-legacy
1556
+ // Use type assertion for fields that may not exist in backend-legacy yet
1533
1557
  const adapticUpdatePromise = adaptic$1.alpacaAccount.update({
1534
1558
  id: account.id,
1535
1559
  user: {
@@ -1546,6 +1570,7 @@ async function updateConfiguration(user, account, updatedConfig) {
1546
1570
  cryptoTradingEnabled: updatedConfig.cryptoTradingEnabled,
1547
1571
  cryptoTradingPairs: updatedConfig.cryptoTradingPairs,
1548
1572
  cryptoTradeAllocationPct: updatedConfig.cryptoTradeAllocationPct,
1573
+ autoAllocation: updatedConfig.autoAllocation,
1549
1574
  enablePortfolioTrailingStop: updatedConfig.enablePortfolioTrailingStop,
1550
1575
  portfolioTrailPercent: updatedConfig.portfolioTrailPercent,
1551
1576
  portfolioProfitThresholdPercent: updatedConfig.portfolioProfitThresholdPercent,
@@ -1563,6 +1588,16 @@ async function updateConfiguration(user, account, updatedConfig) {
1563
1588
  adapticUpdatePromise,
1564
1589
  allocUpdatePromise
1565
1590
  ]);
1591
+ console.log('=== PROMISE.ALL RESULTS ===');
1592
+ console.log('updatedAllocation from Promise.all:', updatedAllocation);
1593
+ console.log('updatedAllocation fields:', {
1594
+ stocks: updatedAllocation?.stocks,
1595
+ options: updatedAllocation?.options,
1596
+ futures: updatedAllocation?.futures,
1597
+ etfs: updatedAllocation?.etfs,
1598
+ forex: updatedAllocation?.forex,
1599
+ crypto: updatedAllocation?.crypto,
1600
+ });
1566
1601
  if (!alpacaResponse.ok) {
1567
1602
  console.error('Failed to update account configuration at Alpaca:', alpacaResponse.statusText);
1568
1603
  throw new Error(`Failed to update account config at Alpaca: ${alpacaResponse.statusText}`);
@@ -1572,6 +1607,16 @@ async function updateConfiguration(user, account, updatedConfig) {
1572
1607
  throw new Error('Failed to update Alpaca Account in @adaptic/backend-legacy.');
1573
1608
  }
1574
1609
  // Merge final data from Alpaca + local DB fields
1610
+ // Type assertion for fields that may not exist in backend-legacy yet
1611
+ const updatedAccountWithAllocation = updatedAlpacaAccount;
1612
+ // FIX: Use the validated input allocation instead of mutation response
1613
+ // The mutation response may return stale/cached data, but we already validated
1614
+ // and sent the correct values to the database, so use updatedConfig.allocation
1615
+ const selectedAllocation = updatedConfig.allocation || updatedAllocation || updatedAccountWithAllocation.allocation;
1616
+ console.log('=== ALLOCATION DEBUG (will be removed after fix verified) ===');
1617
+ console.log('Using updatedConfig.allocation (validated input):', updatedConfig.allocation);
1618
+ console.log('Ignoring potentially stale updatedAllocation:', updatedAllocation);
1619
+ console.log('Final allocation:', selectedAllocation);
1575
1620
  const finalConfig = {
1576
1621
  ...alpacaData,
1577
1622
  marketOpen: updatedAlpacaAccount.marketOpen,
@@ -1583,7 +1628,8 @@ async function updateConfiguration(user, account, updatedConfig) {
1583
1628
  cryptoTradingEnabled: updatedAlpacaAccount.cryptoTradingEnabled,
1584
1629
  cryptoTradingPairs: updatedAlpacaAccount.cryptoTradingPairs,
1585
1630
  cryptoTradeAllocationPct: updatedAlpacaAccount.cryptoTradeAllocationPct,
1586
- allocation: updatedAllocation || updatedAlpacaAccount.allocation || updatedConfig.allocation,
1631
+ autoAllocation: updatedAccountWithAllocation.autoAllocation,
1632
+ allocation: selectedAllocation,
1587
1633
  enablePortfolioTrailingStop: updatedAlpacaAccount.enablePortfolioTrailingStop,
1588
1634
  portfolioTrailPercent: updatedAlpacaAccount.portfolioTrailPercent,
1589
1635
  portfolioProfitThresholdPercent: updatedAlpacaAccount.portfolioProfitThresholdPercent,
@@ -15604,6 +15650,1042 @@ const DEFAULT_CACHE_OPTIONS = {
15604
15650
  enableBackgroundRefresh: true,
15605
15651
  };
15606
15652
 
15653
+ /**
15654
+ * Intelligent Asset Allocation Algorithm
15655
+ *
15656
+ * Determines optimal asset allocation across multiple asset classes based on:
15657
+ * - User risk profile
15658
+ * - Market conditions
15659
+ * - Account size
15660
+ * - User preferences
15661
+ * - Modern Portfolio Theory principles
15662
+ * - Risk-adjusted returns
15663
+ * - Diversification optimization
15664
+ */
15665
+ /**
15666
+ * Asset Allocation Engine
15667
+ *
15668
+ * Implements sophisticated portfolio optimization using:
15669
+ * - Mean-variance optimization
15670
+ * - Risk parity approach
15671
+ * - Black-Litterman model influences
15672
+ * - Correlation-based diversification
15673
+ * - Dynamic risk adjustment
15674
+ */
15675
+ class AssetAllocationEngine {
15676
+ config;
15677
+ defaultConfig = {
15678
+ objective: 'MAX_SHARPE',
15679
+ riskFreeRate: 0.04, // 4% risk-free rate
15680
+ rebalancingThreshold: 5, // 5% drift threshold
15681
+ transactionCostModel: 'PERCENTAGE',
15682
+ timeHorizon: 5, // 5 year horizon
15683
+ allowLeverage: false,
15684
+ includeAlternatives: true
15685
+ };
15686
+ /**
15687
+ * Default risk profiles with typical asset class allocations
15688
+ */
15689
+ defaultRiskProfiles = new Map([
15690
+ [
15691
+ 'CONSERVATIVE',
15692
+ {
15693
+ profile: 'CONSERVATIVE',
15694
+ description: 'Capital preservation focused with minimal volatility',
15695
+ baseAllocations: new Map([
15696
+ ['EQUITIES', 0.20],
15697
+ ['OPTIONS', 0.05],
15698
+ ['FUTURES', 0.00],
15699
+ ['ETF', 0.50],
15700
+ ['FOREX', 0.10],
15701
+ ['CRYPTO', 0.00]
15702
+ ]),
15703
+ maxVolatility: 8,
15704
+ maxDrawdown: 10,
15705
+ targetReturn: 5,
15706
+ riskScore: 20
15707
+ }
15708
+ ],
15709
+ [
15710
+ 'MODERATE_CONSERVATIVE',
15711
+ {
15712
+ profile: 'MODERATE_CONSERVATIVE',
15713
+ description: 'Income focused with moderate growth potential',
15714
+ baseAllocations: new Map([
15715
+ ['EQUITIES', 0.30],
15716
+ ['OPTIONS', 0.10],
15717
+ ['FUTURES', 0.05],
15718
+ ['ETF', 0.40],
15719
+ ['FOREX', 0.10],
15720
+ ['CRYPTO', 0.05]
15721
+ ]),
15722
+ maxVolatility: 12,
15723
+ maxDrawdown: 15,
15724
+ targetReturn: 7,
15725
+ riskScore: 35
15726
+ }
15727
+ ],
15728
+ [
15729
+ 'MODERATE',
15730
+ {
15731
+ profile: 'MODERATE',
15732
+ description: 'Balanced growth and income with managed volatility',
15733
+ baseAllocations: new Map([
15734
+ ['EQUITIES', 0.40],
15735
+ ['OPTIONS', 0.15],
15736
+ ['FUTURES', 0.10],
15737
+ ['ETF', 0.25],
15738
+ ['FOREX', 0.05],
15739
+ ['CRYPTO', 0.05]
15740
+ ]),
15741
+ maxVolatility: 15,
15742
+ maxDrawdown: 20,
15743
+ targetReturn: 10,
15744
+ riskScore: 50
15745
+ }
15746
+ ],
15747
+ [
15748
+ 'MODERATE_AGGRESSIVE',
15749
+ {
15750
+ profile: 'MODERATE_AGGRESSIVE',
15751
+ description: 'Growth focused with higher volatility tolerance',
15752
+ baseAllocations: new Map([
15753
+ ['EQUITIES', 0.50],
15754
+ ['OPTIONS', 0.20],
15755
+ ['FUTURES', 0.10],
15756
+ ['ETF', 0.10],
15757
+ ['FOREX', 0.05],
15758
+ ['CRYPTO', 0.05]
15759
+ ]),
15760
+ maxVolatility: 20,
15761
+ maxDrawdown: 25,
15762
+ targetReturn: 13,
15763
+ riskScore: 70
15764
+ }
15765
+ ],
15766
+ [
15767
+ 'AGGRESSIVE',
15768
+ {
15769
+ profile: 'AGGRESSIVE',
15770
+ description: 'Maximum growth with high volatility acceptance',
15771
+ baseAllocations: new Map([
15772
+ ['EQUITIES', 0.45],
15773
+ ['OPTIONS', 0.25],
15774
+ ['FUTURES', 0.15],
15775
+ ['ETF', 0.05],
15776
+ ['FOREX', 0.05],
15777
+ ['CRYPTO', 0.05]
15778
+ ]),
15779
+ maxVolatility: 30,
15780
+ maxDrawdown: 35,
15781
+ targetReturn: 18,
15782
+ riskScore: 85
15783
+ }
15784
+ ]
15785
+ ]);
15786
+ constructor(config = {}) {
15787
+ this.config = config;
15788
+ this.config = { ...this.defaultConfig, ...config };
15789
+ }
15790
+ /**
15791
+ * Generate optimal asset allocation recommendation
15792
+ */
15793
+ async generateAllocation(input) {
15794
+ // Step 1: Determine risk profile if not provided
15795
+ const riskProfile = input.riskProfile || this.inferRiskProfile(input);
15796
+ // Step 2: Assess market conditions
15797
+ const marketCondition = this.assessMarketCondition(input.marketConditions);
15798
+ // Step 3: Get base allocations from risk profile
15799
+ const baseAllocations = this.getBaseAllocations(riskProfile);
15800
+ // Step 4: Adjust allocations based on market conditions
15801
+ const marketAdjustedAllocations = this.adjustForMarketConditions(baseAllocations, marketCondition, input.marketConditions);
15802
+ // Step 5: Apply user preferences and constraints
15803
+ const constrainedAllocations = this.applyConstraints(marketAdjustedAllocations, input.preferences, input.constraints, input.assetCharacteristics);
15804
+ // Step 6: Optimize allocations using selected objective
15805
+ const optimizedAllocations = this.optimizeAllocations(constrainedAllocations, input.assetCharacteristics, input.accountSize, riskProfile);
15806
+ // Step 7: Calculate portfolio metrics
15807
+ const portfolioMetrics = this.calculatePortfolioMetrics(optimizedAllocations, input.assetCharacteristics);
15808
+ // Step 8: Perform risk analysis
15809
+ const riskAnalysis = this.performRiskAnalysis(optimizedAllocations, input.assetCharacteristics, riskProfile);
15810
+ // Step 9: Calculate diversification metrics
15811
+ const diversification = this.calculateDiversification(optimizedAllocations, input.assetCharacteristics);
15812
+ // Step 10: Generate rebalancing recommendations if current positions exist
15813
+ const rebalancing = input.currentPositions
15814
+ ? this.generateRebalancingActions(input.currentPositions, optimizedAllocations, input.accountSize)
15815
+ : undefined;
15816
+ // Step 11: Build allocation recommendation
15817
+ const recommendation = {
15818
+ id: this.generateRecommendationId(),
15819
+ allocations: this.buildAssetAllocations(optimizedAllocations, input.accountSize, input.assetCharacteristics, portfolioMetrics, riskProfile),
15820
+ portfolioMetrics,
15821
+ riskAnalysis,
15822
+ diversification,
15823
+ rebalancing,
15824
+ timestamp: new Date(),
15825
+ nextRebalancingDate: this.calculateNextRebalancingDate(input.preferences?.rebalancingFrequency),
15826
+ methodology: this.getMethodologyDescription(this.config.objective, riskProfile),
15827
+ warnings: this.generateWarnings(optimizedAllocations, riskAnalysis, input)
15828
+ };
15829
+ return recommendation;
15830
+ }
15831
+ /**
15832
+ * Infer risk profile from account characteristics
15833
+ */
15834
+ inferRiskProfile(input) {
15835
+ let riskScore = 50; // Start at moderate
15836
+ // Adjust based on account size
15837
+ if (input.accountSize < 10000) {
15838
+ riskScore -= 10; // Smaller accounts tend to be more conservative
15839
+ }
15840
+ else if (input.accountSize > 100000) {
15841
+ riskScore += 10; // Larger accounts can take more risk
15842
+ }
15843
+ // Adjust based on preferences
15844
+ if (input.preferences?.maxDrawdown) {
15845
+ if (input.preferences.maxDrawdown < 15)
15846
+ riskScore -= 15;
15847
+ else if (input.preferences.maxDrawdown > 25)
15848
+ riskScore += 15;
15849
+ }
15850
+ if (input.preferences?.targetReturn) {
15851
+ if (input.preferences.targetReturn < 6)
15852
+ riskScore -= 10;
15853
+ else if (input.preferences.targetReturn > 12)
15854
+ riskScore += 10;
15855
+ }
15856
+ // Adjust based on excluded asset classes (conservative if many excluded)
15857
+ if (input.preferences?.excludedAssetClasses) {
15858
+ riskScore -= input.preferences.excludedAssetClasses.length * 5;
15859
+ }
15860
+ // Map score to profile
15861
+ if (riskScore < 30)
15862
+ return 'CONSERVATIVE';
15863
+ if (riskScore < 45)
15864
+ return 'MODERATE_CONSERVATIVE';
15865
+ if (riskScore < 60)
15866
+ return 'MODERATE';
15867
+ if (riskScore < 75)
15868
+ return 'MODERATE_AGGRESSIVE';
15869
+ return 'AGGRESSIVE';
15870
+ }
15871
+ /**
15872
+ * Assess current market condition
15873
+ */
15874
+ assessMarketCondition(metrics) {
15875
+ // High volatility check
15876
+ if (metrics.volatilityIndex > 30) {
15877
+ return 'HIGH_VOLATILITY';
15878
+ }
15879
+ // Low volatility check
15880
+ if (metrics.volatilityIndex < 12) {
15881
+ return 'LOW_VOLATILITY';
15882
+ }
15883
+ // Crisis detection
15884
+ if (metrics.volatilityIndex > 40 ||
15885
+ metrics.sentimentScore < 20 ||
15886
+ metrics.creditSpread > 500) {
15887
+ return 'CRISIS';
15888
+ }
15889
+ // Bull market
15890
+ if (metrics.trendDirection === 'UP' &&
15891
+ metrics.marketStrength > 60 &&
15892
+ metrics.sentimentScore > 60) {
15893
+ return 'BULL';
15894
+ }
15895
+ // Bear market
15896
+ if (metrics.trendDirection === 'DOWN' &&
15897
+ metrics.marketStrength < 40 &&
15898
+ metrics.sentimentScore < 40) {
15899
+ return 'BEAR';
15900
+ }
15901
+ // Default to sideways
15902
+ return 'SIDEWAYS';
15903
+ }
15904
+ /**
15905
+ * Get base allocations from risk profile
15906
+ */
15907
+ getBaseAllocations(riskProfile) {
15908
+ const profile = this.defaultRiskProfiles.get(riskProfile);
15909
+ if (!profile) {
15910
+ throw new Error(`Unknown risk profile: ${riskProfile}`);
15911
+ }
15912
+ return new Map(profile.baseAllocations);
15913
+ }
15914
+ /**
15915
+ * Adjust allocations based on market conditions
15916
+ */
15917
+ adjustForMarketConditions(baseAllocations, condition, metrics) {
15918
+ const adjusted = new Map(baseAllocations);
15919
+ switch (condition) {
15920
+ case 'CRISIS':
15921
+ // Shift to defensive assets
15922
+ this.scaleAllocation(adjusted, 'EQUITIES', 0.5);
15923
+ this.scaleAllocation(adjusted, 'OPTIONS', 0.3);
15924
+ this.scaleAllocation(adjusted, 'FUTURES', 0.2);
15925
+ this.scaleAllocation(adjusted, 'ETF', 1.5);
15926
+ this.scaleAllocation(adjusted, 'CRYPTO', 0.1);
15927
+ break;
15928
+ case 'HIGH_VOLATILITY':
15929
+ // Reduce volatile assets
15930
+ this.scaleAllocation(adjusted, 'OPTIONS', 0.7);
15931
+ this.scaleAllocation(adjusted, 'FUTURES', 0.7);
15932
+ this.scaleAllocation(adjusted, 'CRYPTO', 0.5);
15933
+ this.scaleAllocation(adjusted, 'ETF', 1.2);
15934
+ break;
15935
+ case 'LOW_VOLATILITY':
15936
+ // Can take more risk
15937
+ this.scaleAllocation(adjusted, 'EQUITIES', 1.1);
15938
+ this.scaleAllocation(adjusted, 'OPTIONS', 1.2);
15939
+ this.scaleAllocation(adjusted, 'CRYPTO', 1.3);
15940
+ break;
15941
+ case 'BULL':
15942
+ // Increase growth assets
15943
+ this.scaleAllocation(adjusted, 'EQUITIES', 1.2);
15944
+ this.scaleAllocation(adjusted, 'OPTIONS', 1.1);
15945
+ this.scaleAllocation(adjusted, 'CRYPTO', 1.2);
15946
+ this.scaleAllocation(adjusted, 'ETF', 0.9);
15947
+ break;
15948
+ case 'BEAR':
15949
+ // Defensive positioning
15950
+ this.scaleAllocation(adjusted, 'EQUITIES', 0.7);
15951
+ this.scaleAllocation(adjusted, 'OPTIONS', 0.8);
15952
+ this.scaleAllocation(adjusted, 'CRYPTO', 0.6);
15953
+ this.scaleAllocation(adjusted, 'ETF', 1.3);
15954
+ this.scaleAllocation(adjusted, 'FOREX', 1.2);
15955
+ break;
15956
+ case 'SIDEWAYS':
15957
+ // Favor income and options strategies
15958
+ this.scaleAllocation(adjusted, 'OPTIONS', 1.2);
15959
+ this.scaleAllocation(adjusted, 'EQUITIES', 0.95);
15960
+ break;
15961
+ }
15962
+ // Additional adjustments based on specific metrics
15963
+ if (metrics.inflationRate > 4) {
15964
+ // High inflation - favor real assets
15965
+ this.scaleAllocation(adjusted, 'CRYPTO', 1.1);
15966
+ this.scaleAllocation(adjusted, 'ETF', 0.9);
15967
+ }
15968
+ if (metrics.interestRateLevel === 'HIGH') {
15969
+ // High rates - favor fixed income and reduce growth
15970
+ this.scaleAllocation(adjusted, 'EQUITIES', 0.9);
15971
+ this.scaleAllocation(adjusted, 'ETF', 1.1);
15972
+ }
15973
+ // Normalize to sum to 1.0
15974
+ return this.normalizeAllocations(adjusted);
15975
+ }
15976
+ /**
15977
+ * Scale allocation for a specific asset class
15978
+ */
15979
+ scaleAllocation(allocations, assetClass, scaleFactor) {
15980
+ const current = allocations.get(assetClass) || 0;
15981
+ allocations.set(assetClass, current * scaleFactor);
15982
+ }
15983
+ /**
15984
+ * Normalize allocations to sum to 1.0
15985
+ */
15986
+ normalizeAllocations(allocations) {
15987
+ const total = Array.from(allocations.values()).reduce((sum, val) => sum + val, 0);
15988
+ if (total === 0) {
15989
+ // Equal weight if all zeros
15990
+ const assetCount = allocations.size;
15991
+ allocations.forEach((_, key) => allocations.set(key, 1 / assetCount));
15992
+ return allocations;
15993
+ }
15994
+ const normalized = new Map();
15995
+ allocations.forEach((value, key) => {
15996
+ normalized.set(key, value / total);
15997
+ });
15998
+ return normalized;
15999
+ }
16000
+ /**
16001
+ * Apply user constraints and preferences
16002
+ */
16003
+ applyConstraints(allocations, preferences, constraints, characteristics) {
16004
+ const constrained = new Map(allocations);
16005
+ // Apply exclusions
16006
+ if (preferences?.excludedAssetClasses) {
16007
+ preferences.excludedAssetClasses.forEach(asset => {
16008
+ constrained.set(asset, 0);
16009
+ });
16010
+ }
16011
+ // Apply preferred asset classes
16012
+ if (preferences?.preferredAssetClasses && preferences.preferredAssetClasses.length > 0) {
16013
+ // Zero out non-preferred assets
16014
+ constrained.forEach((_, asset) => {
16015
+ if (!preferences.preferredAssetClasses.includes(asset)) {
16016
+ constrained.set(asset, 0);
16017
+ }
16018
+ });
16019
+ }
16020
+ // Apply min/max per class
16021
+ if (preferences?.minAllocationPerClass !== undefined) {
16022
+ constrained.forEach((value, asset) => {
16023
+ if (value > 0 && value < preferences.minAllocationPerClass) {
16024
+ constrained.set(asset, preferences.minAllocationPerClass);
16025
+ }
16026
+ });
16027
+ }
16028
+ if (preferences?.maxAllocationPerClass !== undefined) {
16029
+ constrained.forEach((value, asset) => {
16030
+ if (value > preferences.maxAllocationPerClass) {
16031
+ constrained.set(asset, preferences.maxAllocationPerClass);
16032
+ }
16033
+ });
16034
+ }
16035
+ // Apply specific constraints
16036
+ if (constraints) {
16037
+ constraints.forEach(constraint => {
16038
+ if (constraint.assetClass) {
16039
+ const current = constrained.get(constraint.assetClass) || 0;
16040
+ switch (constraint.type) {
16041
+ case 'MIN_ALLOCATION':
16042
+ if (current < constraint.value && constraint.hard) {
16043
+ constrained.set(constraint.assetClass, constraint.value);
16044
+ }
16045
+ break;
16046
+ case 'MAX_ALLOCATION':
16047
+ if (current > constraint.value) {
16048
+ constrained.set(constraint.assetClass, constraint.value);
16049
+ }
16050
+ break;
16051
+ }
16052
+ }
16053
+ });
16054
+ }
16055
+ // Re-normalize after constraint application
16056
+ return this.normalizeAllocations(constrained);
16057
+ }
16058
+ /**
16059
+ * Optimize allocations using specified objective
16060
+ */
16061
+ optimizeAllocations(allocations, characteristics, accountSize, riskProfile) {
16062
+ const charMap = new Map(characteristics.map(c => [c.assetClass, c]));
16063
+ switch (this.config.objective) {
16064
+ case 'MAX_SHARPE':
16065
+ return this.maximizeSharpeRatio(allocations, charMap);
16066
+ case 'MIN_RISK':
16067
+ return this.minimizeRisk(allocations, charMap);
16068
+ case 'MAX_RETURN':
16069
+ return this.maximizeReturn(allocations, charMap, riskProfile);
16070
+ case 'RISK_PARITY':
16071
+ return this.riskParityAllocation(allocations, charMap);
16072
+ case 'MAX_DIVERSIFICATION':
16073
+ return this.maximizeDiversification(allocations, charMap);
16074
+ default:
16075
+ return allocations;
16076
+ }
16077
+ }
16078
+ /**
16079
+ * Maximize Sharpe ratio allocation
16080
+ */
16081
+ maximizeSharpeRatio(allocations, characteristics) {
16082
+ const optimized = new Map();
16083
+ // Calculate excess returns (return - risk-free rate)
16084
+ const excessReturns = new Map();
16085
+ allocations.forEach((_, asset) => {
16086
+ const char = characteristics.get(asset);
16087
+ if (char) {
16088
+ const excessReturn = char.expectedReturn - this.config.riskFreeRate * 100;
16089
+ excessReturns.set(asset, excessReturn);
16090
+ }
16091
+ });
16092
+ // Weight by Sharpe ratio (simplified)
16093
+ let totalSharpe = 0;
16094
+ const sharpeRatios = new Map();
16095
+ allocations.forEach((_, asset) => {
16096
+ const char = characteristics.get(asset);
16097
+ if (char && char.volatility > 0) {
16098
+ const sharpe = (excessReturns.get(asset) || 0) / char.volatility;
16099
+ sharpeRatios.set(asset, Math.max(0, sharpe)); // Only positive sharpe
16100
+ totalSharpe += Math.max(0, sharpe);
16101
+ }
16102
+ });
16103
+ // Allocate proportional to Sharpe ratio
16104
+ if (totalSharpe > 0) {
16105
+ sharpeRatios.forEach((sharpe, asset) => {
16106
+ optimized.set(asset, sharpe / totalSharpe);
16107
+ });
16108
+ }
16109
+ else {
16110
+ // Fall back to equal weight
16111
+ const count = allocations.size;
16112
+ allocations.forEach((_, asset) => {
16113
+ optimized.set(asset, 1 / count);
16114
+ });
16115
+ }
16116
+ // Blend with original allocations (50/50 blend)
16117
+ const blended = new Map();
16118
+ allocations.forEach((originalWeight, asset) => {
16119
+ const optimizedWeight = optimized.get(asset) || 0;
16120
+ blended.set(asset, 0.5 * originalWeight + 0.5 * optimizedWeight);
16121
+ });
16122
+ return this.normalizeAllocations(blended);
16123
+ }
16124
+ /**
16125
+ * Minimize portfolio risk
16126
+ */
16127
+ minimizeRisk(allocations, characteristics) {
16128
+ const optimized = new Map();
16129
+ // Weight inversely to volatility
16130
+ let totalInvVol = 0;
16131
+ const invVolatilities = new Map();
16132
+ allocations.forEach((_, asset) => {
16133
+ const char = characteristics.get(asset);
16134
+ if (char && char.volatility > 0) {
16135
+ const invVol = 1 / char.volatility;
16136
+ invVolatilities.set(asset, invVol);
16137
+ totalInvVol += invVol;
16138
+ }
16139
+ });
16140
+ // Allocate proportional to inverse volatility
16141
+ if (totalInvVol > 0) {
16142
+ invVolatilities.forEach((invVol, asset) => {
16143
+ optimized.set(asset, invVol / totalInvVol);
16144
+ });
16145
+ }
16146
+ else {
16147
+ const count = allocations.size;
16148
+ allocations.forEach((_, asset) => {
16149
+ optimized.set(asset, 1 / count);
16150
+ });
16151
+ }
16152
+ // Blend with original
16153
+ const blended = new Map();
16154
+ allocations.forEach((originalWeight, asset) => {
16155
+ const optimizedWeight = optimized.get(asset) || 0;
16156
+ blended.set(asset, 0.6 * optimizedWeight + 0.4 * originalWeight);
16157
+ });
16158
+ return this.normalizeAllocations(blended);
16159
+ }
16160
+ /**
16161
+ * Maximize expected return
16162
+ */
16163
+ maximizeReturn(allocations, characteristics, riskProfile) {
16164
+ const profile = this.defaultRiskProfiles.get(riskProfile);
16165
+ if (!profile)
16166
+ return allocations;
16167
+ const optimized = new Map();
16168
+ // Weight by expected return, but cap by volatility constraint
16169
+ let totalAdjustedReturn = 0;
16170
+ const adjustedReturns = new Map();
16171
+ allocations.forEach((_, asset) => {
16172
+ const char = characteristics.get(asset);
16173
+ if (char) {
16174
+ // Penalize high volatility assets
16175
+ const volatilityPenalty = char.volatility > profile.maxVolatility
16176
+ ? 0.5
16177
+ : 1.0;
16178
+ const adjustedReturn = char.expectedReturn * volatilityPenalty;
16179
+ adjustedReturns.set(asset, Math.max(0, adjustedReturn));
16180
+ totalAdjustedReturn += Math.max(0, adjustedReturn);
16181
+ }
16182
+ });
16183
+ // Allocate proportional to adjusted returns
16184
+ if (totalAdjustedReturn > 0) {
16185
+ adjustedReturns.forEach((adjReturn, asset) => {
16186
+ optimized.set(asset, adjReturn / totalAdjustedReturn);
16187
+ });
16188
+ }
16189
+ else {
16190
+ const count = allocations.size;
16191
+ allocations.forEach((_, asset) => {
16192
+ optimized.set(asset, 1 / count);
16193
+ });
16194
+ }
16195
+ // Blend with original
16196
+ const blended = new Map();
16197
+ allocations.forEach((originalWeight, asset) => {
16198
+ const optimizedWeight = optimized.get(asset) || 0;
16199
+ blended.set(asset, 0.5 * optimizedWeight + 0.5 * originalWeight);
16200
+ });
16201
+ return this.normalizeAllocations(blended);
16202
+ }
16203
+ /**
16204
+ * Risk parity allocation (equal risk contribution)
16205
+ */
16206
+ riskParityAllocation(allocations, characteristics) {
16207
+ // Simplified risk parity: weight inversely to volatility
16208
+ return this.minimizeRisk(allocations, characteristics);
16209
+ }
16210
+ /**
16211
+ * Maximize diversification
16212
+ */
16213
+ maximizeDiversification(allocations, characteristics) {
16214
+ const optimized = new Map();
16215
+ // Calculate average correlation for each asset
16216
+ const avgCorrelations = new Map();
16217
+ allocations.forEach((_, asset) => {
16218
+ const char = characteristics.get(asset);
16219
+ if (char && char.correlations) {
16220
+ let sumCorr = 0;
16221
+ let count = 0;
16222
+ char.correlations.forEach((corr, _) => {
16223
+ sumCorr += Math.abs(corr);
16224
+ count++;
16225
+ });
16226
+ const avgCorr = count > 0 ? sumCorr / count : 0.5;
16227
+ avgCorrelations.set(asset, avgCorr);
16228
+ }
16229
+ });
16230
+ // Weight inversely to average correlation
16231
+ let totalInvCorr = 0;
16232
+ const invCorrelations = new Map();
16233
+ avgCorrelations.forEach((avgCorr, asset) => {
16234
+ const invCorr = 1 / (0.1 + avgCorr); // Add small constant to avoid division by zero
16235
+ invCorrelations.set(asset, invCorr);
16236
+ totalInvCorr += invCorr;
16237
+ });
16238
+ // Allocate proportional to inverse correlation
16239
+ if (totalInvCorr > 0) {
16240
+ invCorrelations.forEach((invCorr, asset) => {
16241
+ optimized.set(asset, invCorr / totalInvCorr);
16242
+ });
16243
+ }
16244
+ else {
16245
+ const count = allocations.size;
16246
+ allocations.forEach((_, asset) => {
16247
+ optimized.set(asset, 1 / count);
16248
+ });
16249
+ }
16250
+ // Blend with original
16251
+ const blended = new Map();
16252
+ allocations.forEach((originalWeight, asset) => {
16253
+ const optimizedWeight = optimized.get(asset) || 0;
16254
+ blended.set(asset, 0.5 * optimizedWeight + 0.5 * originalWeight);
16255
+ });
16256
+ return this.normalizeAllocations(blended);
16257
+ }
16258
+ /**
16259
+ * Calculate comprehensive portfolio metrics
16260
+ */
16261
+ calculatePortfolioMetrics(allocations, characteristics) {
16262
+ const charMap = new Map(characteristics.map(c => [c.assetClass, c]));
16263
+ let expectedReturn = 0;
16264
+ let portfolioVariance = 0;
16265
+ // Calculate expected return
16266
+ allocations.forEach((weight, asset) => {
16267
+ const char = charMap.get(asset);
16268
+ if (char) {
16269
+ expectedReturn += weight * char.expectedReturn;
16270
+ }
16271
+ });
16272
+ // Calculate portfolio variance (simplified - assumes correlations)
16273
+ const assetList = Array.from(allocations.keys());
16274
+ for (let i = 0; i < assetList.length; i++) {
16275
+ for (let j = 0; j < assetList.length; j++) {
16276
+ const asset1 = assetList[i];
16277
+ const asset2 = assetList[j];
16278
+ const w1 = allocations.get(asset1) || 0;
16279
+ const w2 = allocations.get(asset2) || 0;
16280
+ const char1 = charMap.get(asset1);
16281
+ const char2 = charMap.get(asset2);
16282
+ if (char1 && char2) {
16283
+ const vol1 = char1.volatility / 100; // Convert from percentage
16284
+ const vol2 = char2.volatility / 100;
16285
+ let correlation = 1.0;
16286
+ if (i !== j) {
16287
+ correlation = char1.correlations?.get(asset2) ?? 0.3; // Default correlation
16288
+ }
16289
+ portfolioVariance += w1 * w2 * vol1 * vol2 * correlation;
16290
+ }
16291
+ }
16292
+ }
16293
+ const expectedVolatility = Math.sqrt(portfolioVariance) * 100; // Convert to percentage
16294
+ // Calculate Sharpe ratio
16295
+ const excessReturn = expectedReturn - this.config.riskFreeRate * 100;
16296
+ const sharpeRatio = expectedVolatility > 0 ? excessReturn / expectedVolatility : 0;
16297
+ // Calculate Sortino ratio (simplified - using volatility as proxy)
16298
+ const downsideDeviation = expectedVolatility * 0.7; // Rough approximation
16299
+ const sortinoRatio = downsideDeviation > 0 ? excessReturn / downsideDeviation : 0;
16300
+ // Estimate maximum drawdown (rough approximation)
16301
+ const maxDrawdown = expectedVolatility * 1.5;
16302
+ // VaR and CVaR (parametric approach, 95% confidence)
16303
+ const valueAtRisk95 = 1.645 * expectedVolatility;
16304
+ const conditionalVaR = 2.063 * expectedVolatility; // 95% CVaR
16305
+ // Beta and Alpha (simplified)
16306
+ const beta = 1.0; // Assume market beta for now
16307
+ const alpha = expectedReturn - (this.config.riskFreeRate * 100 + beta * 6); // Assume 6% market risk premium
16308
+ // Information ratio
16309
+ const trackingError = expectedVolatility * 0.5;
16310
+ const informationRatio = trackingError > 0 ? alpha / trackingError : 0;
16311
+ return {
16312
+ expectedReturn,
16313
+ expectedVolatility,
16314
+ sharpeRatio,
16315
+ sortinoRatio,
16316
+ maxDrawdown,
16317
+ valueAtRisk95,
16318
+ conditionalVaR,
16319
+ beta,
16320
+ alpha,
16321
+ informationRatio
16322
+ };
16323
+ }
16324
+ /**
16325
+ * Perform comprehensive risk analysis
16326
+ */
16327
+ performRiskAnalysis(allocations, characteristics, riskProfile) {
16328
+ const charMap = new Map(characteristics.map(c => [c.assetClass, c]));
16329
+ const profile = this.defaultRiskProfiles.get(riskProfile);
16330
+ // Calculate portfolio volatility
16331
+ let totalVolatility = 0;
16332
+ allocations.forEach((weight, asset) => {
16333
+ const char = charMap.get(asset);
16334
+ if (char) {
16335
+ totalVolatility += weight * char.volatility;
16336
+ }
16337
+ });
16338
+ // Risk score (0-100)
16339
+ const riskScore = Math.min(100, (totalVolatility / profile.maxVolatility) * 100);
16340
+ // Risk level
16341
+ let riskLevel;
16342
+ if (riskScore < 40)
16343
+ riskLevel = 'LOW';
16344
+ else if (riskScore < 60)
16345
+ riskLevel = 'MEDIUM';
16346
+ else if (riskScore < 80)
16347
+ riskLevel = 'HIGH';
16348
+ else
16349
+ riskLevel = 'EXTREME';
16350
+ // Systematic vs idiosyncratic risk (simplified)
16351
+ const systematicRisk = totalVolatility * 0.7; // 70% systematic
16352
+ const idiosyncraticRisk = totalVolatility * 0.3; // 30% idiosyncratic
16353
+ // Tail risk (simplified - higher volatility = higher tail risk)
16354
+ const tailRisk = totalVolatility * 1.2;
16355
+ // Liquidity risk
16356
+ let liquidityRisk = 0;
16357
+ allocations.forEach((weight, asset) => {
16358
+ const char = charMap.get(asset);
16359
+ if (char) {
16360
+ liquidityRisk += weight * (100 - char.liquidityScore);
16361
+ }
16362
+ });
16363
+ // Concentration risk (HHI)
16364
+ let hhi = 0;
16365
+ allocations.forEach(weight => {
16366
+ hhi += weight * weight;
16367
+ });
16368
+ const concentrationRisk = hhi * 100;
16369
+ // Currency risk (simplified)
16370
+ const forexWeight = allocations.get('FOREX') || 0;
16371
+ const cryptoWeight = allocations.get('CRYPTO') || 0;
16372
+ const currencyRisk = (forexWeight + cryptoWeight) * 50;
16373
+ // Risk decomposition by asset class
16374
+ const riskDecomposition = new Map();
16375
+ let totalRiskContribution = 0;
16376
+ allocations.forEach((weight, asset) => {
16377
+ const char = charMap.get(asset);
16378
+ if (char) {
16379
+ const riskContribution = weight * char.volatility;
16380
+ riskDecomposition.set(asset, riskContribution);
16381
+ totalRiskContribution += riskContribution;
16382
+ }
16383
+ });
16384
+ // Normalize risk contributions
16385
+ if (totalRiskContribution > 0) {
16386
+ riskDecomposition.forEach((contrib, asset) => {
16387
+ riskDecomposition.set(asset, (contrib / totalRiskContribution) * 100);
16388
+ });
16389
+ }
16390
+ return {
16391
+ riskScore,
16392
+ riskLevel,
16393
+ systematicRisk,
16394
+ idiosyncraticRisk,
16395
+ tailRisk,
16396
+ liquidityRisk,
16397
+ concentrationRisk,
16398
+ currencyRisk,
16399
+ riskDecomposition
16400
+ };
16401
+ }
16402
+ /**
16403
+ * Calculate diversification metrics
16404
+ */
16405
+ calculateDiversification(allocations, characteristics) {
16406
+ const charMap = new Map(characteristics.map(c => [c.assetClass, c]));
16407
+ // Herfindahl-Hirschman Index (concentration measure)
16408
+ let hhi = 0;
16409
+ allocations.forEach(weight => {
16410
+ hhi += weight * weight;
16411
+ });
16412
+ // Effective number of assets
16413
+ const effectiveNumberOfAssets = hhi > 0 ? 1 / hhi : allocations.size;
16414
+ // Average correlation
16415
+ let sumCorrelations = 0;
16416
+ let correlationCount = 0;
16417
+ let maxCorr = 0;
16418
+ const assetList = Array.from(allocations.keys());
16419
+ for (let i = 0; i < assetList.length; i++) {
16420
+ for (let j = i + 1; j < assetList.length; j++) {
16421
+ const asset1 = assetList[i];
16422
+ const asset2 = assetList[j];
16423
+ const char1 = charMap.get(asset1);
16424
+ if (char1?.correlations) {
16425
+ const corr = Math.abs(char1.correlations.get(asset2) ?? 0.3);
16426
+ sumCorrelations += corr;
16427
+ correlationCount++;
16428
+ maxCorr = Math.max(maxCorr, corr);
16429
+ }
16430
+ }
16431
+ }
16432
+ const averageCorrelation = correlationCount > 0 ? sumCorrelations / correlationCount : 0;
16433
+ // Diversification ratio (weighted average vol / portfolio vol)
16434
+ let weightedAvgVol = 0;
16435
+ let portfolioVar = 0;
16436
+ allocations.forEach((weight, asset) => {
16437
+ const char = charMap.get(asset);
16438
+ if (char) {
16439
+ weightedAvgVol += weight * char.volatility;
16440
+ }
16441
+ });
16442
+ // Calculate portfolio variance
16443
+ for (let i = 0; i < assetList.length; i++) {
16444
+ for (let j = 0; j < assetList.length; j++) {
16445
+ const asset1 = assetList[i];
16446
+ const asset2 = assetList[j];
16447
+ const w1 = allocations.get(asset1) || 0;
16448
+ const w2 = allocations.get(asset2) || 0;
16449
+ const char1 = charMap.get(asset1);
16450
+ const char2 = charMap.get(asset2);
16451
+ if (char1 && char2) {
16452
+ const vol1 = char1.volatility / 100;
16453
+ const vol2 = char2.volatility / 100;
16454
+ const corr = i === j ? 1.0 : (char1.correlations?.get(asset2) ?? 0.3);
16455
+ portfolioVar += w1 * w2 * vol1 * vol2 * corr;
16456
+ }
16457
+ }
16458
+ }
16459
+ const portfolioVol = Math.sqrt(portfolioVar) * 100;
16460
+ const diversificationRatio = portfolioVol > 0 ? weightedAvgVol / portfolioVol : 1;
16461
+ // Asset class diversity score (based on number of asset classes used)
16462
+ const nonZeroAllocations = Array.from(allocations.values()).filter(w => w > 0.01).length;
16463
+ const totalAssetClasses = allocations.size;
16464
+ const assetClassDiversity = (nonZeroAllocations / totalAssetClasses) * 100;
16465
+ // Build correlation matrix
16466
+ const correlationMatrix = [];
16467
+ for (let i = 0; i < assetList.length; i++) {
16468
+ correlationMatrix[i] = [];
16469
+ for (let j = 0; j < assetList.length; j++) {
16470
+ if (i === j) {
16471
+ correlationMatrix[i][j] = 1.0;
16472
+ }
16473
+ else {
16474
+ const char1 = charMap.get(assetList[i]);
16475
+ const corr = char1?.correlations?.get(assetList[j]) ?? 0.3;
16476
+ correlationMatrix[i][j] = corr;
16477
+ }
16478
+ }
16479
+ }
16480
+ return {
16481
+ diversificationRatio,
16482
+ herfindahlIndex: hhi,
16483
+ effectiveNumberOfAssets,
16484
+ averageCorrelation,
16485
+ maxPairwiseCorrelation: maxCorr,
16486
+ correlationMatrix,
16487
+ assetClassDiversity
16488
+ };
16489
+ }
16490
+ /**
16491
+ * Generate rebalancing actions
16492
+ */
16493
+ generateRebalancingActions(currentPositions, targetAllocations, accountSize) {
16494
+ const actions = [];
16495
+ // Calculate current total value
16496
+ let currentTotal = 0;
16497
+ currentPositions.forEach(amount => {
16498
+ currentTotal += amount;
16499
+ });
16500
+ // Generate actions for each asset class
16501
+ targetAllocations.forEach((targetWeight, asset) => {
16502
+ const currentAmount = currentPositions.get(asset) || 0;
16503
+ const currentWeight = currentTotal > 0 ? currentAmount / currentTotal : 0;
16504
+ const targetAmount = accountSize * targetWeight;
16505
+ const drift = Math.abs(currentWeight - targetWeight);
16506
+ // Only rebalance if drift exceeds threshold
16507
+ if (drift > this.config.rebalancingThreshold / 100) {
16508
+ const tradeDelta = targetAmount - currentAmount;
16509
+ actions.push({
16510
+ assetClass: asset,
16511
+ currentAllocation: currentWeight,
16512
+ targetAllocation: targetWeight,
16513
+ action: tradeDelta > 0 ? 'BUY' : 'SELL',
16514
+ tradeAmount: Math.abs(tradeDelta),
16515
+ priority: drift > 0.15 ? 1 : drift > 0.10 ? 2 : 3,
16516
+ estimatedCost: Math.abs(tradeDelta) * 0.001, // 0.1% transaction cost
16517
+ reason: `Drift of ${(drift * 100).toFixed(2)}% exceeds threshold`
16518
+ });
16519
+ }
16520
+ });
16521
+ // Sort by priority
16522
+ actions.sort((a, b) => a.priority - b.priority);
16523
+ return actions;
16524
+ }
16525
+ /**
16526
+ * Build detailed asset allocations
16527
+ */
16528
+ buildAssetAllocations(allocations, accountSize, characteristics, portfolioMetrics, riskProfile) {
16529
+ const charMap = new Map(characteristics.map(c => [c.assetClass, c]));
16530
+ const assetAllocations = [];
16531
+ allocations.forEach((weight, asset) => {
16532
+ if (weight > 0.001) { // Only include meaningful allocations
16533
+ const char = charMap.get(asset);
16534
+ const amount = accountSize * weight;
16535
+ // Calculate risk contribution
16536
+ const assetVol = char?.volatility || 0;
16537
+ const portfolioVol = portfolioMetrics.expectedVolatility;
16538
+ const riskContribution = portfolioVol > 0 ? (weight * assetVol) / portfolioVol : 0;
16539
+ // Calculate return contribution
16540
+ const assetReturn = char?.expectedReturn || 0;
16541
+ const returnContribution = weight * assetReturn;
16542
+ // Generate rationale
16543
+ const rationale = this.generateAllocationRationale(asset, weight, char, riskProfile);
16544
+ // Calculate confidence (based on data quality and market conditions)
16545
+ const confidence = char ? Math.min(0.95, 0.7 + (char.liquidityScore / 200)) : 0.5;
16546
+ assetAllocations.push({
16547
+ assetClass: asset,
16548
+ allocation: weight,
16549
+ amount,
16550
+ riskContribution,
16551
+ returnContribution,
16552
+ rationale,
16553
+ confidence
16554
+ });
16555
+ }
16556
+ });
16557
+ // Sort by allocation size
16558
+ assetAllocations.sort((a, b) => b.allocation - a.allocation);
16559
+ return assetAllocations;
16560
+ }
16561
+ /**
16562
+ * Generate rationale for asset allocation
16563
+ */
16564
+ generateAllocationRationale(asset, weight, characteristics, riskProfile) {
16565
+ if (!characteristics) {
16566
+ return `${(weight * 100).toFixed(1)}% allocated to ${asset}`;
16567
+ }
16568
+ const reasons = [];
16569
+ // Add size description
16570
+ if (weight > 0.3) {
16571
+ reasons.push('Core holding');
16572
+ }
16573
+ else if (weight > 0.15) {
16574
+ reasons.push('Significant position');
16575
+ }
16576
+ else if (weight > 0.05) {
16577
+ reasons.push('Moderate allocation');
16578
+ }
16579
+ else {
16580
+ reasons.push('Tactical allocation');
16581
+ }
16582
+ // Add characteristic-based reasoning
16583
+ if (characteristics.sharpeRatio > 1.5) {
16584
+ reasons.push('strong risk-adjusted returns');
16585
+ }
16586
+ if (characteristics.volatility < 15) {
16587
+ reasons.push('low volatility');
16588
+ }
16589
+ else if (characteristics.volatility > 25) {
16590
+ reasons.push('high growth potential');
16591
+ }
16592
+ if (characteristics.liquidityScore > 80) {
16593
+ reasons.push('high liquidity');
16594
+ }
16595
+ // Add risk profile context
16596
+ if (riskProfile === 'CONSERVATIVE' && asset === 'ETF') {
16597
+ reasons.push('diversification and stability');
16598
+ }
16599
+ else if (riskProfile === 'AGGRESSIVE' && asset === 'OPTIONS') {
16600
+ reasons.push('leveraged growth opportunities');
16601
+ }
16602
+ return `${(weight * 100).toFixed(1)}% allocation - ${reasons.join(', ')}`;
16603
+ }
16604
+ /**
16605
+ * Calculate next rebalancing date
16606
+ */
16607
+ calculateNextRebalancingDate(frequencyDays) {
16608
+ const days = frequencyDays || 90; // Default to quarterly
16609
+ const nextDate = new Date();
16610
+ nextDate.setDate(nextDate.getDate() + days);
16611
+ return nextDate;
16612
+ }
16613
+ /**
16614
+ * Get methodology description
16615
+ */
16616
+ getMethodologyDescription(objective, riskProfile) {
16617
+ const descriptions = {
16618
+ MAX_SHARPE: 'Sharpe ratio maximization with risk-adjusted return optimization',
16619
+ MIN_RISK: 'Minimum variance optimization prioritizing capital preservation',
16620
+ MAX_RETURN: 'Return maximization within risk tolerance constraints',
16621
+ RISK_PARITY: 'Equal risk contribution across asset classes',
16622
+ MAX_DIVERSIFICATION: 'Correlation-based diversification maximization',
16623
+ TARGET_RETURN: 'Target return achievement with minimum required risk',
16624
+ TARGET_RISK: 'Target risk level with maximum potential return'
16625
+ };
16626
+ return `${descriptions[objective]} tailored for ${riskProfile} risk profile`;
16627
+ }
16628
+ /**
16629
+ * Generate warnings based on allocation
16630
+ */
16631
+ generateWarnings(allocations, riskAnalysis, input) {
16632
+ const warnings = [];
16633
+ // High risk warning
16634
+ if (riskAnalysis.riskLevel === 'HIGH' || riskAnalysis.riskLevel === 'EXTREME') {
16635
+ warnings.push(`Portfolio risk level is ${riskAnalysis.riskLevel}. Consider reducing exposure to volatile assets.`);
16636
+ }
16637
+ // Concentration warning
16638
+ if (riskAnalysis.concentrationRisk > 40) {
16639
+ warnings.push('High concentration detected. Portfolio may benefit from additional diversification.');
16640
+ }
16641
+ // Liquidity warning
16642
+ if (riskAnalysis.liquidityRisk > 30) {
16643
+ warnings.push('Some positions may have limited liquidity. Consider exit strategies in advance.');
16644
+ }
16645
+ // Small account warning
16646
+ if (input.accountSize < 5000) {
16647
+ warnings.push('Small account size may limit diversification. Consider focusing on ETFs for broader exposure.');
16648
+ }
16649
+ // High volatility market warning
16650
+ if (input.marketConditions.volatilityIndex > 25) {
16651
+ warnings.push('Market volatility is elevated. Consider maintaining higher cash reserves.');
16652
+ }
16653
+ // Crypto allocation warning
16654
+ const cryptoAlloc = allocations.get('CRYPTO') || 0;
16655
+ if (cryptoAlloc > 0.15) {
16656
+ warnings.push('Cryptocurrency allocation exceeds 15%. Be aware of high volatility and regulatory risks.');
16657
+ }
16658
+ // Options allocation warning
16659
+ const optionsAlloc = allocations.get('OPTIONS') || 0;
16660
+ if (optionsAlloc > 0.25) {
16661
+ warnings.push('Options allocation is significant. Ensure adequate knowledge and risk management.');
16662
+ }
16663
+ return warnings;
16664
+ }
16665
+ /**
16666
+ * Generate unique recommendation ID
16667
+ */
16668
+ generateRecommendationId() {
16669
+ const timestamp = Date.now();
16670
+ const random = Math.random().toString(36).substring(2, 9);
16671
+ return `alloc_${timestamp}_${random}`;
16672
+ }
16673
+ }
16674
+ /**
16675
+ * Convenience function to generate allocation with default settings
16676
+ */
16677
+ async function generateOptimalAllocation(input, config) {
16678
+ const engine = new AssetAllocationEngine(config);
16679
+ return engine.generateAllocation(input);
16680
+ }
16681
+ /**
16682
+ * Convenience function to get default risk profile characteristics
16683
+ */
16684
+ function getDefaultRiskProfile(profile) {
16685
+ const engine = new AssetAllocationEngine();
16686
+ return engine.defaultRiskProfiles.get(profile);
16687
+ }
16688
+
15607
16689
  // Export factory functions for easier instantiation
15608
16690
  const createAlpacaTradingAPI = (credentials) => {
15609
16691
  return new AlpacaTradingAPI(credentials);
@@ -15750,6 +16832,7 @@ const adptc = adaptic;
15750
16832
 
15751
16833
  exports.AlpacaMarketDataAPI = AlpacaMarketDataAPI;
15752
16834
  exports.AlpacaTradingAPI = AlpacaTradingAPI;
16835
+ exports.AssetAllocationEngine = AssetAllocationEngine;
15753
16836
  exports.DEFAULT_CACHE_OPTIONS = DEFAULT_CACHE_OPTIONS;
15754
16837
  exports.StampedeProtectedCache = StampedeProtectedCache;
15755
16838
  exports.adaptic = adaptic;
@@ -15757,4 +16840,6 @@ exports.adptc = adptc;
15757
16840
  exports.createAlpacaMarketDataAPI = createAlpacaMarketDataAPI;
15758
16841
  exports.createAlpacaTradingAPI = createAlpacaTradingAPI;
15759
16842
  exports.createStampedeProtectedCache = createStampedeProtectedCache;
16843
+ exports.generateOptimalAllocation = generateOptimalAllocation;
16844
+ exports.getDefaultRiskProfile = getDefaultRiskProfile;
15760
16845
  //# sourceMappingURL=index.cjs.map