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
526 lines
14 KiB
Markdown
526 lines
14 KiB
Markdown
# 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
|
||
|
||
- [x] Strategy validated on 11 trades (Nov 19 - Dec 17, 2025)
|
||
- [x] Performance documented: 63.6% WR, +183.4% return, 3.88 PF
|
||
- [x] Risk warnings documented (small sample size, outlier dependency)
|
||
- [x] User approval obtained: "implement the winner you found"
|
||
- [x] 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:**
|
||
```typescript
|
||
// Direction-specific thresholds
|
||
const MIN_LONG_QUALITY = 90;
|
||
const MIN_SHORT_QUALITY = 80;
|
||
```
|
||
|
||
**New code:**
|
||
```typescript
|
||
// Unified Q>=95 threshold (Dec 18, 2025 optimization)
|
||
const MIN_LONG_QUALITY = 95;
|
||
const MIN_SHORT_QUALITY = 95;
|
||
```
|
||
|
||
**OR update .env file:**
|
||
```bash
|
||
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:**
|
||
```typescript
|
||
/**
|
||
* 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:**
|
||
```typescript
|
||
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:**
|
||
```typescript
|
||
// 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`:
|
||
```bash
|
||
# 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`:
|
||
```sql
|
||
SELECT enumlabel
|
||
FROM pg_enum
|
||
WHERE enumtypid = (
|
||
SELECT oid FROM pg_type WHERE typname = 'BlockReason'
|
||
);
|
||
```
|
||
|
||
If not present, add it:
|
||
```sql
|
||
ALTER TYPE "BlockReason" ADD VALUE 'INSTANT_REVERSAL_RISK';
|
||
```
|
||
|
||
OR update Prisma schema:
|
||
```prisma
|
||
enum BlockReason {
|
||
// ... existing reasons
|
||
INSTANT_REVERSAL_RISK // Add this
|
||
}
|
||
```
|
||
|
||
Then run:
|
||
```bash
|
||
npx prisma db push
|
||
```
|
||
|
||
---
|
||
|
||
## Step 5: Testing Protocol
|
||
|
||
### 5.1 Unit Tests (if test suite exists)
|
||
|
||
Create `tests/instant-reversal-filter.test.ts`:
|
||
```typescript
|
||
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:
|
||
```bash
|
||
npm test -- instant-reversal-filter.test.ts
|
||
```
|
||
|
||
### 5.2 Integration Test (Manual)
|
||
|
||
1. **Trigger a test signal:**
|
||
- Send webhook to n8n with quality score 95
|
||
- Verify: Trade executes (quality threshold passed)
|
||
|
||
2. **Check BlockedSignal table:**
|
||
```sql
|
||
SELECT * FROM "BlockedSignal"
|
||
WHERE "blockReason" = 'INSTANT_REVERSAL_RISK'
|
||
ORDER BY timestamp DESC
|
||
LIMIT 10;
|
||
```
|
||
|
||
3. **Verify logs:**
|
||
- Check container logs: `docker logs trading-bot-v4 --tail 100`
|
||
- Look for: `⚠️ BLOCKED: Instant reversal risk` or `✅ Quality: 95 → Trade approved`
|
||
|
||
### 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
|
||
```bash
|
||
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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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:
|
||
|
||
```sql
|
||
-- 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)
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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:**
|
||
1. Win rate >= 55%
|
||
2. Profit factor >= 1.5
|
||
3. Average loss <= $35
|
||
4. Total P&L positive
|
||
5. No catastrophic losses (>$100 single trade)
|
||
|
||
❌ **Strategy failed if:**
|
||
1. Win rate < 40%
|
||
2. Profit factor < 0.8
|
||
3. Average loss > $50
|
||
4. Total drawdown > 50%
|
||
5. 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)
|
||
- **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)
|