- Add SymbolSettings interface with enabled/positionSize/leverage fields - Implement per-symbol ENV variables (SOLANA_*, ETHEREUM_*) - Add SOL and ETH sections to settings UI with enable/disable toggles - Add symbol-specific test buttons (SOL LONG/SHORT, ETH LONG/SHORT) - Update execute and test endpoints to check symbol enabled status - Add real-time risk/reward calculator per symbol - Rename 'Position Sizing' to 'Global Fallback' for clarity - Fix position manager P&L calculation for externally closed positions - Fix zero P&L bug affecting 12 historical trades - Add SQL scripts for recalculating historical P&L data - Move archive TypeScript files to .archive to fix build Defaults: - SOL: 10 base × 10x leverage = 100 notional (profit trading) - ETH: base × 1x leverage = notional (data collection) - Global: 10 × 10x for BTC and other symbols Configuration priority: Per-symbol ENV > Market config > Global ENV > Defaults
337 lines
12 KiB
TypeScript
337 lines
12 KiB
TypeScript
/**
|
|
* Trading Bot v4 - Configuration
|
|
*
|
|
* Optimized for 5-minute scalping with 10x leverage on Drift Protocol
|
|
*/
|
|
|
|
export interface SymbolSettings {
|
|
enabled: boolean
|
|
positionSize: number
|
|
leverage: number
|
|
}
|
|
|
|
export interface TradingConfig {
|
|
// Position sizing (global fallback)
|
|
positionSize: number // USD amount to trade
|
|
leverage: number // Leverage multiplier
|
|
|
|
// Per-symbol settings
|
|
solana?: SymbolSettings
|
|
ethereum?: SymbolSettings
|
|
|
|
// Risk management (as percentages of entry price)
|
|
stopLossPercent: number // Negative number (e.g., -1.5)
|
|
takeProfit1Percent: number // Positive number (e.g., 0.7)
|
|
takeProfit2Percent: number // Positive number (e.g., 1.5)
|
|
emergencyStopPercent: number // Hard stop (e.g., -2.0)
|
|
|
|
// Dual Stop System (Advanced)
|
|
useDualStops: boolean // Enable dual stop system
|
|
softStopPercent: number // Soft stop trigger (e.g., -1.5)
|
|
softStopBuffer: number // Buffer for soft stop limit (e.g., 0.4)
|
|
hardStopPercent: number // Hard stop trigger (e.g., -2.5)
|
|
|
|
// Dynamic adjustments
|
|
breakEvenTriggerPercent: number // When to move SL to breakeven
|
|
profitLockTriggerPercent: number // When to lock in profit
|
|
profitLockPercent: number // How much profit to lock
|
|
|
|
// Trailing stop for runner (after TP2)
|
|
useTrailingStop: boolean // Enable trailing stop for remaining position
|
|
trailingStopPercent: number // Trail by this % below peak
|
|
trailingStopActivation: number // Activate when runner profits exceed this %
|
|
|
|
// DEX specific
|
|
priceCheckIntervalMs: number // How often to check prices
|
|
slippageTolerance: number // Max acceptable slippage (%)
|
|
|
|
// Risk limits
|
|
maxDailyDrawdown: number // USD stop trading threshold
|
|
maxTradesPerHour: number // Limit overtrading
|
|
minTimeBetweenTrades: number // Cooldown period (minutes)
|
|
|
|
// Execution
|
|
useMarketOrders: boolean // true = instant execution
|
|
confirmationTimeout: number // Max time to wait for confirmation
|
|
// Take profit size splits (percentages of position to close at TP1/TP2)
|
|
takeProfit1SizePercent: number
|
|
takeProfit2SizePercent: number
|
|
}
|
|
|
|
export interface MarketConfig {
|
|
symbol: string // e.g., 'SOL-PERP'
|
|
driftMarketIndex: number
|
|
pythPriceFeedId: string
|
|
minOrderSize: number
|
|
tickSize: number
|
|
// Position sizing overrides (optional)
|
|
positionSize?: number
|
|
leverage?: number
|
|
}
|
|
|
|
// Default configuration for 5-minute scalping with $1000 capital and 10x leverage
|
|
export const DEFAULT_TRADING_CONFIG: TradingConfig = {
|
|
// Position sizing (global fallback)
|
|
positionSize: 50, // $50 base capital (SAFE FOR TESTING)
|
|
leverage: 10, // 10x leverage = $500 position size
|
|
|
|
// Per-symbol settings
|
|
solana: {
|
|
enabled: true,
|
|
positionSize: 210, // $210 base capital
|
|
leverage: 10, // 10x leverage = $2100 notional
|
|
},
|
|
ethereum: {
|
|
enabled: true,
|
|
positionSize: 4, // $4 base capital (DATA ONLY - minimum size)
|
|
leverage: 1, // 1x leverage = $4 notional
|
|
},
|
|
|
|
// Risk parameters (wider for DEX slippage/wicks)
|
|
stopLossPercent: -1.5, // -1.5% price = -15% account loss (closes 100%)
|
|
takeProfit1Percent: 0.7, // +0.7% price = +7% account gain (closes 50%)
|
|
takeProfit2Percent: 1.5, // +1.5% price = +15% account gain (closes 50%)
|
|
emergencyStopPercent: -2.0, // -2% hard stop = -20% account loss
|
|
|
|
// Dual Stop System
|
|
useDualStops: false, // Disabled by default
|
|
softStopPercent: -1.5, // Soft stop (TRIGGER_LIMIT)
|
|
softStopBuffer: 0.4, // 0.4% buffer (limit at -1.9%)
|
|
hardStopPercent: -2.5, // Hard stop (TRIGGER_MARKET)
|
|
|
|
// Dynamic adjustments
|
|
breakEvenTriggerPercent: 0.4, // Move SL to this profit level after TP1 hits
|
|
profitLockTriggerPercent: 1.0, // Lock profit at +1.0%
|
|
profitLockPercent: 0.4, // Lock +0.4% profit
|
|
|
|
// Trailing stop for runner (after TP2)
|
|
useTrailingStop: true, // Enable trailing stop for remaining position after TP2
|
|
trailingStopPercent: 0.3, // Trail by 0.3% below peak price
|
|
trailingStopActivation: 0.5, // Activate trailing when runner is +0.5% in profit
|
|
|
|
// DEX settings
|
|
priceCheckIntervalMs: 2000, // Check every 2 seconds
|
|
slippageTolerance: 1.0, // 1% max slippage on market orders
|
|
|
|
// Risk limits
|
|
maxDailyDrawdown: -150, // Stop trading if daily loss exceeds $150 (-15%)
|
|
maxTradesPerHour: 6, // Max 6 trades per hour
|
|
minTimeBetweenTrades: 10, // 10 minutes cooldown
|
|
|
|
// Execution
|
|
useMarketOrders: true, // Use market orders for reliable fills
|
|
confirmationTimeout: 30000, // 30 seconds max wait
|
|
takeProfit1SizePercent: 75, // Close 75% at TP1 to lock in profit
|
|
takeProfit2SizePercent: 80, // Close 80% of remaining 25% at TP2 (leaves 5% as runner)
|
|
}
|
|
|
|
// Supported markets on Drift Protocol
|
|
export const SUPPORTED_MARKETS: Record<string, MarketConfig> = {
|
|
'SOL-PERP': {
|
|
symbol: 'SOL-PERP',
|
|
driftMarketIndex: 0,
|
|
pythPriceFeedId: '0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d',
|
|
minOrderSize: 0.1, // 0.1 SOL minimum
|
|
tickSize: 0.0001,
|
|
// Use default config values (positionSize: 50, leverage: 10)
|
|
},
|
|
'BTC-PERP': {
|
|
symbol: 'BTC-PERP',
|
|
driftMarketIndex: 1,
|
|
pythPriceFeedId: '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
|
|
minOrderSize: 0.001, // 0.001 BTC minimum
|
|
tickSize: 0.01,
|
|
// Use default config values
|
|
},
|
|
'ETH-PERP': {
|
|
symbol: 'ETH-PERP',
|
|
driftMarketIndex: 2,
|
|
pythPriceFeedId: '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
|
|
minOrderSize: 0.01, // 0.01 ETH minimum
|
|
tickSize: 0.01,
|
|
// DATA COLLECTION MODE: Minimal risk (Drift minimum 0.01 ETH = ~$38)
|
|
positionSize: 40, // $40 base capital (meets exchange minimum)
|
|
leverage: 1, // 1x leverage = $40 total exposure
|
|
},
|
|
}
|
|
|
|
// Map TradingView symbols to Drift markets
|
|
export function normalizeTradingViewSymbol(tvSymbol: string): string {
|
|
const upper = tvSymbol.toUpperCase()
|
|
|
|
if (upper.includes('SOL')) return 'SOL-PERP'
|
|
if (upper.includes('BTC')) return 'BTC-PERP'
|
|
if (upper.includes('ETH')) return 'ETH-PERP'
|
|
|
|
// Default to SOL if unknown
|
|
console.warn(`Unknown symbol ${tvSymbol}, defaulting to SOL-PERP`)
|
|
return 'SOL-PERP'
|
|
}
|
|
|
|
// Get market configuration
|
|
export function getMarketConfig(symbol: string): MarketConfig {
|
|
const config = SUPPORTED_MARKETS[symbol]
|
|
if (!config) {
|
|
throw new Error(`Unsupported market: ${symbol}`)
|
|
}
|
|
return config
|
|
}
|
|
|
|
// Get position size for specific symbol (prioritizes per-symbol config)
|
|
export function getPositionSizeForSymbol(symbol: string, baseConfig: TradingConfig): { size: number; leverage: number; enabled: boolean } {
|
|
// Check per-symbol settings first
|
|
if (symbol === 'SOL-PERP' && baseConfig.solana) {
|
|
return {
|
|
size: baseConfig.solana.positionSize,
|
|
leverage: baseConfig.solana.leverage,
|
|
enabled: baseConfig.solana.enabled,
|
|
}
|
|
}
|
|
|
|
if (symbol === 'ETH-PERP' && baseConfig.ethereum) {
|
|
return {
|
|
size: baseConfig.ethereum.positionSize,
|
|
leverage: baseConfig.ethereum.leverage,
|
|
enabled: baseConfig.ethereum.enabled,
|
|
}
|
|
}
|
|
|
|
// Fallback to market-specific config, then global config
|
|
const marketConfig = getMarketConfig(symbol)
|
|
return {
|
|
size: marketConfig.positionSize ?? baseConfig.positionSize,
|
|
leverage: marketConfig.leverage ?? baseConfig.leverage,
|
|
enabled: true, // BTC or other markets default to enabled
|
|
}
|
|
}
|
|
|
|
// Validate trading configuration
|
|
export function validateTradingConfig(config: TradingConfig): void {
|
|
if (config.positionSize <= 0) {
|
|
throw new Error('Position size must be positive')
|
|
}
|
|
|
|
if (config.leverage < 1 || config.leverage > 20) {
|
|
throw new Error('Leverage must be between 1 and 20')
|
|
}
|
|
|
|
if (config.stopLossPercent >= 0) {
|
|
throw new Error('Stop loss must be negative')
|
|
}
|
|
|
|
if (config.takeProfit1Percent <= 0 || config.takeProfit2Percent <= 0) {
|
|
throw new Error('Take profit values must be positive')
|
|
}
|
|
|
|
if (config.takeProfit1Percent >= config.takeProfit2Percent) {
|
|
throw new Error('TP2 must be greater than TP1')
|
|
}
|
|
|
|
if (config.slippageTolerance < 0 || config.slippageTolerance > 10) {
|
|
throw new Error('Slippage tolerance must be between 0 and 10%')
|
|
}
|
|
}
|
|
|
|
// Environment-based configuration
|
|
export function getConfigFromEnv(): Partial<TradingConfig> {
|
|
const config: Partial<TradingConfig> = {
|
|
positionSize: process.env.MAX_POSITION_SIZE_USD
|
|
? parseFloat(process.env.MAX_POSITION_SIZE_USD)
|
|
: undefined,
|
|
|
|
// Per-symbol settings from ENV
|
|
solana: {
|
|
enabled: process.env.SOLANA_ENABLED !== 'false',
|
|
positionSize: process.env.SOLANA_POSITION_SIZE
|
|
? parseFloat(process.env.SOLANA_POSITION_SIZE)
|
|
: 210,
|
|
leverage: process.env.SOLANA_LEVERAGE
|
|
? parseInt(process.env.SOLANA_LEVERAGE)
|
|
: 10,
|
|
},
|
|
ethereum: {
|
|
enabled: process.env.ETHEREUM_ENABLED !== 'false',
|
|
positionSize: process.env.ETHEREUM_POSITION_SIZE
|
|
? parseFloat(process.env.ETHEREUM_POSITION_SIZE)
|
|
: 4,
|
|
leverage: process.env.ETHEREUM_LEVERAGE
|
|
? parseInt(process.env.ETHEREUM_LEVERAGE)
|
|
: 1,
|
|
},
|
|
leverage: process.env.LEVERAGE
|
|
? parseInt(process.env.LEVERAGE)
|
|
: undefined,
|
|
stopLossPercent: process.env.STOP_LOSS_PERCENT
|
|
? parseFloat(process.env.STOP_LOSS_PERCENT)
|
|
: undefined,
|
|
useDualStops: process.env.USE_DUAL_STOPS
|
|
? process.env.USE_DUAL_STOPS === 'true'
|
|
: undefined,
|
|
softStopPercent: process.env.SOFT_STOP_PERCENT
|
|
? parseFloat(process.env.SOFT_STOP_PERCENT)
|
|
: undefined,
|
|
softStopBuffer: process.env.SOFT_STOP_BUFFER
|
|
? parseFloat(process.env.SOFT_STOP_BUFFER)
|
|
: undefined,
|
|
hardStopPercent: process.env.HARD_STOP_PERCENT
|
|
? parseFloat(process.env.HARD_STOP_PERCENT)
|
|
: undefined,
|
|
takeProfit1Percent: process.env.TAKE_PROFIT_1_PERCENT
|
|
? parseFloat(process.env.TAKE_PROFIT_1_PERCENT)
|
|
: undefined,
|
|
takeProfit2Percent: process.env.TAKE_PROFIT_2_PERCENT
|
|
? parseFloat(process.env.TAKE_PROFIT_2_PERCENT)
|
|
: undefined,
|
|
takeProfit1SizePercent: process.env.TAKE_PROFIT_1_SIZE_PERCENT
|
|
? parseFloat(process.env.TAKE_PROFIT_1_SIZE_PERCENT)
|
|
: undefined,
|
|
takeProfit2SizePercent: process.env.TAKE_PROFIT_2_SIZE_PERCENT
|
|
? parseFloat(process.env.TAKE_PROFIT_2_SIZE_PERCENT)
|
|
: undefined,
|
|
breakEvenTriggerPercent: process.env.BREAKEVEN_TRIGGER_PERCENT
|
|
? parseFloat(process.env.BREAKEVEN_TRIGGER_PERCENT)
|
|
: undefined,
|
|
profitLockTriggerPercent: process.env.PROFIT_LOCK_TRIGGER_PERCENT
|
|
? parseFloat(process.env.PROFIT_LOCK_TRIGGER_PERCENT)
|
|
: undefined,
|
|
profitLockPercent: process.env.PROFIT_LOCK_PERCENT
|
|
? parseFloat(process.env.PROFIT_LOCK_PERCENT)
|
|
: undefined,
|
|
useTrailingStop: process.env.USE_TRAILING_STOP
|
|
? process.env.USE_TRAILING_STOP === 'true'
|
|
: undefined,
|
|
trailingStopPercent: process.env.TRAILING_STOP_PERCENT
|
|
? parseFloat(process.env.TRAILING_STOP_PERCENT)
|
|
: undefined,
|
|
trailingStopActivation: process.env.TRAILING_STOP_ACTIVATION
|
|
? parseFloat(process.env.TRAILING_STOP_ACTIVATION)
|
|
: undefined,
|
|
maxDailyDrawdown: process.env.MAX_DAILY_DRAWDOWN
|
|
? parseFloat(process.env.MAX_DAILY_DRAWDOWN)
|
|
: undefined,
|
|
maxTradesPerHour: process.env.MAX_TRADES_PER_HOUR
|
|
? parseInt(process.env.MAX_TRADES_PER_HOUR)
|
|
: undefined,
|
|
minTimeBetweenTrades: process.env.MIN_TIME_BETWEEN_TRADES
|
|
? parseInt(process.env.MIN_TIME_BETWEEN_TRADES)
|
|
: undefined,
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
// Merge configurations
|
|
export function getMergedConfig(
|
|
overrides?: Partial<TradingConfig>
|
|
): TradingConfig {
|
|
const envConfig = getConfigFromEnv()
|
|
const config = {
|
|
...DEFAULT_TRADING_CONFIG,
|
|
...envConfig,
|
|
...overrides,
|
|
}
|
|
|
|
validateTradingConfig(config)
|
|
return config
|
|
}
|