@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.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.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
|
|
1390
|
-
|
|
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
|
-
|
|
1393
|
-
|
|
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
|
-
|
|
1495
|
-
|
|
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
|
-
|
|
1502
|
-
|
|
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
|
-
|
|
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
|