skayn-trading-sdk
Version:
Professional Bitcoin trading strategy backtesting framework with institutional-grade architecture
521 lines (458 loc) ⢠18.4 kB
JavaScript
const logger = require('../../utils/logger');
/**
* Simple Position Manager for Backtesting
* Handles position sizing, leverage, and risk management
*/
class SimplePositionManager {
constructor(config = {}) {
this.config = {
adaptiveMinimum: 85000, // $100 minimum for premium adaptive strategy
maxPositions: config.maxPositions || 5,
...config
};
// Strategy-specific configuration with tiers
this.strategyTiers = {
conservative: {
tier: 'FREE',
leverage: { max: 1.0, default: 1.0 },
minBalance: 0,
description: 'Basic trading - any balance, no leverage'
},
enhanced: {
tier: 'FREE',
leverage: { max: 2.0, default: 2.0 },
minBalance: 0,
description: 'Advanced indicators - any balance, 2x leverage'
},
momentum: {
tier: 'FREE',
leverage: { max: 5.0, default: 4.0 },
minBalance: 0,
description: 'Trend-following momentum - any balance, up to 5x leverage'
},
adaptive: {
tier: 'PREMIUM',
leverage: { max: 10.0, default: 2.0 },
minBalance: 85000, // $100+ minimum for serious traders
description: 'AI-powered Claude analysis - $100+ minimum, up to 10x leverage'
}
};
this.positions = [];
this.totalExposure = 0;
this.tradingFee = config.tradingFee || 0.001; // Default 0.1% per trade
}
/**
* Calculate position size and leverage profile
*/
calculatePosition(signal, portfolioValue, currentPrice) {
try {
// Ensure strategy is defined (safety check for zero-config)
if (!signal.strategy || signal.strategy === undefined) {
signal.strategy = 'conservative';
logger.debug('š§ Position manager applied default strategy: conservative');
}
// Validate strategy tier requirements
this.validateStrategyAccess(signal.strategy, portfolioValue);
// Get leverage profile with reasoning
const leverageProfile = this.calculateLeverageProfile(signal);
// Calculate base position size (percentage of portfolio)
const basePositionPercent = this.getPositionSizePercent(signal);
const basePositionValue = portfolioValue * basePositionPercent;
// Apply strategy-specific minimums
let positionValue = basePositionValue;
if (signal.strategy === 'adaptive') {
// Premium strategy enforces $100 minimum per position
positionValue = Math.max(basePositionValue, this.config.adaptiveMinimum);
}
// Calculate leveraged position size
const leveragedPositionValue = positionValue * leverageProfile.leverage;
const quantity = leveragedPositionValue / currentPrice; // USD position / USD price = BTC quantity
// Calculate margin requirement in sats (actual money at risk)
const marginRequired = Math.round(positionValue / currentPrice * 100000000); // Convert USD to sats
// Calculate stop loss and take profit
const stopLossPrice = this.calculateStopLoss(currentPrice, signal, leverageProfile);
const takeProfitPrice = this.calculateTakeProfit(currentPrice, signal, leverageProfile);
return {
quantity: quantity,
positionValue: leveragedPositionValue,
marginRequired: Math.round(marginRequired),
leverage: leverageProfile.leverage,
leverageReason: leverageProfile.reason,
riskLevel: leverageProfile.riskLevel,
stopLossPrice: Math.round(stopLossPrice),
takeProfitPrice: Math.round(takeProfitPrice),
entryPrice: currentPrice,
maxLoss: Math.round(marginRequired * 0.8), // Max 80% of margin
potentialProfit: Math.round((takeProfitPrice - currentPrice) * quantity)
};
} catch (error) {
logger.error('ā Position calculation failed', { error: error.message, signal });
throw error;
}
}
/**
* Validate strategy access based on tier requirements
*/
validateStrategyAccess(strategy, portfolioValue) {
const tier = this.strategyTiers[strategy];
if (!tier) {
// Default unknown strategies to conservative tier for zero-config experience
logger.warn(`ā ļø Unknown strategy "${strategy}" - defaulting to conservative tier (1x leverage, basic risk management)`);
return this.validateStrategyAccess('conservative', portfolioValue);
}
if (strategy === 'adaptive' && portfolioValue < tier.minBalance) {
throw new Error(
`š ADAPTIVE QUANT STRATEGY - PREMIUM TIER REQUIRED\n\n` +
`š§ UNLOCK ADAPTIVE INTELLIGENCE:\n` +
` ⢠Real-Time Market Sentiment Analysis\n` +
` ⢠Multi-Factor Signal Processing\n` +
` ⢠Dynamic Risk-Adjusted Position Sizing\n` +
` ⢠Advanced Leverage Optimization (up to 10x)\n` +
` ⢠Adaptive Confidence-Based Execution\n` +
` ⢠Systematic Alpha Generation\n\n` +
`š° Minimum Balance: ${tier.minBalance.toLocaleString()} sats ($100)\n` +
`ā” Built for serious traders seeking edge\n` +
`šÆ Precision execution with intelligent risk management\n\n` +
`š Your Balance: ${portfolioValue.toLocaleString()} sats\n` +
`š” Try Enhanced (2x leverage) or Conservative strategies instead`
);
}
}
/**
* Calculate leverage and generate reasoning profile
*/
calculateLeverageProfile(signal) {
const strategyTier = this.strategyTiers[signal.strategy] || this.strategyTiers.conservative;
const strategyLeverage = strategyTier.leverage;
if (signal.strategy === 'adaptive') {
// AGGRESSIVE: Claude confidence-based leverage
if (signal.confidence >= 0.95) {
return {
leverage: 10.0,
reason: 'MAXIMUM CONVICTION - Claude extremely confident',
riskLevel: 'AGGRESSIVE'
};
}
if (signal.confidence >= 0.90) {
return {
leverage: 8.0,
reason: 'VERY HIGH confidence - Strong market signals',
riskLevel: 'HIGH'
};
}
if (signal.confidence >= 0.85) {
return {
leverage: 6.0,
reason: 'HIGH confidence - Multiple indicators aligned',
riskLevel: 'ELEVATED'
};
}
if (signal.confidence >= 0.80) {
return {
leverage: 4.0,
reason: 'GOOD confidence - Solid technical setup',
riskLevel: 'MODERATE'
};
}
if (signal.confidence >= 0.70) {
return {
leverage: 3.0,
reason: 'MODERATE confidence - Some uncertainty',
riskLevel: 'CONSERVATIVE'
};
}
return {
leverage: strategyLeverage.default,
reason: 'LOW confidence - Playing it safe',
riskLevel: 'MINIMAL'
};
}
// CONSERVATIVE leverage for momentum strategy
if (signal.strategy === 'momentum') {
// Moderate leverage based on confidence for momentum trades
if (signal.confidence >= 0.85) {
return {
leverage: 4.0, // 4x for very high confidence (was 8x)
reason: 'HIGH CONFIDENCE momentum - Moderate leverage',
riskLevel: 'MODERATE'
};
} else if (signal.confidence >= 0.75) {
return {
leverage: 3.0, // 3x for high confidence (was 6x)
reason: 'GOOD CONFIDENCE momentum - Conservative leverage',
riskLevel: 'MODERATE'
};
} else {
return {
leverage: 2.0, // 2x for standard momentum (was 5x)
reason: 'STANDARD momentum - Conservative leverage',
riskLevel: 'CONSERVATIVE'
};
}
}
// Fixed leverage for other strategies
const tier = this.strategyTiers[signal.strategy] || this.strategyTiers.conservative;
return {
leverage: strategyLeverage.max,
reason: `${tier.tier} ${signal.strategy.toUpperCase()} - ${tier.description}`,
riskLevel: signal.strategy === 'enhanced' ? 'MODERATE' : 'CONSERVATIVE'
};
}
/**
* Get position size as percentage of portfolio
*/
getPositionSizePercent(signal) {
// CONSERVATIVE position sizing to prevent over-leverage
const baseSizes = {
conservative: 0.10, // 10% per position
enhanced: 0.15, // 15% per position
momentum: 0.20, // 20% per position (REDUCED from 40%)
adaptive: 0.25 // 25% per position
};
let baseSize = baseSizes[signal.strategy] || baseSizes.conservative;
// More conservative confidence-based sizing for momentum
if (signal.strategy === 'momentum' && signal.confidence) {
if (signal.confidence >= 0.85) {
baseSize = 0.25; // 25% for very high confidence (was 50%)
} else if (signal.confidence >= 0.75) {
baseSize = 0.22; // 22% for high confidence (was 45%)
} else if (signal.confidence >= 0.65) {
baseSize = 0.20; // 20% for good confidence (was 40%)
} else {
baseSize = 0.15; // 15% for lower confidence (was 30%)
}
}
// Conservative adaptive strategy sizing
if (signal.strategy === 'adaptive' && signal.confidence) {
const confidenceBonus = (signal.confidence - 0.5) * 0.10; // Up to 5% bonus (was 7.5%)
baseSize = Math.min(baseSize + confidenceBonus, 0.30); // Max 30% (was 45%)
}
return baseSize;
}
/**
* Calculate dynamic stop loss based on leverage
*/
calculateStopLoss(entryPrice, signal, leverageProfile) {
// TIGHTER STOP LOSSES - Better risk management
const baseStopLoss = {
conservative: 0.025, // 2.5% (was 4.5%)
enhanced: 0.03, // 3.0% (was 4.5%)
momentum: 0.04, // 4.0% (was 8.0%)
adaptive: 0.025 // 2.5% (was 4.5%)
};
let stopLossPercent = baseStopLoss[signal.strategy] || baseStopLoss.conservative;
// Slightly wider stops for higher confidence only
if (signal.confidence && signal.confidence > 0.85) {
stopLossPercent *= 1.2; // 20% wider for very high confidence
}
return (signal.action === 'LONG' || signal.action === 'BUY')
? entryPrice * (1 - stopLossPercent)
: entryPrice * (1 + stopLossPercent);
}
/**
* Calculate dynamic take profit based on leverage and confidence
*/
calculateTakeProfit(entryPrice, signal, leverageProfile) {
// AGGRESSIVE TAKE PROFIT - Bigger targets for better returns
const baseTakeProfit = {
conservative: 0.04, // 4%
enhanced: 0.05, // 5%
momentum: 0.15, // 15% MUCH HIGHER targets (was 7%)
adaptive: 0.12 // 12% higher targets
};
let takeProfitPercent = baseTakeProfit[signal.strategy] || baseTakeProfit.conservative;
// MUCH higher targets for higher confidence
if (signal.confidence && signal.confidence > 0.85) {
takeProfitPercent *= 1.5; // 50% higher target (was 30%)
} else if (signal.confidence && signal.confidence > 0.75) {
takeProfitPercent *= 1.3; // 30% higher target (was 15%)
}
// More aggressive targets for higher leverage
if (leverageProfile.leverage > 7) {
takeProfitPercent *= 1.4; // 40% higher
} else if (leverageProfile.leverage > 5) {
takeProfitPercent *= 1.25; // 25% higher
}
return (signal.action === 'LONG' || signal.action === 'BUY')
? entryPrice * (1 + takeProfitPercent)
: entryPrice * (1 - takeProfitPercent);
}
/**
* Open a new position
*/
openPosition(positionData, signal) {
const position = {
id: `pos_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
...positionData,
action: signal.action,
strategy: signal.strategy,
openTime: Date.now(), // Will be overridden by backtest engine with historical timestamp
status: 'open',
signalReason: signal.reason,
maxHoldTime: signal.exitRules?.maxHoldTime || null // Add time stop from signal
};
this.positions.push(position);
this.totalExposure += positionData.positionValue;
logger.info('š Position opened in backtest', {
id: position.id,
strategy: position.strategy,
action: position.action,
leverage: `${position.leverage}x`,
reason: position.leverageReason,
riskLevel: position.riskLevel,
quantity: position.quantity,
marginRequired: `${position.marginRequired} sats`,
stopLoss: position.stopLossPrice,
takeProfit: position.takeProfitPrice
});
return position;
}
/**
* Close a position
*/
closePosition(positionId, exitPrice, reason = 'Manual close') {
const position = this.positions.find(p => p.id === positionId);
if (!position) {
throw new Error(`Position ${positionId} not found`);
}
// Calculate P&L
const priceDiff = (position.action === 'LONG' || position.action === 'BUY')
? exitPrice - position.entryPrice
: position.entryPrice - exitPrice;
// CRITICAL FIX: P&L is in USD, convert to sats
const rawPnLUSD = priceDiff * position.quantity;
const rawPnL = Math.round(rawPnLUSD / exitPrice * 100000000); // Convert USD to sats
// Calculate trading fees in sats
const tradingFeesUSD = position.positionValue * this.tradingFee * 2;
const tradingFees = Math.round(tradingFeesUSD / exitPrice * 100000000); // Convert USD to sats
const realizedPnL = rawPnL - tradingFees;
position.exitPrice = exitPrice;
position.closeTime = Date.now();
position.status = 'closed';
position.realizedPnL = realizedPnL;
position.tradingFees = tradingFees;
position.rawPnL = rawPnL;
position.rawPnLUSD = rawPnLUSD; // Store USD amount for debugging
position.closeReason = reason;
this.totalExposure -= position.positionValue;
logger.info('š Position closed in backtest', {
id: position.id,
strategy: position.strategy,
entryPrice: position.entryPrice,
exitPrice: exitPrice,
quantity: position.quantity,
rawPnL: rawPnL,
fees: tradingFees,
pnl: `${realizedPnL > 0 ? '+' : ''}${realizedPnL.toLocaleString()} sats`,
reason: reason,
holdTime: `${((position.closeTime - position.openTime) / 1000 / 60).toFixed(1)}m`
});
return position;
}
/**
* Get current open positions
*/
getOpenPositions() {
return this.positions.filter(p => p.status === 'open');
}
/**
* Get closed positions
*/
getClosedPositions() {
return this.positions.filter(p => p.status === 'closed');
}
/**
* Check if position limits allow new position
*/
canOpenPosition(strategy, portfolioSizeUSD = null) {
const openPositions = this.getOpenPositions();
const strategyPositions = openPositions.filter(p => p.strategy === strategy);
// Base strategy-specific limits
const baseLimits = {
conservative: 1, // Single position - truly conservative
enhanced: 5, // Multiple positions - active trading
momentum: 5, // Multiple positions - trend following
adaptive: 5 // Multiple positions - premium tier
};
let limit = baseLimits[strategy] || baseLimits.conservative;
// Scale position limits based on portfolio size for momentum strategy
if (strategy === 'momentum' && portfolioSizeUSD) {
if (portfolioSizeUSD >= 5000) { // $5K+ portfolio
limit = 8; // 60% more positions
} else if (portfolioSizeUSD >= 3000) { // $3K+ portfolio
limit = 6; // 20% more positions
} else if (portfolioSizeUSD >= 1500) { // $1.5K+ portfolio
limit = 5; // Standard 5 positions
} else { // Small portfolios
limit = 3; // Fewer positions for risk management
}
}
return strategyPositions.length < limit;
}
/**
* Get portfolio summary
*/
getPortfolioSummary() {
const openPositions = this.getOpenPositions();
const closedPositions = this.getClosedPositions();
const totalPnL = closedPositions.reduce((sum, pos) => sum + (pos.realizedPnL || 0), 0);
const totalMarginUsed = openPositions.reduce((sum, pos) => sum + pos.marginRequired, 0);
return {
openPositions: openPositions.length,
closedPositions: closedPositions.length,
totalPnL: totalPnL,
totalMarginUsed: totalMarginUsed,
totalExposure: this.totalExposure,
winRate: closedPositions.length > 0
? (closedPositions.filter(p => (p.realizedPnL || 0) > 0).length / closedPositions.length) * 100
: 0
};
}
/**
* Get available strategy tiers for current balance
*/
getAvailableStrategies(portfolioValue) {
const available = [];
for (const [strategy, tier] of Object.entries(this.strategyTiers)) {
if (portfolioValue >= tier.minBalance) {
available.push({
strategy,
tier: tier.tier,
description: tier.description,
maxLeverage: `${tier.leverage.max}x`,
minBalance: tier.minBalance === 0 ? 'Any amount' : `${tier.minBalance.toLocaleString()} sats`
});
}
}
return available;
}
/**
* Display strategy tier information
*/
displayStrategyTiers(portfolioValue) {
const available = this.getAvailableStrategies(portfolioValue);
console.log('\nš AVAILABLE TRADING STRATEGIES:');
console.log('='.repeat(50));
available.forEach(strategy => {
const tierBadge = strategy.tier === 'PREMIUM' ? 'š PREMIUM' : 'š FREE';
console.log(`${tierBadge} ${strategy.strategy.toUpperCase()}`);
console.log(` ${strategy.description}`);
console.log(` Max Leverage: ${strategy.maxLeverage}`);
console.log(` Minimum: ${strategy.minBalance}\n`);
});
// Show unavailable premium strategies
if (portfolioValue < this.strategyTiers.adaptive.minBalance) {
console.log('š PREMIUM STRATEGY (Upgrade Required):');
console.log(` ADAPTIVE - ${this.strategyTiers.adaptive.description}`);
console.log(` Requires: ${this.strategyTiers.adaptive.minBalance.toLocaleString()} sats ($100+)\n`);
}
}
/**
* Reset position manager
*/
reset() {
this.positions = [];
this.totalExposure = 0;
}
}
module.exports = SimplePositionManager;