@adaptic/utils 0.0.370 → 0.0.373

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