Initial commit
This commit is contained in:
16
.claude-plugin/plugin.json
Normal file
16
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "crypto-portfolio-tracker",
|
||||||
|
"description": "Professional crypto portfolio tracking with real-time prices, PnL analysis, and risk metrics",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Intent Solutions IO",
|
||||||
|
"email": "jeremy@intentsolutions.ai",
|
||||||
|
"url": "https://intentsolutions.ai"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
"./commands"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# crypto-portfolio-tracker
|
||||||
|
|
||||||
|
Professional crypto portfolio tracking with real-time prices, PnL analysis, and risk metrics
|
||||||
580
commands/portfolio-analysis.md
Normal file
580
commands/portfolio-analysis.md
Normal file
@@ -0,0 +1,580 @@
|
|||||||
|
---
|
||||||
|
description: Analyze entire crypto portfolio with allocation, risk metrics, and rebalancing suggestions
|
||||||
|
shortcut: pa
|
||||||
|
---
|
||||||
|
|
||||||
|
# Portfolio Analysis
|
||||||
|
|
||||||
|
Comprehensive portfolio analysis for cryptocurrency holdings with advanced metrics, risk assessment, and optimization recommendations.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Analyze the user's complete crypto portfolio to provide insights on:
|
||||||
|
- Asset allocation and diversification
|
||||||
|
- Risk metrics (Sharpe ratio, volatility, max drawdown)
|
||||||
|
- Performance attribution
|
||||||
|
- Correlation analysis
|
||||||
|
- Rebalancing recommendations
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1. Portfolio Analytics Engine
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class PortfolioAnalyzer {
|
||||||
|
constructor() {
|
||||||
|
this.riskFreeRate = 0.02; // 2% annual risk-free rate
|
||||||
|
this.historicalData = new Map();
|
||||||
|
this.correlationMatrix = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async analyzePortfolio(positions) {
|
||||||
|
// Fetch current prices and calculate values
|
||||||
|
const portfolioData = await this.enrichPositions(positions);
|
||||||
|
|
||||||
|
// Calculate core metrics
|
||||||
|
const metrics = {
|
||||||
|
totalValue: this.calculateTotalValue(portfolioData),
|
||||||
|
totalCost: this.calculateTotalCost(portfolioData),
|
||||||
|
totalPnL: this.calculateTotalPnL(portfolioData),
|
||||||
|
allocation: this.calculateAllocation(portfolioData),
|
||||||
|
concentration: this.calculateConcentration(portfolioData),
|
||||||
|
volatility: await this.calculateVolatility(portfolioData),
|
||||||
|
sharpeRatio: await this.calculateSharpeRatio(portfolioData),
|
||||||
|
sortino: await this.calculateSortinoRatio(portfolioData),
|
||||||
|
maxDrawdown: await this.calculateMaxDrawdown(portfolioData),
|
||||||
|
correlations: await this.calculateCorrelations(portfolioData),
|
||||||
|
var95: this.calculateValueAtRisk(portfolioData, 0.95),
|
||||||
|
var99: this.calculateValueAtRisk(portfolioData, 0.99)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate insights and recommendations
|
||||||
|
const analysis = {
|
||||||
|
metrics,
|
||||||
|
riskAssessment: this.assessRisk(metrics),
|
||||||
|
diversificationScore: this.calculateDiversificationScore(metrics),
|
||||||
|
rebalancingPlan: this.generateRebalancingPlan(portfolioData, metrics),
|
||||||
|
optimizations: this.suggestOptimizations(metrics)
|
||||||
|
};
|
||||||
|
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateAllocation(portfolioData) {
|
||||||
|
const totalValue = portfolioData.reduce((sum, p) => sum + p.currentValue, 0);
|
||||||
|
|
||||||
|
return portfolioData.map(position => ({
|
||||||
|
symbol: position.symbol,
|
||||||
|
value: position.currentValue,
|
||||||
|
percentage: (position.currentValue / totalValue) * 100,
|
||||||
|
targetPercentage: position.targetAllocation || null,
|
||||||
|
deviation: position.targetAllocation
|
||||||
|
? Math.abs(((position.currentValue / totalValue) * 100) - position.targetAllocation)
|
||||||
|
: 0
|
||||||
|
})).sort((a, b) => b.percentage - a.percentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateConcentration(portfolioData) {
|
||||||
|
const allocations = this.calculateAllocation(portfolioData);
|
||||||
|
const herfindahlIndex = allocations.reduce((sum, a) => {
|
||||||
|
return sum + Math.pow(a.percentage / 100, 2);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
herfindahlIndex: herfindahlIndex.toFixed(4),
|
||||||
|
effectiveAssets: (1 / herfindahlIndex).toFixed(2),
|
||||||
|
topAssetConcentration: allocations[0].percentage.toFixed(2),
|
||||||
|
top3Concentration: allocations.slice(0, 3).reduce((sum, a) => sum + a.percentage, 0).toFixed(2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async calculateVolatility(portfolioData, period = 30) {
|
||||||
|
const returns = await this.getHistoricalReturns(portfolioData, period);
|
||||||
|
|
||||||
|
if (returns.length < 2) return null;
|
||||||
|
|
||||||
|
const avgReturn = returns.reduce((sum, r) => sum + r, 0) / returns.length;
|
||||||
|
const variance = returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / (returns.length - 1);
|
||||||
|
const volatility = Math.sqrt(variance);
|
||||||
|
|
||||||
|
// Annualized volatility
|
||||||
|
return {
|
||||||
|
daily: (volatility * 100).toFixed(2),
|
||||||
|
weekly: (volatility * Math.sqrt(7) * 100).toFixed(2),
|
||||||
|
monthly: (volatility * Math.sqrt(30) * 100).toFixed(2),
|
||||||
|
annual: (volatility * Math.sqrt(365) * 100).toFixed(2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async calculateSharpeRatio(portfolioData) {
|
||||||
|
const returns = await this.getHistoricalReturns(portfolioData, 365);
|
||||||
|
if (returns.length < 30) return null;
|
||||||
|
|
||||||
|
const avgReturn = returns.reduce((sum, r) => sum + r, 0) / returns.length;
|
||||||
|
const annualizedReturn = avgReturn * 365;
|
||||||
|
|
||||||
|
const stdDev = Math.sqrt(
|
||||||
|
returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / (returns.length - 1)
|
||||||
|
);
|
||||||
|
const annualizedStdDev = stdDev * Math.sqrt(365);
|
||||||
|
|
||||||
|
const sharpeRatio = (annualizedReturn - this.riskFreeRate) / annualizedStdDev;
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: sharpeRatio.toFixed(3),
|
||||||
|
interpretation: this.interpretSharpeRatio(sharpeRatio)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interpretSharpeRatio(ratio) {
|
||||||
|
if (ratio < 0) return 'POOR - Returns below risk-free rate';
|
||||||
|
if (ratio < 0.5) return 'SUBOPTIMAL - Low risk-adjusted returns';
|
||||||
|
if (ratio < 1.0) return 'ACCEPTABLE - Moderate risk-adjusted returns';
|
||||||
|
if (ratio < 2.0) return 'GOOD - Strong risk-adjusted returns';
|
||||||
|
return 'EXCELLENT - Outstanding risk-adjusted returns';
|
||||||
|
}
|
||||||
|
|
||||||
|
async calculateCorrelations(portfolioData) {
|
||||||
|
if (portfolioData.length < 2) return null;
|
||||||
|
|
||||||
|
const correlationMatrix = [];
|
||||||
|
const symbols = portfolioData.map(p => p.symbol);
|
||||||
|
|
||||||
|
for (let i = 0; i < symbols.length; i++) {
|
||||||
|
const row = [];
|
||||||
|
for (let j = 0; j < symbols.length; j++) {
|
||||||
|
if (i === j) {
|
||||||
|
row.push(1.0);
|
||||||
|
} else {
|
||||||
|
const correlation = await this.calculatePairCorrelation(
|
||||||
|
symbols[i],
|
||||||
|
symbols[j],
|
||||||
|
30 // 30-day correlation
|
||||||
|
);
|
||||||
|
row.push(correlation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
correlationMatrix.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find highest correlations
|
||||||
|
const highCorrelations = [];
|
||||||
|
for (let i = 0; i < symbols.length; i++) {
|
||||||
|
for (let j = i + 1; j < symbols.length; j++) {
|
||||||
|
if (Math.abs(correlationMatrix[i][j]) > 0.7) {
|
||||||
|
highCorrelations.push({
|
||||||
|
pair: `${symbols[i]}-${symbols[j]}`,
|
||||||
|
correlation: correlationMatrix[i][j].toFixed(3),
|
||||||
|
interpretation: correlationMatrix[i][j] > 0 ? 'POSITIVE' : 'NEGATIVE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
matrix: correlationMatrix,
|
||||||
|
symbols,
|
||||||
|
highCorrelations,
|
||||||
|
averageCorrelation: this.calculateAverageCorrelation(correlationMatrix)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateValueAtRisk(portfolioData, confidenceLevel) {
|
||||||
|
const returns = this.historicalData.get('portfolio_returns') || [];
|
||||||
|
if (returns.length < 100) return null;
|
||||||
|
|
||||||
|
const sortedReturns = returns.sort((a, b) => a - b);
|
||||||
|
const index = Math.floor((1 - confidenceLevel) * sortedReturns.length);
|
||||||
|
const var_value = sortedReturns[index];
|
||||||
|
const totalValue = portfolioData.reduce((sum, p) => sum + p.currentValue, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
percentage: (var_value * 100).toFixed(2),
|
||||||
|
dollarAmount: (totalValue * var_value).toFixed(2),
|
||||||
|
confidenceLevel: (confidenceLevel * 100).toFixed(0),
|
||||||
|
interpretation: `${confidenceLevel * 100}% chance that losses won't exceed $${Math.abs(totalValue * var_value).toFixed(2)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
assessRisk(metrics) {
|
||||||
|
const riskFactors = [];
|
||||||
|
let riskScore = 0;
|
||||||
|
|
||||||
|
// Concentration risk
|
||||||
|
if (metrics.concentration.topAssetConcentration > 50) {
|
||||||
|
riskFactors.push({
|
||||||
|
type: 'CONCENTRATION',
|
||||||
|
severity: 'HIGH',
|
||||||
|
description: `Top asset represents ${metrics.concentration.topAssetConcentration}% of portfolio`
|
||||||
|
});
|
||||||
|
riskScore += 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volatility risk
|
||||||
|
if (metrics.volatility && metrics.volatility.annual > 100) {
|
||||||
|
riskFactors.push({
|
||||||
|
type: 'VOLATILITY',
|
||||||
|
severity: 'HIGH',
|
||||||
|
description: `Annual volatility exceeds 100% (${metrics.volatility.annual}%)`
|
||||||
|
});
|
||||||
|
riskScore += 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correlation risk
|
||||||
|
if (metrics.correlations && metrics.correlations.highCorrelations.length > 0) {
|
||||||
|
riskFactors.push({
|
||||||
|
type: 'CORRELATION',
|
||||||
|
severity: 'MEDIUM',
|
||||||
|
description: `${metrics.correlations.highCorrelations.length} asset pairs highly correlated`
|
||||||
|
});
|
||||||
|
riskScore += 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drawdown risk
|
||||||
|
if (metrics.maxDrawdown && metrics.maxDrawdown.percentage > 40) {
|
||||||
|
riskFactors.push({
|
||||||
|
type: 'DRAWDOWN',
|
||||||
|
severity: 'HIGH',
|
||||||
|
description: `Maximum drawdown of ${metrics.maxDrawdown.percentage}% observed`
|
||||||
|
});
|
||||||
|
riskScore += 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
overallScore: Math.min(riskScore, 100),
|
||||||
|
level: this.determineRiskLevel(riskScore),
|
||||||
|
factors: riskFactors,
|
||||||
|
recommendations: this.generateRiskRecommendations(riskFactors)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
determineRiskLevel(score) {
|
||||||
|
if (score < 20) return 'LOW';
|
||||||
|
if (score < 40) return 'MODERATE';
|
||||||
|
if (score < 60) return 'ELEVATED';
|
||||||
|
if (score < 80) return 'HIGH';
|
||||||
|
return 'CRITICAL';
|
||||||
|
}
|
||||||
|
|
||||||
|
generateRebalancingPlan(portfolioData, metrics) {
|
||||||
|
const currentAllocations = metrics.allocation;
|
||||||
|
const targetAllocations = this.calculateOptimalAllocation(portfolioData, metrics);
|
||||||
|
const totalValue = metrics.totalValue;
|
||||||
|
|
||||||
|
const rebalancingActions = [];
|
||||||
|
|
||||||
|
currentAllocations.forEach((current, index) => {
|
||||||
|
const target = targetAllocations[index];
|
||||||
|
const currentValue = current.value;
|
||||||
|
const targetValue = (target.percentage / 100) * totalValue;
|
||||||
|
const difference = targetValue - currentValue;
|
||||||
|
|
||||||
|
if (Math.abs(difference) > totalValue * 0.01) { // Only rebalance if > 1% of portfolio
|
||||||
|
rebalancingActions.push({
|
||||||
|
symbol: current.symbol,
|
||||||
|
action: difference > 0 ? 'BUY' : 'SELL',
|
||||||
|
amount: Math.abs(difference).toFixed(2),
|
||||||
|
currentPercentage: current.percentage.toFixed(2),
|
||||||
|
targetPercentage: target.percentage.toFixed(2),
|
||||||
|
reason: target.reason
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions: rebalancingActions,
|
||||||
|
estimatedCost: this.estimateRebalancingCost(rebalancingActions),
|
||||||
|
expectedImprovement: this.estimateImprovementMetrics(targetAllocations, currentAllocations)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateOptimalAllocation(portfolioData, metrics) {
|
||||||
|
// Modern Portfolio Theory optimization
|
||||||
|
// This is a simplified version - real implementation would use quadratic programming
|
||||||
|
|
||||||
|
const riskTolerance = this.determineRiskTolerance(metrics);
|
||||||
|
const correlations = metrics.correlations?.matrix || [];
|
||||||
|
|
||||||
|
// Start with equal weight
|
||||||
|
let allocations = portfolioData.map(p => ({
|
||||||
|
symbol: p.symbol,
|
||||||
|
percentage: 100 / portfolioData.length,
|
||||||
|
reason: 'Equal weight baseline'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Adjust based on performance
|
||||||
|
allocations = this.adjustForPerformance(allocations, portfolioData);
|
||||||
|
|
||||||
|
// Adjust based on risk
|
||||||
|
allocations = this.adjustForRisk(allocations, metrics, riskTolerance);
|
||||||
|
|
||||||
|
// Adjust based on correlations
|
||||||
|
if (correlations.length > 0) {
|
||||||
|
allocations = this.adjustForCorrelations(allocations, correlations);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply constraints
|
||||||
|
allocations = this.applyAllocationConstraints(allocations);
|
||||||
|
|
||||||
|
return allocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustForPerformance(allocations, portfolioData) {
|
||||||
|
// Increase allocation to better performers
|
||||||
|
const performances = portfolioData.map(p => ({
|
||||||
|
symbol: p.symbol,
|
||||||
|
performance: p.pnlPercentage
|
||||||
|
})).sort((a, b) => b.performance - a.performance);
|
||||||
|
|
||||||
|
return allocations.map(alloc => {
|
||||||
|
const perf = performances.find(p => p.symbol === alloc.symbol);
|
||||||
|
const rank = performances.indexOf(perf);
|
||||||
|
|
||||||
|
// Top 1/3 get boost, bottom 1/3 get reduction
|
||||||
|
if (rank < performances.length / 3) {
|
||||||
|
alloc.percentage *= 1.2;
|
||||||
|
alloc.reason = 'Strong performance';
|
||||||
|
} else if (rank > (performances.length * 2 / 3)) {
|
||||||
|
alloc.percentage *= 0.8;
|
||||||
|
alloc.reason = 'Weak performance';
|
||||||
|
}
|
||||||
|
|
||||||
|
return alloc;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
applyAllocationConstraints(allocations) {
|
||||||
|
// No single asset > 40%
|
||||||
|
const maxAllocation = 40;
|
||||||
|
// No single asset < 5%
|
||||||
|
const minAllocation = 5;
|
||||||
|
|
||||||
|
// Normalize to ensure sum = 100
|
||||||
|
const total = allocations.reduce((sum, a) => sum + a.percentage, 0);
|
||||||
|
allocations = allocations.map(a => ({
|
||||||
|
...a,
|
||||||
|
percentage: (a.percentage / total) * 100
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Apply constraints
|
||||||
|
allocations = allocations.map(alloc => ({
|
||||||
|
...alloc,
|
||||||
|
percentage: Math.min(Math.max(alloc.percentage, minAllocation), maxAllocation)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Re-normalize
|
||||||
|
const newTotal = allocations.reduce((sum, a) => sum + a.percentage, 0);
|
||||||
|
return allocations.map(a => ({
|
||||||
|
...a,
|
||||||
|
percentage: (a.percentage / newTotal) * 100
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Portfolio Visualization
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class PortfolioVisualizer {
|
||||||
|
generateReport(analysis) {
|
||||||
|
return `
|
||||||
|
╔════════════════════════════════════════════════════════════════════════════╗
|
||||||
|
║ PORTFOLIO ANALYSIS REPORT ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ SUMMARY METRICS ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ Total Value: $${analysis.metrics.totalValue.toFixed(2).padEnd(55)} ║
|
||||||
|
║ Total Cost: $${analysis.metrics.totalCost.toFixed(2).padEnd(55)} ║
|
||||||
|
║ Total P&L: ${this.formatPnL(analysis.metrics.totalPnL).padEnd(56)} ║
|
||||||
|
║ Total Return: ${this.formatPercentage(analysis.metrics.totalReturn).padEnd(56)} ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ RISK METRICS ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ Risk Level: ${analysis.riskAssessment.level.padEnd(56)} ║
|
||||||
|
║ Risk Score: ${(analysis.riskAssessment.overallScore + '/100').padEnd(56)} ║
|
||||||
|
║ Sharpe Ratio: ${analysis.metrics.sharpeRatio?.value || 'N/A'.padEnd(56)} ║
|
||||||
|
║ Annual Volatility: ${analysis.metrics.volatility?.annual + '%' || 'N/A'.padEnd(56)} ║
|
||||||
|
║ Max Drawdown: ${analysis.metrics.maxDrawdown?.percentage + '%' || 'N/A'.padEnd(56)} ║
|
||||||
|
║ VaR (95%): ${analysis.metrics.var95?.dollarAmount || 'N/A'.padEnd(56)} ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ ASSET ALLOCATION ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
${this.formatAllocationTable(analysis.metrics.allocation)}
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ CONCENTRATION ANALYSIS ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ Herfindahl Index: ${analysis.metrics.concentration.herfindahlIndex.padEnd(56)} ║
|
||||||
|
║ Effective Assets: ${analysis.metrics.concentration.effectiveAssets.padEnd(56)} ║
|
||||||
|
║ Top Asset: ${analysis.metrics.concentration.topAssetConcentration + '%'.padEnd(56)} ║
|
||||||
|
║ Top 3 Assets: ${analysis.metrics.concentration.top3Concentration + '%'.padEnd(56)} ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ REBALANCING RECOMMENDATIONS ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
${this.formatRebalancingActions(analysis.rebalancingPlan.actions)}
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
║ RISK FACTORS ║
|
||||||
|
╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
${this.formatRiskFactors(analysis.riskAssessment.factors)}
|
||||||
|
╚════════════════════════════════════════════════════════════════════════════╝
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatAllocationTable(allocations) {
|
||||||
|
return allocations.slice(0, 5).map(a =>
|
||||||
|
`║ ${a.symbol.padEnd(10)} ${(a.percentage.toFixed(2) + '%').padEnd(10)} $${a.value.toFixed(2).padEnd(15)} ${this.getAllocationBar(a.percentage).padEnd(20)} ║`
|
||||||
|
).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllocationBar(percentage) {
|
||||||
|
const barLength = Math.floor(percentage / 5);
|
||||||
|
return '█'.repeat(Math.min(barLength, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
formatRebalancingActions(actions) {
|
||||||
|
if (actions.length === 0) {
|
||||||
|
return '║ Portfolio is well-balanced. No actions required. ║';
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions.slice(0, 3).map(a =>
|
||||||
|
`║ ${a.action.padEnd(5)} ${a.symbol.padEnd(6)} $${a.amount.padEnd(10)} (${a.currentPercentage}% → ${a.targetPercentage}%)`.padEnd(77) + '║'
|
||||||
|
).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
formatRiskFactors(factors) {
|
||||||
|
if (factors.length === 0) {
|
||||||
|
return '║ No significant risk factors identified. ║';
|
||||||
|
}
|
||||||
|
|
||||||
|
return factors.map(f =>
|
||||||
|
`║ [${f.severity}] ${f.type}: ${f.description}`.padEnd(77) + '║'
|
||||||
|
).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
formatPnL(value) {
|
||||||
|
const formatted = `$${Math.abs(value).toFixed(2)}`;
|
||||||
|
return value >= 0 ? `+${formatted} ` : `-${formatted} `;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatPercentage(value) {
|
||||||
|
const formatted = `${Math.abs(value).toFixed(2)}%`;
|
||||||
|
return value >= 0 ? `+${formatted} ` : `-${formatted} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Optimization Strategies
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class PortfolioOptimizer {
|
||||||
|
suggestOptimizations(metrics) {
|
||||||
|
const suggestions = [];
|
||||||
|
|
||||||
|
// Diversification suggestions
|
||||||
|
if (metrics.concentration.effectiveAssets < 3) {
|
||||||
|
suggestions.push({
|
||||||
|
priority: 'HIGH',
|
||||||
|
category: 'DIVERSIFICATION',
|
||||||
|
action: 'Add more uncorrelated assets',
|
||||||
|
benefit: 'Reduce concentration risk by 30-40%',
|
||||||
|
implementation: `
|
||||||
|
Consider adding:
|
||||||
|
- Large cap altcoins (ETH, BNB) if heavily in BTC
|
||||||
|
- DeFi tokens if heavily in L1s
|
||||||
|
- Stablecoins for risk reduction
|
||||||
|
`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebalancing suggestions
|
||||||
|
if (metrics.allocation.some(a => a.deviation > 10)) {
|
||||||
|
suggestions.push({
|
||||||
|
priority: 'MEDIUM',
|
||||||
|
category: 'REBALANCING',
|
||||||
|
action: 'Rebalance to target allocations',
|
||||||
|
benefit: 'Improve risk-adjusted returns',
|
||||||
|
implementation: `
|
||||||
|
Set up periodic rebalancing:
|
||||||
|
- Monthly for volatile markets
|
||||||
|
- Quarterly for stable markets
|
||||||
|
- Threshold-based (when deviation > 15%)
|
||||||
|
`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Risk management suggestions
|
||||||
|
if (!metrics.stopLossesSet) {
|
||||||
|
suggestions.push({
|
||||||
|
priority: 'HIGH',
|
||||||
|
category: 'RISK_MANAGEMENT',
|
||||||
|
action: 'Implement stop-loss orders',
|
||||||
|
benefit: 'Limit downside risk',
|
||||||
|
implementation: `
|
||||||
|
Recommended stop-loss levels:
|
||||||
|
- Conservative: 15% below entry
|
||||||
|
- Moderate: 25% below entry
|
||||||
|
- Aggressive: 35% below entry
|
||||||
|
`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performance suggestions
|
||||||
|
if (metrics.sharpeRatio && metrics.sharpeRatio.value < 0.5) {
|
||||||
|
suggestions.push({
|
||||||
|
priority: 'MEDIUM',
|
||||||
|
category: 'PERFORMANCE',
|
||||||
|
action: 'Improve risk-adjusted returns',
|
||||||
|
benefit: 'Better Sharpe ratio',
|
||||||
|
implementation: `
|
||||||
|
Options to improve:
|
||||||
|
- Reduce allocation to high-volatility assets
|
||||||
|
- Add yield-generating positions (staking, lending)
|
||||||
|
- Consider market-neutral strategies
|
||||||
|
`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function executePortfolioAnalysis() {
|
||||||
|
try {
|
||||||
|
const analyzer = new PortfolioAnalyzer();
|
||||||
|
const visualizer = new PortfolioVisualizer();
|
||||||
|
|
||||||
|
// Get all open positions
|
||||||
|
const positions = await getOpenPositions();
|
||||||
|
|
||||||
|
if (positions.length === 0) {
|
||||||
|
console.log('No positions found. Start by tracking some positions first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run analysis
|
||||||
|
console.log('Analyzing portfolio...');
|
||||||
|
const analysis = await analyzer.analyzePortfolio(positions);
|
||||||
|
|
||||||
|
// Display report
|
||||||
|
const report = visualizer.generateReport(analysis);
|
||||||
|
console.log(report);
|
||||||
|
|
||||||
|
// Save analysis
|
||||||
|
await saveAnalysis(analysis);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Portfolio analysis failed:', error.message);
|
||||||
|
|
||||||
|
if (error.code === 'INSUFFICIENT_DATA') {
|
||||||
|
console.log('Not enough historical data for full analysis. Some metrics may be unavailable.');
|
||||||
|
} else if (error.code === 'API_ERROR') {
|
||||||
|
console.log('Failed to fetch market data. Please check your internet connection.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This comprehensive portfolio analysis command provides institutional-grade analytics for crypto portfolios with actionable insights and optimization recommendations.
|
||||||
418
commands/track-position.md
Normal file
418
commands/track-position.md
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
---
|
||||||
|
description: Track crypto positions with entry prices, current values, and PnL calculations
|
||||||
|
shortcut: tp
|
||||||
|
---
|
||||||
|
|
||||||
|
# Track Crypto Position
|
||||||
|
|
||||||
|
Comprehensive position tracking for cryptocurrency investments with real-time price updates and advanced analytics.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
When the user wants to track a crypto position, gather the following information and implement a complete tracking system:
|
||||||
|
|
||||||
|
### Required Information
|
||||||
|
- **Symbol**: The cryptocurrency ticker (BTC, ETH, SOL, etc.)
|
||||||
|
- **Entry Price**: Purchase price per unit
|
||||||
|
- **Quantity**: Amount purchased
|
||||||
|
- **Entry Date**: When the position was opened
|
||||||
|
- **Exchange**: Where the trade was executed (optional)
|
||||||
|
- **Target Price**: Profit target (optional)
|
||||||
|
- **Stop Loss**: Risk management level (optional)
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1. Database Schema
|
||||||
|
|
||||||
|
Create a structured database to track positions:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS crypto_positions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
symbol VARCHAR(10) NOT NULL,
|
||||||
|
entry_price DECIMAL(20,8) NOT NULL,
|
||||||
|
quantity DECIMAL(20,8) NOT NULL,
|
||||||
|
entry_date TIMESTAMP NOT NULL,
|
||||||
|
current_price DECIMAL(20,8),
|
||||||
|
last_updated TIMESTAMP,
|
||||||
|
exchange VARCHAR(50),
|
||||||
|
target_price DECIMAL(20,8),
|
||||||
|
stop_loss DECIMAL(20,8),
|
||||||
|
status VARCHAR(20) DEFAULT 'open',
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS position_history (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
position_id UUID REFERENCES crypto_positions(id),
|
||||||
|
price DECIMAL(20,8) NOT NULL,
|
||||||
|
value DECIMAL(20,8) NOT NULL,
|
||||||
|
pnl DECIMAL(20,8) NOT NULL,
|
||||||
|
pnl_percentage DECIMAL(10,4) NOT NULL,
|
||||||
|
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_positions_symbol ON crypto_positions(symbol);
|
||||||
|
CREATE INDEX idx_positions_status ON crypto_positions(status);
|
||||||
|
CREATE INDEX idx_history_position ON position_history(position_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Position Tracking Class
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class CryptoPositionTracker {
|
||||||
|
constructor() {
|
||||||
|
this.priceFeeds = {
|
||||||
|
coingecko: 'https://api.coingecko.com/api/v3',
|
||||||
|
binance: 'https://api.binance.com/api/v3',
|
||||||
|
coinbase: 'https://api.coinbase.com/v2'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async trackPosition(positionData) {
|
||||||
|
const {
|
||||||
|
symbol,
|
||||||
|
entryPrice,
|
||||||
|
quantity,
|
||||||
|
entryDate,
|
||||||
|
exchange = 'Unknown',
|
||||||
|
targetPrice = null,
|
||||||
|
stopLoss = null,
|
||||||
|
notes = ''
|
||||||
|
} = positionData;
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
this.validatePositionData(positionData);
|
||||||
|
|
||||||
|
// Get current price
|
||||||
|
const currentPrice = await this.getCurrentPrice(symbol);
|
||||||
|
|
||||||
|
// Calculate metrics
|
||||||
|
const metrics = this.calculateMetrics({
|
||||||
|
entryPrice,
|
||||||
|
quantity,
|
||||||
|
currentPrice
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store position
|
||||||
|
const position = await this.storePosition({
|
||||||
|
symbol: symbol.toUpperCase(),
|
||||||
|
entry_price: entryPrice,
|
||||||
|
quantity,
|
||||||
|
entry_date: entryDate,
|
||||||
|
current_price: currentPrice,
|
||||||
|
exchange,
|
||||||
|
target_price: targetPrice,
|
||||||
|
stop_loss: stopLoss,
|
||||||
|
notes
|
||||||
|
});
|
||||||
|
|
||||||
|
// Record initial history
|
||||||
|
await this.recordHistory(position.id, currentPrice, metrics);
|
||||||
|
|
||||||
|
return {
|
||||||
|
position,
|
||||||
|
metrics,
|
||||||
|
analysis: this.analyzePosition(position, metrics)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateMetrics({ entryPrice, quantity, currentPrice }) {
|
||||||
|
const entryValue = entryPrice * quantity;
|
||||||
|
const currentValue = currentPrice * quantity;
|
||||||
|
const unrealizedPnL = currentValue - entryValue;
|
||||||
|
const pnlPercentage = ((currentValue - entryValue) / entryValue) * 100;
|
||||||
|
|
||||||
|
return {
|
||||||
|
entryValue: parseFloat(entryValue.toFixed(2)),
|
||||||
|
currentValue: parseFloat(currentValue.toFixed(2)),
|
||||||
|
unrealizedPnL: parseFloat(unrealizedPnL.toFixed(2)),
|
||||||
|
pnlPercentage: parseFloat(pnlPercentage.toFixed(2)),
|
||||||
|
roi: pnlPercentage,
|
||||||
|
riskRewardRatio: this.calculateRiskReward({
|
||||||
|
entryPrice,
|
||||||
|
currentPrice,
|
||||||
|
targetPrice: position.target_price,
|
||||||
|
stopLoss: position.stop_loss
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateRiskReward({ entryPrice, currentPrice, targetPrice, stopLoss }) {
|
||||||
|
if (!targetPrice || !stopLoss) return null;
|
||||||
|
|
||||||
|
const potentialReward = targetPrice - entryPrice;
|
||||||
|
const potentialRisk = entryPrice - stopLoss;
|
||||||
|
|
||||||
|
if (potentialRisk === 0) return null;
|
||||||
|
|
||||||
|
return parseFloat((potentialReward / potentialRisk).toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzePosition(position, metrics) {
|
||||||
|
const analysis = {
|
||||||
|
status: this.determineStatus(metrics),
|
||||||
|
recommendation: this.getRecommendation(position, metrics),
|
||||||
|
risks: this.identifyRisks(position, metrics),
|
||||||
|
opportunities: this.identifyOpportunities(position, metrics)
|
||||||
|
};
|
||||||
|
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
determineStatus(metrics) {
|
||||||
|
if (metrics.pnlPercentage > 20) return 'STRONG_PROFIT';
|
||||||
|
if (metrics.pnlPercentage > 5) return 'PROFIT';
|
||||||
|
if (metrics.pnlPercentage > -5) return 'NEUTRAL';
|
||||||
|
if (metrics.pnlPercentage > -20) return 'LOSS';
|
||||||
|
return 'SIGNIFICANT_LOSS';
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecommendation(position, metrics) {
|
||||||
|
const recommendations = [];
|
||||||
|
|
||||||
|
// Check stop loss
|
||||||
|
if (position.stop_loss && position.current_price <= position.stop_loss) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'EXIT',
|
||||||
|
reason: 'Stop loss hit',
|
||||||
|
urgency: 'HIGH'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check target
|
||||||
|
if (position.target_price && position.current_price >= position.target_price) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'TAKE_PROFIT',
|
||||||
|
reason: 'Target price reached',
|
||||||
|
urgency: 'MEDIUM'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check significant profit
|
||||||
|
if (metrics.pnlPercentage > 50) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'PARTIAL_PROFIT',
|
||||||
|
reason: 'Consider taking partial profits (50%+ gain)',
|
||||||
|
urgency: 'LOW'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check significant loss
|
||||||
|
if (metrics.pnlPercentage < -30 && !position.stop_loss) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'SET_STOP_LOSS',
|
||||||
|
reason: 'Significant loss without stop loss protection',
|
||||||
|
urgency: 'HIGH'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return recommendations;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCurrentPrice(symbol) {
|
||||||
|
// Implementation would fetch from multiple sources
|
||||||
|
// and return average or most reliable price
|
||||||
|
try {
|
||||||
|
const prices = await Promise.all([
|
||||||
|
this.fetchCoingeckoPrice(symbol),
|
||||||
|
this.fetchBinancePrice(symbol)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return prices.reduce((sum, price) => sum + price, 0) / prices.length;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to fetch price for ${symbol}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAllPositions() {
|
||||||
|
const openPositions = await this.getOpenPositions();
|
||||||
|
const updates = [];
|
||||||
|
|
||||||
|
for (const position of openPositions) {
|
||||||
|
try {
|
||||||
|
const currentPrice = await this.getCurrentPrice(position.symbol);
|
||||||
|
const metrics = this.calculateMetrics({
|
||||||
|
entryPrice: position.entry_price,
|
||||||
|
quantity: position.quantity,
|
||||||
|
currentPrice
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.updatePosition(position.id, {
|
||||||
|
current_price: currentPrice,
|
||||||
|
last_updated: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.recordHistory(position.id, currentPrice, metrics);
|
||||||
|
|
||||||
|
updates.push({
|
||||||
|
symbol: position.symbol,
|
||||||
|
currentPrice,
|
||||||
|
metrics,
|
||||||
|
status: 'UPDATED'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
updates.push({
|
||||||
|
symbol: position.symbol,
|
||||||
|
status: 'ERROR',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Display Format
|
||||||
|
|
||||||
|
When displaying position information, format it clearly:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function displayPosition(position, metrics) {
|
||||||
|
const output = `
|
||||||
|
╔════════════════════════════════════════════════════════════════╗
|
||||||
|
║ CRYPTO POSITION TRACKER ║
|
||||||
|
╠════════════════════════════════════════════════════════════════╣
|
||||||
|
║ Symbol: ${position.symbol.padEnd(48)} ║
|
||||||
|
║ Entry Price: $${position.entry_price.toFixed(2).padEnd(47)} ║
|
||||||
|
║ Current Price: $${position.current_price.toFixed(2).padEnd(47)} ║
|
||||||
|
║ Quantity: ${position.quantity.toString().padEnd(48)} ║
|
||||||
|
╠════════════════════════════════════════════════════════════════╣
|
||||||
|
║ METRICS ║
|
||||||
|
╠════════════════════════════════════════════════════════════════╣
|
||||||
|
║ Entry Value: $${metrics.entryValue.toFixed(2).padEnd(47)} ║
|
||||||
|
║ Current Value: $${metrics.currentValue.toFixed(2).padEnd(47)} ║
|
||||||
|
║ Unrealized P&L: ${formatPnL(metrics.unrealizedPnL).padEnd(47)} ║
|
||||||
|
║ P&L %: ${formatPnLPercentage(metrics.pnlPercentage).padEnd(48)} ║
|
||||||
|
╠════════════════════════════════════════════════════════════════╣
|
||||||
|
║ Status: ${determineStatusEmoji(metrics.pnlPercentage)} ${metrics.status.padEnd(43)} ║
|
||||||
|
╚════════════════════════════════════════════════════════════════╝
|
||||||
|
`;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPnL(value) {
|
||||||
|
const formatted = `$${Math.abs(value).toFixed(2)}`;
|
||||||
|
if (value >= 0) {
|
||||||
|
return `+${formatted} `;
|
||||||
|
} else {
|
||||||
|
return `-${formatted} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPnLPercentage(percentage) {
|
||||||
|
const formatted = `${Math.abs(percentage).toFixed(2)}%`;
|
||||||
|
if (percentage >= 0) {
|
||||||
|
return `+${formatted} `;
|
||||||
|
} else {
|
||||||
|
return `-${formatted} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function determineStatusEmoji(percentage) {
|
||||||
|
if (percentage > 20) return '';
|
||||||
|
if (percentage > 5) return '';
|
||||||
|
if (percentage > -5) return '';
|
||||||
|
if (percentage > -20) return '️';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Alert System
|
||||||
|
|
||||||
|
Set up automatic alerts for significant events:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class PositionAlertSystem {
|
||||||
|
constructor() {
|
||||||
|
this.alertThresholds = {
|
||||||
|
profitTarget: 0.20, // 20% profit
|
||||||
|
lossWarning: -0.10, // 10% loss
|
||||||
|
criticalLoss: -0.25, // 25% loss
|
||||||
|
volatilitySpike: 0.15 // 15% daily move
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkAlerts(position, previousPrice, currentPrice) {
|
||||||
|
const alerts = [];
|
||||||
|
const priceChange = ((currentPrice - previousPrice) / previousPrice) * 100;
|
||||||
|
|
||||||
|
// Check profit targets
|
||||||
|
if (metrics.pnlPercentage >= this.alertThresholds.profitTarget * 100) {
|
||||||
|
alerts.push({
|
||||||
|
type: 'PROFIT_TARGET',
|
||||||
|
message: ` ${position.symbol} hit profit target: +${metrics.pnlPercentage.toFixed(2)}%`,
|
||||||
|
severity: 'INFO',
|
||||||
|
action: 'Consider taking profits'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check loss warnings
|
||||||
|
if (metrics.pnlPercentage <= this.alertThresholds.lossWarning * 100 &&
|
||||||
|
metrics.pnlPercentage > this.alertThresholds.criticalLoss * 100) {
|
||||||
|
alerts.push({
|
||||||
|
type: 'LOSS_WARNING',
|
||||||
|
message: `️ ${position.symbol} loss warning: ${metrics.pnlPercentage.toFixed(2)}%`,
|
||||||
|
severity: 'WARNING',
|
||||||
|
action: 'Review position and consider stop loss'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check critical loss
|
||||||
|
if (metrics.pnlPercentage <= this.alertThresholds.criticalLoss * 100) {
|
||||||
|
alerts.push({
|
||||||
|
type: 'CRITICAL_LOSS',
|
||||||
|
message: ` ${position.symbol} critical loss: ${metrics.pnlPercentage.toFixed(2)}%`,
|
||||||
|
severity: 'CRITICAL',
|
||||||
|
action: 'Immediate review required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check volatility
|
||||||
|
if (Math.abs(priceChange) >= this.alertThresholds.volatilitySpike * 100) {
|
||||||
|
alerts.push({
|
||||||
|
type: 'VOLATILITY_SPIKE',
|
||||||
|
message: ` ${position.symbol} volatility spike: ${priceChange > 0 ? '+' : ''}${priceChange.toFixed(2)}% in 24h`,
|
||||||
|
severity: 'INFO',
|
||||||
|
action: 'Monitor closely for opportunities or risks'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return alerts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Always implement comprehensive error handling:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
try {
|
||||||
|
const position = await tracker.trackPosition({
|
||||||
|
symbol: 'BTC',
|
||||||
|
entryPrice: 45000,
|
||||||
|
quantity: 0.5,
|
||||||
|
entryDate: new Date('2024-01-01'),
|
||||||
|
targetPrice: 60000,
|
||||||
|
stopLoss: 40000
|
||||||
|
});
|
||||||
|
|
||||||
|
displayPosition(position.position, position.metrics);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'INVALID_SYMBOL') {
|
||||||
|
console.error(`Invalid cryptocurrency symbol: ${error.symbol}`);
|
||||||
|
} else if (error.code === 'API_ERROR') {
|
||||||
|
console.error(`Failed to fetch price data: ${error.message}`);
|
||||||
|
} else {
|
||||||
|
console.error(`Unexpected error: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This command provides comprehensive position tracking with real-time updates, PnL calculations, risk analysis, and actionable recommendations for crypto investments.
|
||||||
89
plugin.lock.json
Normal file
89
plugin.lock.json
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:jeremylongshore/claude-code-plugins-plus:plugins/crypto/crypto-portfolio-tracker",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "4f7c9da8bf1061171006afc775ffbbb569c6c1ba",
|
||||||
|
"treeHash": "7e9415002c8fb95dbce1e035c87f046b75034dd0588c114921b643e471fd9172",
|
||||||
|
"generatedAt": "2025-11-28T10:18:16.306418Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "crypto-portfolio-tracker",
|
||||||
|
"description": "Professional crypto portfolio tracking with real-time prices, PnL analysis, and risk metrics",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "a1e503a8b1c6d7828555b4811ed4121e0d736d56ab54a9a76c252913cf934889"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "61b237256e23633ca78504410690484a246131e6e7ed2b4846133bfbc9f9af82"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/track-position.md",
|
||||||
|
"sha256": "c1a5e110859ce0f8f80ca78f81409c0541b7c5df61b64cc7080c6841829ec3ae"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/portfolio-analysis.md",
|
||||||
|
"sha256": "37aa1243c8db4b41f519b6eadb4dee9ef10ba43e51b91da2becfc47cf91c1680"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/references/examples.md",
|
||||||
|
"sha256": "922bbc3c4ebf38b76f515b5c1998ebde6bf902233e00e2c5a0e9176f975a7572"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/references/best-practices.md",
|
||||||
|
"sha256": "c8f32b3566252f50daacd346d7045a1060c718ef5cfb07c55a0f2dec5f1fb39e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/references/README.md",
|
||||||
|
"sha256": "779b6cf97de7e9c8db5ba04d28f94cd18636931a1e6c82c4e5b3a57ec6db8eae"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/scripts/helper-template.sh",
|
||||||
|
"sha256": "0881d5660a8a7045550d09ae0acc15642c24b70de6f08808120f47f86ccdf077"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/scripts/validation.sh",
|
||||||
|
"sha256": "92551a29a7f512d2036e4f1fb46c2a3dc6bff0f7dde4a9f699533e446db48502"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/scripts/README.md",
|
||||||
|
"sha256": "fec629c4d3b22cd08f9e81ad3481ee9ef44538da009943a94cc5ff7637157e56"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/assets/test-data.json",
|
||||||
|
"sha256": "ac17dca3d6e253a5f39f2a2f1b388e5146043756b05d9ce7ac53a0042eee139d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/assets/README.md",
|
||||||
|
"sha256": "dd3a93632741c98e438badefe6b48e7589ba40170afbd30867f247afddcef4e9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/assets/skill-schema.json",
|
||||||
|
"sha256": "f5639ba823a24c9ac4fb21444c0717b7aefde1a4993682897f5bf544f863c2cd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-adapter/assets/config-template.json",
|
||||||
|
"sha256": "0c2ba33d2d3c5ccb266c0848fc43caa68a2aa6a80ff315d4b378352711f83e1c"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "7e9415002c8fb95dbce1e035c87f046b75034dd0588c114921b643e471fd9172"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
7
skills/skill-adapter/assets/README.md
Normal file
7
skills/skill-adapter/assets/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Assets
|
||||||
|
|
||||||
|
Bundled resources for crypto-portfolio-tracker skill
|
||||||
|
|
||||||
|
- [ ] rebalancing_template.xlsx: Excel template for visualizing and implementing rebalancing recommendations.
|
||||||
|
- [ ] portfolio_analysis_dashboard.html: HTML dashboard for displaying portfolio risk metrics and performance.
|
||||||
|
- [ ] example_portfolio.json: Example portfolio data in JSON format for testing and demonstration.
|
||||||
32
skills/skill-adapter/assets/config-template.json
Normal file
32
skills/skill-adapter/assets/config-template.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"skill": {
|
||||||
|
"name": "skill-name",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"enabled": true,
|
||||||
|
"settings": {
|
||||||
|
"verbose": false,
|
||||||
|
"autoActivate": true,
|
||||||
|
"toolRestrictions": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"triggers": {
|
||||||
|
"keywords": [
|
||||||
|
"example-trigger-1",
|
||||||
|
"example-trigger-2"
|
||||||
|
],
|
||||||
|
"patterns": []
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"allowed": [
|
||||||
|
"Read",
|
||||||
|
"Grep",
|
||||||
|
"Bash"
|
||||||
|
],
|
||||||
|
"restricted": []
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Plugin Author",
|
||||||
|
"category": "general",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
28
skills/skill-adapter/assets/skill-schema.json
Normal file
28
skills/skill-adapter/assets/skill-schema.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Claude Skill Configuration",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name", "description"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-z0-9-]+$",
|
||||||
|
"maxLength": 64,
|
||||||
|
"description": "Skill identifier (lowercase, hyphens only)"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1024,
|
||||||
|
"description": "What the skill does and when to use it"
|
||||||
|
},
|
||||||
|
"allowed-tools": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Comma-separated list of allowed tools"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
||||||
|
"description": "Semantic version (x.y.z)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
skills/skill-adapter/assets/test-data.json
Normal file
27
skills/skill-adapter/assets/test-data.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"testCases": [
|
||||||
|
{
|
||||||
|
"name": "Basic activation test",
|
||||||
|
"input": "trigger phrase example",
|
||||||
|
"expected": {
|
||||||
|
"activated": true,
|
||||||
|
"toolsUsed": ["Read", "Grep"],
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Complex workflow test",
|
||||||
|
"input": "multi-step trigger example",
|
||||||
|
"expected": {
|
||||||
|
"activated": true,
|
||||||
|
"steps": 3,
|
||||||
|
"toolsUsed": ["Read", "Write", "Bash"],
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fixtures": {
|
||||||
|
"sampleInput": "example data",
|
||||||
|
"expectedOutput": "processed result"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
skills/skill-adapter/references/README.md
Normal file
9
skills/skill-adapter/references/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# References
|
||||||
|
|
||||||
|
Bundled resources for crypto-portfolio-tracker skill
|
||||||
|
|
||||||
|
- [ ] crypto_risk_metrics.md: Detailed explanation of risk metrics like Sharpe ratio, Sortino ratio, and maximum drawdown.
|
||||||
|
- [ ] modern_portfolio_theory.md: Overview of Modern Portfolio Theory and its application to crypto portfolio optimization.
|
||||||
|
- [ ] exchange_api_documentation.md: Documentation for accessing real-time price data from CoinGecko, Binance, and Coinbase.
|
||||||
|
- [ ] tax_implications_crypto_trading.md: Guide to tax-aware trading suggestions for crypto rebalancing.
|
||||||
|
- [ ] portfolio_diversification_strategies.md: Strategies for improving portfolio diversification in the crypto space.
|
||||||
69
skills/skill-adapter/references/best-practices.md
Normal file
69
skills/skill-adapter/references/best-practices.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Skill Best Practices
|
||||||
|
|
||||||
|
Guidelines for optimal skill usage and development.
|
||||||
|
|
||||||
|
## For Users
|
||||||
|
|
||||||
|
### Activation Best Practices
|
||||||
|
|
||||||
|
1. **Use Clear Trigger Phrases**
|
||||||
|
- Match phrases from skill description
|
||||||
|
- Be specific about intent
|
||||||
|
- Provide necessary context
|
||||||
|
|
||||||
|
2. **Provide Sufficient Context**
|
||||||
|
- Include relevant file paths
|
||||||
|
- Specify scope of analysis
|
||||||
|
- Mention any constraints
|
||||||
|
|
||||||
|
3. **Understand Tool Permissions**
|
||||||
|
- Check allowed-tools in frontmatter
|
||||||
|
- Know what the skill can/cannot do
|
||||||
|
- Request appropriate actions
|
||||||
|
|
||||||
|
### Workflow Optimization
|
||||||
|
|
||||||
|
- Start with simple requests
|
||||||
|
- Build up to complex workflows
|
||||||
|
- Verify each step before proceeding
|
||||||
|
- Use skill consistently for related tasks
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
### Skill Development Guidelines
|
||||||
|
|
||||||
|
1. **Clear Descriptions**
|
||||||
|
- Include explicit trigger phrases
|
||||||
|
- Document all capabilities
|
||||||
|
- Specify limitations
|
||||||
|
|
||||||
|
2. **Proper Tool Permissions**
|
||||||
|
- Use minimal necessary tools
|
||||||
|
- Document security implications
|
||||||
|
- Test with restricted tools
|
||||||
|
|
||||||
|
3. **Comprehensive Documentation**
|
||||||
|
- Provide usage examples
|
||||||
|
- Document common pitfalls
|
||||||
|
- Include troubleshooting guide
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
- Keep version updated
|
||||||
|
- Test after tool updates
|
||||||
|
- Monitor user feedback
|
||||||
|
- Iterate on descriptions
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
- Scope skills to specific domains
|
||||||
|
- Avoid overlapping trigger phrases
|
||||||
|
- Keep descriptions under 1024 chars
|
||||||
|
- Test activation reliability
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Never include secrets in skill files
|
||||||
|
- Validate all inputs
|
||||||
|
- Use read-only tools when possible
|
||||||
|
- Document security requirements
|
||||||
70
skills/skill-adapter/references/examples.md
Normal file
70
skills/skill-adapter/references/examples.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Skill Usage Examples
|
||||||
|
|
||||||
|
This document provides practical examples of how to use this skill effectively.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Example 1: Simple Activation
|
||||||
|
|
||||||
|
**User Request:**
|
||||||
|
```
|
||||||
|
[Describe trigger phrase here]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Skill Response:**
|
||||||
|
1. Analyzes the request
|
||||||
|
2. Performs the required action
|
||||||
|
3. Returns results
|
||||||
|
|
||||||
|
### Example 2: Complex Workflow
|
||||||
|
|
||||||
|
**User Request:**
|
||||||
|
```
|
||||||
|
[Describe complex scenario]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
1. Step 1: Initial analysis
|
||||||
|
2. Step 2: Data processing
|
||||||
|
3. Step 3: Result generation
|
||||||
|
4. Step 4: Validation
|
||||||
|
|
||||||
|
## Advanced Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Chaining Operations
|
||||||
|
|
||||||
|
Combine this skill with other tools:
|
||||||
|
```
|
||||||
|
Step 1: Use this skill for [purpose]
|
||||||
|
Step 2: Chain with [other tool]
|
||||||
|
Step 3: Finalize with [action]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: Error Handling
|
||||||
|
|
||||||
|
If issues occur:
|
||||||
|
- Check trigger phrase matches
|
||||||
|
- Verify context is available
|
||||||
|
- Review allowed-tools permissions
|
||||||
|
|
||||||
|
## Tips & Best Practices
|
||||||
|
|
||||||
|
- ✅ Be specific with trigger phrases
|
||||||
|
- ✅ Provide necessary context
|
||||||
|
- ✅ Check tool permissions match needs
|
||||||
|
- ❌ Avoid vague requests
|
||||||
|
- ❌ Don't mix unrelated tasks
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
**Issue:** Skill doesn't activate
|
||||||
|
**Solution:** Use exact trigger phrases from description
|
||||||
|
|
||||||
|
**Issue:** Unexpected results
|
||||||
|
**Solution:** Check input format and context
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- Main SKILL.md for full documentation
|
||||||
|
- scripts/ for automation helpers
|
||||||
|
- assets/ for configuration examples
|
||||||
9
skills/skill-adapter/scripts/README.md
Normal file
9
skills/skill-adapter/scripts/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Scripts
|
||||||
|
|
||||||
|
Bundled resources for crypto-portfolio-tracker skill
|
||||||
|
|
||||||
|
- [ ] calculate_risk_metrics.py: Calculates Sharpe ratio, Sortino ratio, and maximum drawdown for a given portfolio.
|
||||||
|
- [ ] rebalance_portfolio.py: Generates rebalancing recommendations based on Modern Portfolio Theory.
|
||||||
|
- [ ] set_price_alert.py: Sets up price alerts for specific cryptocurrencies and sends notifications.
|
||||||
|
- [ ] track_position.py: Tracks a new crypto position, including entry price, quantity, and date.
|
||||||
|
- [ ] validate_api_keys.py: Validates API keys for various exchanges (CoinGecko, Binance, Coinbase).
|
||||||
42
skills/skill-adapter/scripts/helper-template.sh
Executable file
42
skills/skill-adapter/scripts/helper-template.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Helper script template for skill automation
|
||||||
|
# Customize this for your skill's specific needs
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
function show_usage() {
|
||||||
|
echo "Usage: $0 [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
echo " -v, --verbose Enable verbose output"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
VERBOSE=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
show_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-v|--verbose)
|
||||||
|
VERBOSE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Your skill logic here
|
||||||
|
if [ "$VERBOSE" = true ]; then
|
||||||
|
echo "Running skill automation..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Complete"
|
||||||
32
skills/skill-adapter/scripts/validation.sh
Executable file
32
skills/skill-adapter/scripts/validation.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Skill validation helper
|
||||||
|
# Validates skill activation and functionality
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔍 Validating skill..."
|
||||||
|
|
||||||
|
# Check if SKILL.md exists
|
||||||
|
if [ ! -f "../SKILL.md" ]; then
|
||||||
|
echo "❌ Error: SKILL.md not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate frontmatter
|
||||||
|
if ! grep -q "^---$" "../SKILL.md"; then
|
||||||
|
echo "❌ Error: No frontmatter found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required fields
|
||||||
|
if ! grep -q "^name:" "../SKILL.md"; then
|
||||||
|
echo "❌ Error: Missing 'name' field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "^description:" "../SKILL.md"; then
|
||||||
|
echo "❌ Error: Missing 'description' field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Skill validation passed"
|
||||||
Reference in New Issue
Block a user