feat: Add FARTCOIN-PERP market support with percentage-based sizing

- Added FARTCOIN-PERP to SUPPORTED_MARKETS (market index 22)
- Updated TradingConfig interface with fartcoin symbol settings
- Added default config: 20% portfolio, 10x leverage, disabled by default
- Updated normalizeTradingViewSymbol to detect FARTCOIN
- Enhanced getPositionSizeForSymbol for FARTCOIN-PERP handling
- Enhanced getActualPositionSizeForSymbol for percentage-based sizing
- Added FARTCOIN ENV variable loading in getConfigFromEnv
- Updated Settings UI with FARTCOIN section and percentage badge
- Added FARTCOIN fields to settings API endpoints (GET/POST)
- Created comprehensive documentation in docs/markets/FARTCOIN-PERP.md
- Build successful: TypeScript compilation and static generation complete

Co-authored-by: mindesbunister <32161838+mindesbunister@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-06 17:44:19 +00:00
parent d3b83ae95a
commit 2df6c69b92
4 changed files with 274 additions and 0 deletions

View File

@@ -28,6 +28,7 @@ export interface TradingConfig {
// Per-symbol settings
solana?: SymbolSettings
ethereum?: SymbolSettings
fartcoin?: SymbolSettings
// Risk management (as percentages of entry price - LEGACY, used as fallback)
stopLossPercent: number // Negative number (e.g., -1.5)
@@ -135,6 +136,12 @@ export const DEFAULT_TRADING_CONFIG: TradingConfig = {
leverage: 1, // 1x leverage = $4 notional
usePercentageSize: false,
},
fartcoin: {
enabled: false, // DISABLED BY DEFAULT
positionSize: 20, // 20% of portfolio (for profit generation)
leverage: 10, // 10x leverage
usePercentageSize: true, // PERCENTAGE-BASED (not fixed USD)
},
// Risk parameters (LEGACY FALLBACK - used when ATR unavailable)
stopLossPercent: -1.5, // Fallback: -1.5% if no ATR
@@ -234,6 +241,14 @@ export const SUPPORTED_MARKETS: Record<string, MarketConfig> = {
positionSize: 40, // $40 base capital
leverage: 1, // 1x leverage = $40 total exposure
},
'FARTCOIN-PERP': {
symbol: 'FARTCOIN-PERP',
driftMarketIndex: 22,
pythPriceFeedId: '2sZomfWMDuQLcFak3nuharXorHrZ3hK8iaML6ZGSHtso',
minOrderSize: 1, // 1 FARTCOIN minimum
tickSize: 0.0001,
// Use per-symbol config below
},
}
// Map TradingView symbols to Drift markets
@@ -243,6 +258,7 @@ export function normalizeTradingViewSymbol(tvSymbol: string): string {
if (upper.includes('SOL')) return 'SOL-PERP'
if (upper.includes('BTC')) return 'BTC-PERP'
if (upper.includes('ETH')) return 'ETH-PERP'
if (upper.includes('FARTCOIN')) return 'FARTCOIN-PERP'
// Default to SOL if unknown
console.warn(`Unknown symbol ${tvSymbol}, defaulting to SOL-PERP`)
@@ -277,6 +293,14 @@ export function getPositionSizeForSymbol(symbol: string, baseConfig: TradingConf
}
}
if (symbol === 'FARTCOIN-PERP' && baseConfig.fartcoin) {
return {
size: baseConfig.fartcoin.positionSize,
leverage: baseConfig.fartcoin.leverage,
enabled: baseConfig.fartcoin.enabled,
}
}
// Fallback to market-specific config, then global config
const marketConfig = getMarketConfig(symbol)
return {
@@ -352,6 +376,13 @@ export async function getActualPositionSizeForSymbol(
enabled: baseConfig.ethereum.enabled,
}
usePercentage = baseConfig.ethereum.usePercentageSize ?? false
} else if (symbol === 'FARTCOIN-PERP' && baseConfig.fartcoin) {
symbolSettings = {
size: baseConfig.fartcoin.positionSize,
leverage: baseConfig.fartcoin.leverage,
enabled: baseConfig.fartcoin.enabled,
}
usePercentage = baseConfig.fartcoin.usePercentageSize ?? false
} else {
// Fallback to market-specific or global config
const marketConfig = getMarketConfig(symbol)
@@ -502,6 +533,16 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
? process.env.ETHEREUM_USE_PERCENTAGE_SIZE === 'true'
: false,
},
fartcoin: {
enabled: process.env.FARTCOIN_ENABLED === 'true',
positionSize: process.env.FARTCOIN_POSITION_SIZE
? parseFloat(process.env.FARTCOIN_POSITION_SIZE)
: 20,
leverage: process.env.FARTCOIN_LEVERAGE
? parseInt(process.env.FARTCOIN_LEVERAGE)
: 10,
usePercentageSize: process.env.FARTCOIN_USE_PERCENTAGE_SIZE !== 'false', // Default true
},
leverage: process.env.LEVERAGE
? parseInt(process.env.LEVERAGE)
: undefined,