Backtest results (28 days): - Original: 32 trades, 43.8% win rate, -16.82 loss - New: 13 trades, 69.2% win rate, +49.99 profit - Improvement: +66.81 (+991%), +25.5% hit rate Changes: 1. Set MIN_SIGNAL_QUALITY_SCORE_LONG/SHORT=95 (was 90/85) 2. Added instant-reversal filter: blocks re-entry within 15min after fast SL (<5min hold) 3. Added 5-candle time exit: exits after 25min if MFE <0 4. HTF filter already effective (no Q≥95 trades blocked) Expected outcome: Turn consistent losses into consistent profits with 69% win rate
14 KiB
Quick Implementation Guide: Q>=95 Strategy
Date: December 18, 2025
Target: Update quality thresholds + add instant reversal filter
Expected Impact: Turn -$687.98 baseline into +$178.91 profit (3.88 PF)
Pre-Implementation Checklist
- Strategy validated on 11 trades (Nov 19 - Dec 17, 2025)
- Performance documented: 63.6% WR, +183.4% return, 3.88 PF
- Risk warnings documented (small sample size, outlier dependency)
- User approval obtained: "implement the winner you found"
- Documentation complete (copilot-instructions.md + STRATEGY_OPTIMIZATION_DEC_2025.md)
- Code changes ready
- Testing plan prepared
- Monitoring dashboard ready
Step 1: Update Quality Thresholds (5 minutes)
File: lib/trading/signal-quality.ts
Location: Search for MIN_SIGNAL_QUALITY_SCORE
Current code:
// Direction-specific thresholds
const MIN_LONG_QUALITY = 90;
const MIN_SHORT_QUALITY = 80;
New code:
// Unified Q>=95 threshold (Dec 18, 2025 optimization)
const MIN_LONG_QUALITY = 95;
const MIN_SHORT_QUALITY = 95;
OR update .env file:
MIN_SIGNAL_QUALITY_SCORE_LONG=95
MIN_SIGNAL_QUALITY_SCORE_SHORT=95
Verify:
- Run
grep -r "MIN_SIGNAL_QUALITY" lib/to find all usages - Check if thresholds are hardcoded or read from config
- Confirm both LONG and SHORT get updated to 95
Step 2: Add Instant Reversal Filter (30-60 minutes)
Option A: Add to check-risk endpoint (RECOMMENDED)
File: app/api/check-risk/route.ts (or similar)
Logic to add:
/**
* Block signals that would likely hit SL within 1-2 candles (instant reversals)
* These indicate poor timing, false breakouts, or late entries
*/
async function checkInstantReversalRisk(
symbol: string,
direction: 'LONG' | 'SHORT',
entryPrice: number,
stopLoss: number
): Promise<{ blocked: boolean; reason?: string }> {
// 1. Fetch last 5-10 price candles for symbol (5-minute timeframe)
const recentCandles = await getRecentCandles(symbol, '5', 10);
if (!recentCandles || recentCandles.length < 3) {
// Insufficient data - fail-open (allow trade)
return { blocked: false };
}
// 2. Calculate recent volatility (ATR proxy from last 5 candles)
const recentRanges = recentCandles.slice(0, 5).map(c =>
Math.abs(c.high - c.low) / c.close
);
const avgRange = recentRanges.reduce((a, b) => a + b, 0) / recentRanges.length;
// 3. Calculate stop loss distance as percentage
const slDistance = Math.abs(stopLoss - entryPrice) / entryPrice;
// 4. Check if SL distance is less than 1-2 candle average range
const instantReversalThreshold = avgRange * 1.5; // 1.5 candles worth of movement
if (slDistance < instantReversalThreshold) {
// 5. Check recent price action - is there momentum in our direction?
const lastCandle = recentCandles[0];
const priceMovement = (lastCandle.close - lastCandle.open) / lastCandle.open;
const momentumInDirection = (
(direction === 'LONG' && priceMovement > 0) ||
(direction === 'SHORT' && priceMovement < 0)
);
if (!momentumInDirection) {
// No momentum + tight SL = likely instant reversal
return {
blocked: true,
reason: `Instant reversal risk: SL distance ${(slDistance * 100).toFixed(2)}% < ${(instantReversalThreshold * 100).toFixed(2)}% (1.5 candles), no momentum in direction`
};
}
}
return { blocked: false };
}
// Add to main check-risk logic:
const instantReversalCheck = await checkInstantReversalRisk(
symbol,
direction,
entryPrice,
calculatedStopLoss
);
if (instantReversalCheck.blocked) {
console.log(`⚠️ BLOCKED: ${instantReversalCheck.reason}`);
// Log to BlockedSignal table
await prisma.blockedSignal.create({
data: {
symbol,
direction,
timeframe: '5',
blockReason: 'INSTANT_REVERSAL_RISK',
details: instantReversalCheck.reason,
// ... other fields
}
});
return Response.json({
success: false,
reason: 'INSTANT_REVERSAL_RISK',
message: instantReversalCheck.reason
});
}
Helper function needed:
async function getRecentCandles(
symbol: string,
timeframe: string,
count: number
): Promise<Array<{ open: number; high: number; low: number; close: number; timestamp: Date }>> {
// Option 1: Query BlockedSignal table for recent candles
const signals = await prisma.blockedSignal.findMany({
where: {
symbol,
timeframe,
timestamp: {
gte: new Date(Date.now() - count * 5 * 60 * 1000) // Last N × 5 minutes
}
},
orderBy: { timestamp: 'desc' },
take: count,
select: {
price: true,
atr: true,
timestamp: true
}
});
// Option 2: Fetch from Drift/Pyth price feed history
// (if BlockedSignal doesn't have OHLC data)
return signals.map(s => ({
open: s.price, // Approximate - may need actual OHLC
high: s.price + s.atr,
low: s.price - s.atr,
close: s.price,
timestamp: s.timestamp
}));
}
Option B: Add to Position Manager (Alternative)
File: lib/trading/position-manager.ts
Add check in entry logic before opening position:
// Before: await driftClient.openPosition(...)
const instantReversalCheck = await checkInstantReversalRisk(
symbol, direction, entryPrice, stopLoss
);
if (instantReversalCheck.blocked) {
console.log(`⚠️ Position NOT opened: ${instantReversalCheck.reason}`);
return { success: false, reason: 'INSTANT_REVERSAL_RISK' };
}
// Continue with normal position opening...
Pros/Cons:
- Option A (check-risk): ✅ Blocks earlier, consistent with other filters, easier to test
- Option B (position-manager): ✅ Has access to real-time price data, but later in pipeline
Recommendation: Implement in check-risk endpoint (Option A) for consistency with HTF filter and quality threshold checks.
Step 3: Environment Variables (if needed)
Add to .env:
# Quality Score Optimization (Dec 18, 2025)
MIN_SIGNAL_QUALITY_SCORE_LONG=95
MIN_SIGNAL_QUALITY_SCORE_SHORT=95
# Instant Reversal Filter (Dec 18, 2025)
INSTANT_REVERSAL_DETECTION_ENABLED=true
INSTANT_REVERSAL_THRESHOLD_CANDLES=1.5 # SL must be > 1.5 candles away
Step 4: Database Updates (if needed)
Add new block reason to BlockedSignal table:
Check if blockReason enum includes INSTANT_REVERSAL_RISK:
SELECT enumlabel
FROM pg_enum
WHERE enumtypid = (
SELECT oid FROM pg_type WHERE typname = 'BlockReason'
);
If not present, add it:
ALTER TYPE "BlockReason" ADD VALUE 'INSTANT_REVERSAL_RISK';
OR update Prisma schema:
enum BlockReason {
// ... existing reasons
INSTANT_REVERSAL_RISK // Add this
}
Then run:
npx prisma db push
Step 5: Testing Protocol
5.1 Unit Tests (if test suite exists)
Create tests/instant-reversal-filter.test.ts:
describe('Instant Reversal Filter', () => {
it('should block trades with SL < 1.5 candles', async () => {
const result = await checkInstantReversalRisk(
'SOL', 'LONG', 100, 99.5, // Entry $100, SL $99.50 (0.5%)
[{ high: 101, low: 99, close: 100 }] // 2% range candle
);
expect(result.blocked).toBe(true);
});
it('should allow trades with SL > 1.5 candles', async () => {
const result = await checkInstantReversalRisk(
'SOL', 'LONG', 100, 98.5, // Entry $100, SL $98.50 (1.5%)
[{ high: 101, low: 99, close: 100 }] // 2% range candle
);
expect(result.blocked).toBe(false);
});
});
Run:
npm test -- instant-reversal-filter.test.ts
5.2 Integration Test (Manual)
-
Trigger a test signal:
- Send webhook to n8n with quality score 95
- Verify: Trade executes (quality threshold passed)
-
Check BlockedSignal table:
SELECT * FROM "BlockedSignal" WHERE "blockReason" = 'INSTANT_REVERSAL_RISK' ORDER BY timestamp DESC LIMIT 10; -
Verify logs:
- Check container logs:
docker logs trading-bot-v4 --tail 100 - Look for:
⚠️ BLOCKED: Instant reversal riskor✅ Quality: 95 → Trade approved
- Check container logs:
5.3 Paper Trade (if available)
- Switch to testnet/paper mode
- Run for 24 hours
- Verify: Fewer trades executed (~0.44/day vs 1.0/day)
- Check: Quality scores of executed trades all >=95
Step 6: Deployment
6.1 Commit Changes
cd /home/icke/traderv4
# Stage changes
git add lib/trading/signal-quality.ts
git add app/api/check-risk/route.ts # Or wherever instant reversal filter added
git add .env # If ENV vars changed
git add docs/STRATEGY_OPTIMIZATION_DEC_2025.md
git add docs/IMPLEMENTATION_GUIDE.md
git add .github/copilot-instructions.md
# Commit
git commit -m "feat: Implement Q>=95 quality threshold + instant reversal filter
- Update quality thresholds: LONG 90→95, SHORT 80→95
- Add instant reversal detection (blocks SL <1.5 candles)
- Validated performance: 11 trades, 63.6% WR, +183.4% return, 3.88 PF
- Ref: docs/STRATEGY_OPTIMIZATION_DEC_2025.md"
# Push
git push origin main
6.2 Restart Container
# Rebuild and restart
docker compose down
docker compose up -d --build
# Verify container started
docker ps | grep trading-bot-v4
# Check logs
docker logs trading-bot-v4 --tail 50 --follow
6.3 Verify Deployment
# 1. Check container timestamp
docker inspect trading-bot-v4 | grep Created
# 2. Verify commit deployed
docker exec trading-bot-v4 git log -1 --oneline
# 3. Test health endpoint
curl http://localhost:3000/api/health
# 4. Check ENV vars
docker exec trading-bot-v4 printenv | grep QUALITY
Step 7: Post-Deployment Monitoring
Day 1 Checklist
- Monitor logs every 2 hours for first 24h
- Check for any Q>=95 signals received
- Verify instant reversal filter triggers (if any)
- Confirm first trade execution (quality logged correctly)
- Check database:
SELECT * FROM "Trade" ORDER BY entryTime DESC LIMIT 3; - Review BlockedSignal:
SELECT blockReason, COUNT(*) FROM "BlockedSignal" WHERE timestamp > NOW() - INTERVAL '1 day' GROUP BY blockReason;
Week 1 Analysis
After 7 days or first 3-5 trades:
-- Performance check
WITH recent_trades AS (
SELECT
realizedPnL,
CASE WHEN realizedPnL > 0 THEN 1 ELSE 0 END as is_win,
signalQualityScore
FROM "Trade"
WHERE entryTime >= '2025-12-18' -- Deployment date
AND exitTime IS NOT NULL
AND timeframe = '5'
)
SELECT
COUNT(*) as trades,
ROUND(100.0 * SUM(is_win) / COUNT(*), 1) as win_rate_pct,
ROUND(SUM(realizedPnL)::numeric, 2) as total_pnl,
ROUND(AVG(CASE WHEN is_win=1 THEN realizedPnL END)::numeric, 2) as avg_win,
ROUND(AVG(CASE WHEN is_win=0 THEN realizedPnL END)::numeric, 2) as avg_loss,
ROUND(AVG(signalQualityScore)::numeric, 1) as avg_quality,
MIN(signalQualityScore) as min_quality
FROM recent_trades;
Compare to validated backtest:
- Expected: ~3 trades (0.44/day × 7 days)
- Expected WR: 60-65%
- Expected avg win: ~$34
- Expected avg loss: ~$21
- Expected PF: 2.0+ (conservative), 3.88 (optimistic)
Alert if:
- ❌ Win rate <50%
- ❌ Avg loss >$35
- ❌ Profit factor <1.5
- ❌ Zero trades in 5 days (threshold too strict)
- ❌ Any quality score <95 (filter bypass bug)
Rollback Procedure (if needed)
# 1. Revert git commit
git revert HEAD
git push origin main
# 2. Rebuild container
docker compose down
docker compose up -d --build
# 3. Verify rollback
docker logs trading-bot-v4 | grep "Quality threshold"
# Should show: LONG=90, SHORT=80 (old values)
# 4. Document failure
# Add notes to docs/STRATEGY_OPTIMIZATION_DEC_2025.md under "Rollback Criteria"
Quick Reference Commands
# Check logs
docker logs trading-bot-v4 --tail 100 --follow
# Recent trades
docker exec trading-bot-v4 psql $DATABASE_URL -c "
SELECT entryTime, direction, realizedPnL, exitReason, signalQualityScore
FROM \"Trade\"
WHERE entryTime >= '2025-12-18'
ORDER BY entryTime DESC
LIMIT 10;
"
# Blocked signals today
docker exec trading-bot-v4 psql $DATABASE_URL -c "
SELECT blockReason, COUNT(*)
FROM \"BlockedSignal\"
WHERE timestamp > CURRENT_DATE
GROUP BY blockReason;
"
# Quality score distribution (last 24h)
docker exec trading-bot-v4 psql $DATABASE_URL -c "
SELECT
CASE
WHEN \"qualityScore\" >= 95 THEN '95+'
WHEN \"qualityScore\" >= 90 THEN '90-94'
WHEN \"qualityScore\" >= 85 THEN '85-89'
ELSE '<85'
END as quality_bucket,
COUNT(*)
FROM \"BlockedSignal\"
WHERE timestamp > NOW() - INTERVAL '24 hours'
GROUP BY quality_bucket
ORDER BY quality_bucket DESC;
"
# Restart if needed
docker restart trading-bot-v4
Success Criteria (After 25 trades or 60 days)
✅ Strategy validated if:
- Win rate >= 55%
- Profit factor >= 1.5
- Average loss <= $35
- Total P&L positive
- No catastrophic losses (>$100 single trade)
❌ Strategy failed if:
- Win rate < 40%
- Profit factor < 0.8
- Average loss > $50
- Total drawdown > 50%
- Multiple instant reversal filter bypasses (bugs)
Contact & Support
- Documentation:
docs/STRATEGY_OPTIMIZATION_DEC_2025.md - Code Locations:
- Quality thresholds:
lib/trading/signal-quality.ts - Instant reversal:
app/api/check-risk/route.ts(to be added) - HTF filter: (existing, no changes)
- 5-candle exit: Position Manager (existing, no changes)
- Quality thresholds:
- User Approval: Obtained Dec 18, 2025
- Questions: Review conversation history or ask user
Last Updated: December 18, 2025
Status: 📋 Documentation complete, ready for implementation
Next Step: Begin Step 1 (Update quality thresholds)