feat: Indicator score bypass - v11.2 sends SCORE:100 to bypass bot quality scoring
Changes: - moneyline_v11_2_indicator.pinescript: Alert format now includes SCORE:100 - parse_signal_enhanced.json: Added indicatorScore parsing (SCORE:X regex) - execute/route.ts: Added hasIndicatorScore bypass (score >= 90 bypasses quality check) - Money_Machine.json: Both Execute Trade nodes now pass indicatorScore to API Rationale: v11.2 indicator filters already optimized (2.544 PF, +51.80% return). Bot-side quality scoring was blocking proven profitable signals (e.g., quality 75). Now indicator passes SCORE:100, bot respects it and executes immediately. This completes the signal chain: Indicator (SCORE:100) → n8n parser (indicatorScore) → workflow → bot endpoint (bypass)
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
import { getInitializedPositionManager } from '../trading/position-manager'
|
||||
import { getOpenTrades, getPrismaClient } from '../database/trades'
|
||||
import { getDriftService } from '../drift/client'
|
||||
import { getMergedConfig } from '../../config/trading'
|
||||
|
||||
export interface HealthCheckResult {
|
||||
isHealthy: boolean
|
||||
@@ -89,6 +90,103 @@ async function autoSyncUntrackedPositions(): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
function calculatePrice(entry: number, percent: number, direction: 'long' | 'short'): number {
|
||||
return direction === 'long'
|
||||
? entry * (1 + percent / 100)
|
||||
: entry * (1 - percent / 100)
|
||||
}
|
||||
|
||||
async function ensureExitOrdersForTrade(
|
||||
trade: any,
|
||||
config: ReturnType<typeof getMergedConfig>
|
||||
): Promise<{ placed: boolean; message?: string; error?: string }> {
|
||||
try {
|
||||
const positionSizeUSD = trade.positionSizeUSD || trade.positionSize || 0
|
||||
if (!positionSizeUSD || !trade.entryPrice) {
|
||||
return { placed: false, error: 'Missing position size or entry price' }
|
||||
}
|
||||
|
||||
const direction: 'long' | 'short' = trade.direction === 'short' ? 'short' : 'long'
|
||||
|
||||
const tp1Price =
|
||||
trade.takeProfit1Price || calculatePrice(trade.entryPrice, config.takeProfit1Percent, direction)
|
||||
const tp2Price =
|
||||
trade.takeProfit2Price || calculatePrice(trade.entryPrice, config.takeProfit2Percent, direction)
|
||||
const stopLossPrice =
|
||||
trade.stopLossPrice || calculatePrice(trade.entryPrice, config.stopLossPercent, direction)
|
||||
|
||||
const tp1SizePercent = trade.tp1SizePercent ?? config.takeProfit1SizePercent
|
||||
const tp2SizePercentRaw = trade.tp2SizePercent ?? config.takeProfit2SizePercent ?? 0
|
||||
const tp2SizePercent = config.useTp2AsTriggerOnly && tp2SizePercentRaw <= 0 ? 0 : tp2SizePercentRaw
|
||||
|
||||
const softStopPrice = config.useDualStops
|
||||
? calculatePrice(trade.entryPrice, config.softStopPercent, direction)
|
||||
: undefined
|
||||
const hardStopPrice = config.useDualStops
|
||||
? calculatePrice(trade.entryPrice, config.hardStopPercent, direction)
|
||||
: undefined
|
||||
|
||||
const { placeExitOrders } = await import('../drift/orders')
|
||||
|
||||
const placeResult = await placeExitOrders({
|
||||
symbol: trade.symbol,
|
||||
positionSizeUSD,
|
||||
entryPrice: trade.entryPrice,
|
||||
tp1Price,
|
||||
tp2Price,
|
||||
stopLossPrice,
|
||||
tp1SizePercent,
|
||||
tp2SizePercent,
|
||||
direction,
|
||||
useDualStops: config.useDualStops,
|
||||
softStopPrice,
|
||||
softStopBuffer: config.softStopBuffer,
|
||||
hardStopPrice,
|
||||
})
|
||||
|
||||
if (!placeResult.success) {
|
||||
return { placed: false, error: placeResult.error || 'Unknown error placing exit orders' }
|
||||
}
|
||||
|
||||
const signatures = placeResult.signatures || []
|
||||
const normalizedTp2Percent = tp2SizePercent === undefined ? 100 : Math.max(0, tp2SizePercent)
|
||||
const tp1USD = (positionSizeUSD * tp1SizePercent) / 100
|
||||
const remainingAfterTP1 = positionSizeUSD - tp1USD
|
||||
const tp2USD = (remainingAfterTP1 * normalizedTp2Percent) / 100
|
||||
|
||||
let idx = 0
|
||||
const updateData: any = {}
|
||||
|
||||
if (tp1USD > 0 && idx < signatures.length) {
|
||||
updateData.tp1OrderTx = signatures[idx++]
|
||||
}
|
||||
if (normalizedTp2Percent > 0 && idx < signatures.length) {
|
||||
updateData.tp2OrderTx = signatures[idx++]
|
||||
}
|
||||
|
||||
if (config.useDualStops && softStopPrice && hardStopPrice) {
|
||||
if (idx < signatures.length) {
|
||||
updateData.softStopOrderTx = signatures[idx++]
|
||||
}
|
||||
if (idx < signatures.length) {
|
||||
updateData.hardStopOrderTx = signatures[idx++]
|
||||
}
|
||||
} else if (idx < signatures.length) {
|
||||
updateData.slOrderTx = signatures[idx++]
|
||||
}
|
||||
|
||||
const prisma = getPrismaClient()
|
||||
await prisma.trade.update({ where: { id: trade.id }, data: updateData })
|
||||
|
||||
return { placed: true, message: 'Protective exits placed' }
|
||||
} catch (error) {
|
||||
return {
|
||||
placed: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Position Manager health
|
||||
*
|
||||
@@ -101,6 +199,7 @@ async function autoSyncUntrackedPositions(): Promise<boolean> {
|
||||
export async function checkPositionManagerHealth(): Promise<HealthCheckResult> {
|
||||
const issues: string[] = []
|
||||
const warnings: string[] = []
|
||||
const config = getMergedConfig()
|
||||
|
||||
try {
|
||||
// Get database open trades
|
||||
@@ -169,26 +268,29 @@ export async function checkPositionManagerHealth(): Promise<HealthCheckResult> {
|
||||
}
|
||||
|
||||
// Check for unprotected positions
|
||||
// NOTE: Synced/placeholder positions (signalSource='autosync') have NULL signatures in DB
|
||||
// but orders exist on Drift. Position Manager monitoring provides backup protection.
|
||||
let unprotectedPositions = 0
|
||||
for (const trade of dbTrades) {
|
||||
const hasDbSignatures = !!(trade.slOrderTx || trade.softStopOrderTx || trade.hardStopOrderTx)
|
||||
const isSyncedPosition = trade.signalSource === 'autosync' || trade.timeframe === 'sync'
|
||||
|
||||
if (!hasDbSignatures && !isSyncedPosition) {
|
||||
// This is NOT a synced position but has no SL orders - CRITICAL
|
||||
if (!hasDbSignatures) {
|
||||
unprotectedPositions++
|
||||
issues.push(`❌ CRITICAL: Position ${trade.symbol} (${trade.id}) has NO STOP LOSS ORDERS!`)
|
||||
issues.push(` Entry: $${trade.entryPrice}, Size: $${trade.positionSizeUSD}`)
|
||||
issues.push(` This is the silent SL placement failure bug`)
|
||||
issues.push(` Attempting automatic protective order placement...`)
|
||||
|
||||
const remediation = await ensureExitOrdersForTrade(trade, config)
|
||||
if (remediation.placed) {
|
||||
warnings.push(`✅ Auto-placed protective exit orders for ${trade.symbol} (${trade.id})`)
|
||||
} else if (remediation.error) {
|
||||
issues.push(` ❌ Failed to auto-place exits: ${remediation.error}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!trade.tp1OrderTx && !isSyncedPosition) {
|
||||
if (!trade.tp1OrderTx) {
|
||||
warnings.push(`⚠️ Position ${trade.symbol} missing TP1 order (not synced)`)
|
||||
}
|
||||
|
||||
if (!trade.tp2OrderTx && !isSyncedPosition) {
|
||||
if (!trade.tp2OrderTx) {
|
||||
warnings.push(`⚠️ Position ${trade.symbol} missing TP2 order (not synced)`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +199,87 @@ export async function syncSinglePosition(driftPos: any, positionManager: any): P
|
||||
console.log(`🔍 Querying Drift for existing orders on ${driftPos.symbol}...`)
|
||||
const existingOrders = await discoverExistingOrders(driftPos.symbol, driftPos.marketIndex)
|
||||
|
||||
// If no stop orders exist, immediately place protective exit orders so the position is never unprotected
|
||||
const hasStopOrders = !!(
|
||||
existingOrders.slOrderTx ||
|
||||
existingOrders.softStopOrderTx ||
|
||||
existingOrders.hardStopOrderTx
|
||||
)
|
||||
|
||||
const tp2SizePercentRaw = config.takeProfit2SizePercent ?? 0
|
||||
const tp2SizePercent =
|
||||
config.useTp2AsTriggerOnly && tp2SizePercentRaw <= 0 ? 0 : tp2SizePercentRaw
|
||||
|
||||
const orderRefs: any = { ...existingOrders }
|
||||
|
||||
if (!hasStopOrders) {
|
||||
console.warn(`🛡️ No SL orders found for ${driftPos.symbol} - placing protective exits now`)
|
||||
try {
|
||||
const { placeExitOrders } = await import('../drift/orders')
|
||||
|
||||
const softStopPrice = config.useDualStops
|
||||
? calculatePrice(entryPrice, config.softStopPercent, direction)
|
||||
: undefined
|
||||
const hardStopPrice = config.useDualStops
|
||||
? calculatePrice(entryPrice, config.hardStopPercent, direction)
|
||||
: undefined
|
||||
|
||||
const placeResult = await placeExitOrders({
|
||||
symbol: driftPos.symbol,
|
||||
positionSizeUSD,
|
||||
entryPrice,
|
||||
tp1Price,
|
||||
tp2Price,
|
||||
stopLossPrice,
|
||||
tp1SizePercent: config.takeProfit1SizePercent,
|
||||
tp2SizePercent,
|
||||
direction,
|
||||
useDualStops: config.useDualStops,
|
||||
softStopPrice,
|
||||
softStopBuffer: config.softStopBuffer,
|
||||
hardStopPrice,
|
||||
})
|
||||
|
||||
if (placeResult.success && placeResult.signatures?.length) {
|
||||
const signatures = placeResult.signatures
|
||||
const normalizedTp2Percent = tp2SizePercent === undefined
|
||||
? 100
|
||||
: Math.max(0, tp2SizePercent)
|
||||
|
||||
const tp1USD = (positionSizeUSD * config.takeProfit1SizePercent) / 100
|
||||
const remainingAfterTP1 = positionSizeUSD - tp1USD
|
||||
const tp2USD = (remainingAfterTP1 * normalizedTp2Percent) / 100
|
||||
|
||||
let idx = 0
|
||||
if (tp1USD > 0 && idx < signatures.length) {
|
||||
orderRefs.tp1OrderTx = signatures[idx++]
|
||||
}
|
||||
if (normalizedTp2Percent > 0 && idx < signatures.length) {
|
||||
orderRefs.tp2OrderTx = signatures[idx++]
|
||||
}
|
||||
|
||||
if (config.useDualStops && softStopPrice && hardStopPrice) {
|
||||
if (idx < signatures.length) {
|
||||
orderRefs.softStopOrderTx = signatures[idx++]
|
||||
}
|
||||
if (idx < signatures.length) {
|
||||
orderRefs.hardStopOrderTx = signatures[idx++]
|
||||
}
|
||||
} else if (idx < signatures.length) {
|
||||
orderRefs.slOrderTx = signatures[idx++]
|
||||
}
|
||||
|
||||
console.log(`✅ Protective exit orders placed for ${driftPos.symbol}`)
|
||||
} else {
|
||||
console.error(
|
||||
`❌ Failed to place protective exit orders for ${driftPos.symbol}: ${placeResult.error || 'unknown error'}`
|
||||
)
|
||||
}
|
||||
} catch (placeError) {
|
||||
console.error(`❌ Error placing protective exit orders for ${driftPos.symbol}:`, placeError)
|
||||
}
|
||||
}
|
||||
|
||||
const placeholderTrade = await prisma.trade.create({
|
||||
data: {
|
||||
positionId: syntheticPositionId,
|
||||
@@ -220,16 +301,16 @@ export async function syncSinglePosition(driftPos: any, positionManager: any): P
|
||||
status: 'open',
|
||||
signalSource: 'autosync',
|
||||
timeframe: 'sync',
|
||||
// CRITICAL FIX (Dec 12, 2025): Record discovered order signatures
|
||||
tp1OrderTx: existingOrders.tp1OrderTx || null,
|
||||
tp2OrderTx: existingOrders.tp2OrderTx || null,
|
||||
slOrderTx: existingOrders.slOrderTx || null,
|
||||
softStopOrderTx: existingOrders.softStopOrderTx || null,
|
||||
hardStopOrderTx: existingOrders.hardStopOrderTx || null,
|
||||
// CRITICAL FIX (Dec 12, 2025): Record discovered (or newly placed) order signatures
|
||||
tp1OrderTx: orderRefs.tp1OrderTx || null,
|
||||
tp2OrderTx: orderRefs.tp2OrderTx || null,
|
||||
slOrderTx: orderRefs.slOrderTx || null,
|
||||
softStopOrderTx: orderRefs.softStopOrderTx || null,
|
||||
hardStopOrderTx: orderRefs.hardStopOrderTx || null,
|
||||
configSnapshot: {
|
||||
source: 'health-monitor-autosync',
|
||||
syncedAt: now.toISOString(),
|
||||
discoveredOrders: existingOrders, // Store for debugging
|
||||
discoveredOrders: orderRefs, // Store for debugging
|
||||
positionManagerState: {
|
||||
currentSize: positionSizeUSD,
|
||||
tp1Hit: false,
|
||||
|
||||
Reference in New Issue
Block a user