@adaptic/utils 0.0.369 → 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.cjs +1096 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1094 -12
- package/dist/index.mjs.map +1 -1
- package/dist/types/alpaca-functions.d.ts.map +1 -1
- package/dist/types/asset-allocation-algorithm.d.ts +137 -0
- package/dist/types/asset-allocation-algorithm.d.ts.map +1 -0
- package/dist/types/examples/asset-allocation-example.d.ts +35 -0
- package/dist/types/examples/asset-allocation-example.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types/alpaca-types.d.ts +7 -3
- package/dist/types/types/alpaca-types.d.ts.map +1 -1
- package/dist/types/types/asset-allocation-types.d.ts +328 -0
- package/dist/types/types/asset-allocation-types.d.ts.map +1 -0
- package/package.json +2 -2
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
|
|
1412
|
-
|
|
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
|
-
|
|
1415
|
-
|
|
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
|
-
|
|
1517
|
-
|
|
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
|
-
|
|
1524
|
-
|
|
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
|
-
|
|
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
|