feat: implement symbol-specific position sizing for multi-asset trading
- Extended MarketConfig with optional positionSize and leverage fields
- Configured ETH-PERP at @ 1x leverage for minimal-risk data collection
- Created getPositionSizeForSymbol() helper function in config/trading.ts
- Integrated symbol-specific sizing into execute endpoint
- Added comprehensive guide in docs/guides/SYMBOL_SPECIFIC_SIZING.md
Purpose: Enable ETH trading for faster signal quality data collection
while preserving SOL's profit-generation sizing (0 @ 10x)
Next: Create ETH alert in TradingView and restart bot
This commit is contained in:
@@ -166,6 +166,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
// Get trading configuration
|
||||
const config = getMergedConfig()
|
||||
|
||||
// Get symbol-specific position sizing
|
||||
const { getPositionSizeForSymbol } = await import('@/config/trading')
|
||||
const { size: positionSize, leverage } = getPositionSizeForSymbol(driftSymbol, config)
|
||||
|
||||
console.log(`📐 Symbol-specific sizing for ${driftSymbol}:`)
|
||||
console.log(` Position size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
|
||||
// Initialize Drift service if not already initialized
|
||||
const driftService = await initializeDriftService()
|
||||
|
||||
@@ -217,12 +225,12 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
}
|
||||
|
||||
// Calculate position size with leverage
|
||||
const positionSizeUSD = config.positionSize * config.leverage
|
||||
const positionSizeUSD = positionSize * leverage
|
||||
|
||||
console.log(`💰 Opening ${body.direction} position:`)
|
||||
console.log(` Symbol: ${driftSymbol}`)
|
||||
console.log(` Base size: $${config.positionSize}`)
|
||||
console.log(` Leverage: ${config.leverage}x`)
|
||||
console.log(` Base size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
console.log(` Total position: $${positionSizeUSD}`)
|
||||
|
||||
// Open position
|
||||
|
||||
@@ -54,6 +54,9 @@ export interface MarketConfig {
|
||||
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
|
||||
@@ -108,6 +111,7 @@ export const SUPPORTED_MARKETS: Record<string, MarketConfig> = {
|
||||
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',
|
||||
@@ -115,6 +119,7 @@ export const SUPPORTED_MARKETS: Record<string, MarketConfig> = {
|
||||
pythPriceFeedId: '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
|
||||
minOrderSize: 0.001, // 0.001 BTC minimum
|
||||
tickSize: 0.01,
|
||||
// Use default config values
|
||||
},
|
||||
'ETH-PERP': {
|
||||
symbol: 'ETH-PERP',
|
||||
@@ -122,6 +127,9 @@ export const SUPPORTED_MARKETS: Record<string, MarketConfig> = {
|
||||
pythPriceFeedId: '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
|
||||
minOrderSize: 0.01, // 0.01 ETH minimum
|
||||
tickSize: 0.01,
|
||||
// DATA COLLECTION MODE: Minimal risk
|
||||
positionSize: 1, // $1 USD base capital only
|
||||
leverage: 1, // 1x leverage = $1 position size total
|
||||
},
|
||||
}
|
||||
|
||||
@@ -147,6 +155,16 @@ export function getMarketConfig(symbol: string): MarketConfig {
|
||||
return config
|
||||
}
|
||||
|
||||
// Get position size for specific symbol (with market-specific overrides)
|
||||
export function getPositionSizeForSymbol(symbol: string, baseConfig: TradingConfig): { size: number; leverage: number } {
|
||||
const marketConfig = getMarketConfig(symbol)
|
||||
|
||||
return {
|
||||
size: marketConfig.positionSize ?? baseConfig.positionSize,
|
||||
leverage: marketConfig.leverage ?? baseConfig.leverage
|
||||
}
|
||||
}
|
||||
|
||||
// Validate trading configuration
|
||||
export function validateTradingConfig(config: TradingConfig): void {
|
||||
if (config.positionSize <= 0) {
|
||||
|
||||
272
docs/guides/SYMBOL_SPECIFIC_SIZING.md
Normal file
272
docs/guides/SYMBOL_SPECIFIC_SIZING.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Symbol-Specific Position Sizing Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The bot now supports different position sizes and leverage for each trading symbol. This enables strategies like:
|
||||
|
||||
- **High-risk symbols (SOL):** $50 @ 10x leverage = $500 exposure (profit generation)
|
||||
- **Low-risk symbols (ETH):** $1 @ 1x leverage = $1 exposure (data collection)
|
||||
|
||||
## Configuration
|
||||
|
||||
### 1. Market Configuration (`config/trading.ts`)
|
||||
|
||||
```typescript
|
||||
export const SUPPORTED_MARKETS: Record<string, MarketConfig> = {
|
||||
'SOL-PERP': {
|
||||
driftMarketIndex: 0,
|
||||
pythFeedId: '0xef0d...',
|
||||
// Uses default config.positionSize and config.leverage
|
||||
},
|
||||
'ETH-PERP': {
|
||||
driftMarketIndex: 1,
|
||||
pythFeedId: '0xff61...',
|
||||
// OVERRIDE: Data collection mode with minimal risk
|
||||
positionSize: 1, // $1 base size
|
||||
leverage: 1, // 1x leverage = $1 total exposure
|
||||
},
|
||||
'BTC-PERP': {
|
||||
driftMarketIndex: 2,
|
||||
pythFeedId: '0xe62d...',
|
||||
// Uses default config.positionSize and config.leverage
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Helper Function
|
||||
|
||||
```typescript
|
||||
export function getPositionSizeForSymbol(
|
||||
symbol: string,
|
||||
baseConfig: TradingConfig
|
||||
): { size: number; leverage: number } {
|
||||
const marketConfig = SUPPORTED_MARKETS[symbol]
|
||||
if (!marketConfig) {
|
||||
throw new Error(`Unsupported symbol: ${symbol}`)
|
||||
}
|
||||
|
||||
return {
|
||||
size: marketConfig.positionSize ?? baseConfig.positionSize,
|
||||
leverage: marketConfig.leverage ?? baseConfig.leverage,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Execute Endpoint Integration
|
||||
|
||||
The `app/api/trading/execute/route.ts` endpoint now:
|
||||
|
||||
1. Normalizes symbol (ETHUSDT → ETH-PERP)
|
||||
2. Gets merged config via `getMergedConfig()`
|
||||
3. **Calls `getPositionSizeForSymbol(symbol, config)`** to get symbol-specific sizing
|
||||
4. Uses returned `{ size, leverage }` for position calculation
|
||||
|
||||
## Example: ETH Data Collection Setup
|
||||
|
||||
### Goal
|
||||
Collect as many ETH signals as possible with minimal risk to improve signal quality analysis.
|
||||
|
||||
### Configuration
|
||||
```typescript
|
||||
'ETH-PERP': {
|
||||
driftMarketIndex: 1,
|
||||
pythFeedId: '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
|
||||
positionSize: 1, // $1 base size
|
||||
leverage: 1, // No leverage
|
||||
}
|
||||
```
|
||||
|
||||
### Expected Behavior
|
||||
- TradingView sends: `ETHUSDT LONG` or `ETHUSDT SHORT`
|
||||
- Bot normalizes to: `ETH-PERP`
|
||||
- Bot loads sizing: `positionSize: 1, leverage: 1`
|
||||
- Position size: `$1 × 1 = $1 total exposure`
|
||||
- At ETH = $3,500: Opens ~0.00029 ETH position
|
||||
- Risk: Maximum loss = ~$1 (if emergency stop hits)
|
||||
|
||||
### Console Output
|
||||
```
|
||||
📊 Normalized symbol: ETHUSDT → ETH-PERP
|
||||
📐 Symbol-specific sizing for ETH-PERP:
|
||||
Position size: $1
|
||||
Leverage: 1x
|
||||
💰 Opening LONG position:
|
||||
Symbol: ETH-PERP
|
||||
Base size: $1
|
||||
Leverage: 1x
|
||||
Total position: $1
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### 1. SOL Trade (Default Sizing)
|
||||
Send webhook:
|
||||
```json
|
||||
{
|
||||
"symbol": "SOLUSDT",
|
||||
"direction": "LONG"
|
||||
}
|
||||
```
|
||||
|
||||
Expected logs:
|
||||
```
|
||||
📐 Symbol-specific sizing for SOL-PERP:
|
||||
Position size: $50
|
||||
Leverage: 10x
|
||||
Total position: $500
|
||||
```
|
||||
|
||||
### 2. ETH Trade (Override Sizing)
|
||||
Send webhook:
|
||||
```json
|
||||
{
|
||||
"symbol": "ETHUSDT",
|
||||
"direction": "SHORT"
|
||||
}
|
||||
```
|
||||
|
||||
Expected logs:
|
||||
```
|
||||
📐 Symbol-specific sizing for ETH-PERP:
|
||||
Position size: $1
|
||||
Leverage: 1x
|
||||
Total position: $1
|
||||
```
|
||||
|
||||
### 3. Verify in Drift
|
||||
Check open positions - ETH position should show ~$1 notional value.
|
||||
|
||||
## Strategy Rationale
|
||||
|
||||
### Why $1 @ 1x for ETH?
|
||||
|
||||
1. **Data Collection Priority:** ETH shows 2-3x more signals than SOL
|
||||
2. **Risk Management:** Cross margin means ETH position uses shared collateral
|
||||
3. **No Profit Pressure:** Not trying to make money on ETH, just gather quality scores
|
||||
4. **Statistical Significance:** Need 20-50 trades with quality scores before Phase 2
|
||||
5. **Collateral Preservation:** Current SOL long uses most collateral, can't risk large ETH positions
|
||||
|
||||
### When to Increase ETH Sizing?
|
||||
|
||||
Only after:
|
||||
- ✅ Phase 1 complete (20-50 trades with quality scores)
|
||||
- ✅ ETH signal quality proven ≥ SOL (win rate, profit factor)
|
||||
- ✅ Sufficient collateral available (not at risk of liquidation)
|
||||
- ✅ Phase 2 ATR-based targets implemented and validated
|
||||
|
||||
## Cross Margin Considerations
|
||||
|
||||
**Critical:** All positions (SOL, ETH, BTC) share the same collateral pool on Drift.
|
||||
|
||||
### Collateral Math
|
||||
```
|
||||
Total Collateral: $500
|
||||
Current SOL position: ~$450 used
|
||||
Free collateral: ~$50
|
||||
|
||||
Adding ETH @ $1:
|
||||
- Maintenance margin: ~$0.05 (5%)
|
||||
- Initial margin: ~$0.10 (10%)
|
||||
- Impact: Minimal ✅
|
||||
|
||||
Adding ETH @ $50:
|
||||
- Maintenance margin: ~$2.50
|
||||
- Initial margin: ~$5
|
||||
- Risk: Higher liquidation risk ⚠️
|
||||
```
|
||||
|
||||
### Safety Buffer
|
||||
Keep at least 30% free collateral at all times:
|
||||
- Total: $500
|
||||
- Max used: $350 (70%)
|
||||
- Reserve: $150 (30%) for margin calls and new positions
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Phase 2: Reserve-Based Sizing Module
|
||||
|
||||
Create `lib/trading/position-sizing.ts`:
|
||||
|
||||
```typescript
|
||||
export interface PositionSizingParams {
|
||||
symbol: string
|
||||
direction: 'long' | 'short'
|
||||
totalCollateral: number
|
||||
usedCollateral: number
|
||||
marketConfig: MarketConfig
|
||||
baseConfig: TradingConfig
|
||||
}
|
||||
|
||||
export function calculatePositionSize(params: PositionSizingParams): number {
|
||||
const freeCollateral = params.totalCollateral - params.usedCollateral
|
||||
const reservePercent = 0.30 // Keep 30% reserve
|
||||
const availableForTrade = freeCollateral * (1 - reservePercent)
|
||||
|
||||
// Get base size from market config or default
|
||||
const baseSize = params.marketConfig.positionSize ?? params.baseConfig.positionSize
|
||||
const leverage = params.marketConfig.leverage ?? params.baseConfig.leverage
|
||||
const requiredCollateral = baseSize * leverage * 0.10 // 10% initial margin
|
||||
|
||||
// If not enough collateral, reduce position size
|
||||
if (requiredCollateral > availableForTrade) {
|
||||
return availableForTrade / leverage / 0.10
|
||||
}
|
||||
|
||||
return baseSize
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Prevents over-leveraging
|
||||
- Maintains safety buffer
|
||||
- Dynamic sizing based on account state
|
||||
- Supports multiple concurrent positions
|
||||
|
||||
**When to implement:** After Phase 1 validation, before increasing ETH position sizes.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: ETH still trading at $50
|
||||
|
||||
**Check:**
|
||||
1. Restart bot after config changes: `docker restart trading-bot-v4`
|
||||
2. Verify config loaded: Check console logs for "Symbol-specific sizing"
|
||||
3. Ensure symbol normalization: ETHUSDT → ETH-PERP (not ETH-USD)
|
||||
|
||||
### Issue: Position Manager using wrong size
|
||||
|
||||
**Root cause:** Position Manager calculates position amounts from on-chain data, not config.
|
||||
|
||||
**Behavior:**
|
||||
- Execute endpoint uses `positionSize` and `leverage` from config
|
||||
- Position Manager reads actual position size from Drift
|
||||
- They're independent systems (by design for safety)
|
||||
|
||||
### Issue: Database shows wrong positionSize
|
||||
|
||||
**Root cause:** Database stores actual executed size, not config size.
|
||||
|
||||
**Expected:**
|
||||
- Config: `ETH-PERP positionSize: 1`
|
||||
- Database: `positionSize: 1.0` (matches execution)
|
||||
- Drift on-chain: ~0.00029 ETH (~$1 notional)
|
||||
|
||||
All three should align. If not, config didn't load properly.
|
||||
|
||||
## Summary
|
||||
|
||||
Symbol-specific sizing enables:
|
||||
- ✅ Multi-asset trading with different risk profiles
|
||||
- ✅ Data collection strategies (ETH @ $1)
|
||||
- ✅ Profit generation strategies (SOL @ $50)
|
||||
- ✅ Cross-margin safety (minimal ETH exposure)
|
||||
- ✅ Faster signal quality validation (more trades)
|
||||
|
||||
**Next steps:**
|
||||
1. ✅ Config updated (DONE)
|
||||
2. ✅ Execute endpoint integrated (DONE)
|
||||
3. ⏸️ Create ETH alert in TradingView (USER ACTION)
|
||||
4. ⏸️ Restart bot: `docker restart trading-bot-v4`
|
||||
5. ⏸️ Monitor first ETH trade for correct sizing
|
||||
6. ⏸️ Collect 20-50 trades with quality scores
|
||||
7. ⏸️ Proceed to Phase 2 (ATR-based dynamic targets)
|
||||
Reference in New Issue
Block a user