Enhancement #6 - SL Distance Validation (Data Collection Phase): - Added slDistanceAtEntry field to StopHunt schema - Calculates distance from revenge entry to stop zone (LONG vs SHORT logic) - Logs distance in dollars + × ATR multiplier - Purpose: Collect 20+ revenge trade samples for optimal multiplier analysis - Created comprehensive analysis guide with SQL queries - Decision deferred until empirical data collected Enhancement #1 - ADX Confirmation (Implementation Plan): - Documented complete 1-minute TradingView alert strategy - Storage analysis: 19.44 MB/month for 3 symbols (negligible) - Two-phase approach: Cache-only MVP → Optional DB persistence - Provided TradingView Pine Script (ready to use) - Cost breakdown: Pro subscription $49.95/month required - Benefits: Real-time ADX, pattern recognition, ML features - Implementation checklist with validation phases Files Changed: - prisma/schema.prisma: +1 field (slDistanceAtEntry) - lib/trading/stop-hunt-tracker.ts: +10 lines (distance calculation + logging) - docs/1MIN_MARKET_DATA_IMPLEMENTATION.md: NEW (comprehensive plan) - docs/ENHANCEMENT_6_ANALYSIS_GUIDE.md: NEW (SQL queries + decision matrix) Status: Enhancement #4 and #10 deployed (previous commit) Enhancement #6 data collection enabled (this commit) Awaiting 20+ revenge trades for Enhancement #6 decision
This commit is contained in:
379
docs/1MIN_MARKET_DATA_IMPLEMENTATION.md
Normal file
379
docs/1MIN_MARKET_DATA_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,379 @@
|
||||
# 1-Minute Market Data Collection - Implementation Plan
|
||||
|
||||
**Status:** READY TO IMPLEMENT (Nov 27, 2025)
|
||||
**Purpose:** Enable real-time ADX validation for revenge system (Enhancement #1)
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**TradingView Alerts:**
|
||||
- Send data ONLY when trend changes (bullish/bearish transitions)
|
||||
- Timeframes: 5min, 15min, 1H, Daily
|
||||
- Problem: No fresh data between trend changes = stale cache for revenge ADX checks
|
||||
|
||||
**Market Data Cache:**
|
||||
- 5-minute expiry
|
||||
- Fields: ADX, ATR, RSI, volumeRatio, pricePosition, currentPrice
|
||||
- Used by: Re-entry analytics, soon revenge system
|
||||
|
||||
---
|
||||
|
||||
## Proposed Solution: 1-Minute Market Data Alerts
|
||||
|
||||
### Database Impact Analysis
|
||||
|
||||
**Current schema:**
|
||||
```sql
|
||||
-- No separate table, data flows through cache only
|
||||
-- Cache: In-memory Map, expires after 5 minutes
|
||||
-- Zero database storage currently
|
||||
```
|
||||
|
||||
**Proposed: Add MarketDataSnapshot table** (OPTIONAL)
|
||||
```sql
|
||||
model MarketDataSnapshot {
|
||||
id String @id @default(cuid())
|
||||
timestamp DateTime @default(now())
|
||||
symbol String
|
||||
timeframe String // "1", "5", "15", "60", "D"
|
||||
|
||||
-- Metrics
|
||||
atr Float
|
||||
adx Float
|
||||
rsi Float
|
||||
volumeRatio Float
|
||||
pricePosition Float
|
||||
currentPrice Float
|
||||
|
||||
-- Metadata
|
||||
indicatorVersion String? // e.g., "v9"
|
||||
|
||||
@@index([symbol, timestamp])
|
||||
@@index([symbol, timeframe, timestamp])
|
||||
}
|
||||
```
|
||||
|
||||
**Storage Calculation:**
|
||||
```
|
||||
Per record: ~150 bytes (8 fields × 8-20 bytes each)
|
||||
Per minute: 150 bytes × 1 symbol = 150 bytes
|
||||
Per hour: 150 × 60 = 9 KB
|
||||
Per day: 9 KB × 24 = 216 KB
|
||||
Per month: 216 KB × 30 = 6.48 MB
|
||||
Per year: 6.48 MB × 12 = 77.76 MB
|
||||
|
||||
With 3 symbols (SOL, ETH, BTC):
|
||||
Per month: 6.48 MB × 3 = 19.44 MB
|
||||
Per year: 77.76 MB × 3 = 233.28 MB
|
||||
|
||||
CONCLUSION: Negligible storage impact (<1 GB/year for 3 symbols)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Options
|
||||
|
||||
### Option A: Cache-Only (RECOMMENDED for MVP)
|
||||
|
||||
**Pros:**
|
||||
- ✅ Zero database changes required
|
||||
- ✅ Existing infrastructure (market-data-cache.ts)
|
||||
- ✅ Fast implementation (just TradingView alert)
|
||||
- ✅ No storage overhead
|
||||
|
||||
**Cons:**
|
||||
- ❌ No historical analysis (can't backtest ADX patterns)
|
||||
- ❌ Lost on container restart (cache clears)
|
||||
- ❌ 5-minute window only (recent data)
|
||||
|
||||
**Use Case:**
|
||||
- Revenge system ADX validation (real-time only)
|
||||
- Re-entry analytics (already working)
|
||||
- Good for Phase 1 validation
|
||||
|
||||
**Implementation Steps:**
|
||||
1. Create TradingView 1-minute alert (15 lines of Pine Script)
|
||||
2. Point to existing `/api/trading/market-data` endpoint
|
||||
3. Cache handles the rest automatically
|
||||
4. Test with revenge system
|
||||
|
||||
---
|
||||
|
||||
### Option B: Cache + Database Persistence
|
||||
|
||||
**Pros:**
|
||||
- ✅ Historical analysis (backtest ADX-based filters)
|
||||
- ✅ Survives container restarts
|
||||
- ✅ Enables future ML models (train on historical patterns)
|
||||
- ✅ Audit trail for debugging
|
||||
|
||||
**Cons:**
|
||||
- ❌ Requires schema migration
|
||||
- ❌ Need cleanup policy (auto-delete old data)
|
||||
- ❌ Slightly more complex
|
||||
|
||||
**Use Case:**
|
||||
- Long-term data science projects
|
||||
- Pattern recognition (what ADX patterns precede stop hunts?)
|
||||
- System optimization with historical validation
|
||||
|
||||
**Implementation Steps:**
|
||||
1. Add MarketDataSnapshot model to schema
|
||||
2. Update `/api/trading/market-data` to save to DB
|
||||
3. Add cleanup job (delete data >30 days old)
|
||||
4. Create TradingView 1-minute alert
|
||||
5. Build analytics queries
|
||||
|
||||
---
|
||||
|
||||
## Recommended Approach: Hybrid (Start with A, Add B Later)
|
||||
|
||||
### Phase 1: Cache-Only (This Week)
|
||||
```
|
||||
1. Create 1-min TradingView alert
|
||||
2. Point to /api/trading/market-data
|
||||
3. Test revenge ADX validation
|
||||
4. Monitor cache hit rate
|
||||
5. Validate revenge outcomes improve
|
||||
```
|
||||
|
||||
### Phase 2: Add Persistence (After 10+ Revenge Trades)
|
||||
```
|
||||
1. Add MarketDataSnapshot table
|
||||
2. Save historical data
|
||||
3. Backtest: "Would ADX filter have helped?"
|
||||
4. Optimize thresholds based on data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TradingView Alert Setup (1-Minute Data)
|
||||
|
||||
### Pine Script Code
|
||||
```pinescript
|
||||
//@version=5
|
||||
indicator("Market Data Feed (1min)", overlay=true)
|
||||
|
||||
// Calculate metrics
|
||||
atr = ta.atr(14)
|
||||
adx = ta.dmi(14, 14)
|
||||
rsi = ta.rsi(close, 14)
|
||||
volumeRatio = volume / ta.sma(volume, 20)
|
||||
pricePosition = (close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100
|
||||
|
||||
// Alert condition: Every bar close (1 minute)
|
||||
alertcondition(true, title="1min Data Feed", message='
|
||||
{
|
||||
"action": "market_data",
|
||||
"symbol": "{{ticker}}",
|
||||
"timeframe": "1",
|
||||
"atr": ' + str.tostring(atr) + ',
|
||||
"adx": ' + str.tostring(adx) + ',
|
||||
"rsi": ' + str.tostring(rsi) + ',
|
||||
"volumeRatio": ' + str.tostring(volumeRatio) + ',
|
||||
"pricePosition": ' + str.tostring(pricePosition) + ',
|
||||
"currentPrice": ' + str.tostring(close) + ',
|
||||
"indicatorVersion": "v9"
|
||||
}
|
||||
')
|
||||
```
|
||||
|
||||
### Alert Configuration
|
||||
- **Condition:** "Once Per Bar Close"
|
||||
- **Timeframe:** 1 minute chart
|
||||
- **Frequency:** Every 1 minute (24/7)
|
||||
- **Webhook URL:** `https://your-domain.com/api/trading/market-data`
|
||||
- **Symbol:** SOL-PERP (start with one, add more later)
|
||||
|
||||
### Expected Alert Volume
|
||||
```
|
||||
Per symbol: 60 alerts/hour = 1,440 alerts/day
|
||||
With 3 symbols: 4,320 alerts/day
|
||||
TradingView free tier: 400 alerts/month (NOT ENOUGH)
|
||||
TradingView Pro tier: Unlimited alerts ✅
|
||||
|
||||
User needs: Pro or Premium plan ($14.95-$59.95/month)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits Beyond Revenge System
|
||||
|
||||
### 1. Improved Re-Entry Analytics
|
||||
- Current: Uses stale data or historical fallback
|
||||
- With 1-min: Always fresh data (<1 minute old)
|
||||
- Effect: Better manual trade validation
|
||||
|
||||
### 2. Pattern Recognition
|
||||
- Track ADX behavior before/after stop hunts
|
||||
- Identify "fake-out" patterns (ADX spikes then drops)
|
||||
- Optimize entry timing (ADX crossing 20 upward)
|
||||
|
||||
### 3. Market Regime Detection
|
||||
- Real-time: Is market trending or chopping?
|
||||
- Use case: Disable trading during low-ADX periods
|
||||
- Implementation: `if (cache.get('SOL-PERP')?.adx < 15) return 'Market too choppy, skip trade'`
|
||||
|
||||
### 4. Signal Quality Evolution
|
||||
- Compare 1-min vs 5-min ADX at signal time
|
||||
- Question: Does fresher data improve quality scores?
|
||||
- A/B test: 5-min alerts vs 1-min alerts performance
|
||||
|
||||
### 5. Future ML Models
|
||||
- Features: ADX_1min_ago, ADX_5min_ago, ADX_15min_ago
|
||||
- Predict: Will this signal hit TP1 or SL?
|
||||
- Training data: Historical 1-min snapshots + trade outcomes
|
||||
|
||||
---
|
||||
|
||||
## API Endpoint Impact
|
||||
|
||||
### Current `/api/trading/market-data` Endpoint
|
||||
```typescript
|
||||
// app/api/trading/market-data/route.ts
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json()
|
||||
|
||||
// Update cache (already handles this)
|
||||
const cache = getMarketDataCache()
|
||||
cache.set(body.symbol, {
|
||||
atr: body.atr,
|
||||
adx: body.adx,
|
||||
rsi: body.rsi,
|
||||
volumeRatio: body.volumeRatio,
|
||||
pricePosition: body.pricePosition,
|
||||
currentPrice: body.currentPrice,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
// NEW (Phase 2): Save to database
|
||||
if (process.env.STORE_MARKET_DATA === 'true') {
|
||||
await prisma.marketDataSnapshot.create({
|
||||
data: {
|
||||
symbol: body.symbol,
|
||||
timeframe: body.timeframe,
|
||||
atr: body.atr,
|
||||
adx: body.adx,
|
||||
rsi: body.rsi,
|
||||
volumeRatio: body.volumeRatio,
|
||||
pricePosition: body.pricePosition,
|
||||
currentPrice: body.currentPrice,
|
||||
indicatorVersion: body.indicatorVersion
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
}
|
||||
```
|
||||
|
||||
**Rate Limit Considerations:**
|
||||
- 1 alert/minute = 1,440 requests/day per symbol
|
||||
- With 3 symbols = 4,320 requests/day
|
||||
- Current bot handles 10,000+ Position Manager checks/day
|
||||
- **Impact: Negligible** (0.04% increase in total requests)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1: Cache-Only (Immediate)
|
||||
- [ ] Create 1-min TradingView alert on SOL-PERP
|
||||
- [ ] Configure webhook to `/api/trading/market-data`
|
||||
- [ ] Verify cache updates every minute: `curl http://localhost:3001/api/trading/market-data`
|
||||
- [ ] Test revenge system ADX validation with fresh data
|
||||
- [ ] Monitor for 24 hours, check cache staleness
|
||||
- [ ] Add ETH-PERP and BTC-PERP alerts if successful
|
||||
|
||||
### Phase 2: Database Persistence (After Validation)
|
||||
- [ ] Add MarketDataSnapshot model to schema
|
||||
- [ ] Update API to save snapshots (feature flag controlled)
|
||||
- [ ] Add cleanup job (delete data >30 days)
|
||||
- [ ] Create analytics queries (ADX patterns before stop hunts)
|
||||
- [ ] Build historical backtest: "Would ADX filter help?"
|
||||
|
||||
### Phase 3: Revenge System Integration
|
||||
- [ ] Implement Enhancement #1 Option A (fetch fresh ADX)
|
||||
- [ ] Add logging: ADX at stop-out vs ADX at revenge entry
|
||||
- [ ] Track: revenge_with_adx_confirmation vs revenge_without
|
||||
- [ ] After 20 trades: Compare win rates
|
||||
|
||||
---
|
||||
|
||||
## Cost Analysis
|
||||
|
||||
### TradingView Subscription
|
||||
- **Current:** Essential ($14.95/month) - 400 alerts/month (NOT ENOUGH)
|
||||
- **Required:** Pro ($49.95/month) - Unlimited alerts ✅
|
||||
- **Alternative:** Premium ($59.95/month) - More indicators + features
|
||||
|
||||
### Database Storage (Phase 2 Only)
|
||||
- **Monthly:** ~20 MB with 3 symbols
|
||||
- **Annual:** ~240 MB
|
||||
- **Cost:** Free (within PostgreSQL disk allocation)
|
||||
|
||||
### Server Resources
|
||||
- **CPU:** Negligible (cache write = microseconds)
|
||||
- **Memory:** +60 KB per symbol in cache (180 KB total for 3 symbols)
|
||||
- **Network:** 150 bytes × 4,320 alerts/day = 648 KB/day = 19.4 MB/month
|
||||
|
||||
**Total Additional Cost:** ~$35/month (TradingView Pro upgrade)
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
### What If Alerts Fail?
|
||||
**Problem:** TradingView alert service down, network issue, rate limiting
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// In revenge system, check data freshness
|
||||
const cache = getMarketDataCache()
|
||||
const freshData = cache.get(stopHunt.symbol)
|
||||
|
||||
if (!freshData) {
|
||||
console.log('⚠️ No market data in cache, using fallback')
|
||||
// Option 1: Use originalADX as proxy
|
||||
// Option 2: Skip ADX validation (fail-open)
|
||||
// Option 3: Block revenge (fail-closed)
|
||||
}
|
||||
|
||||
const dataAge = Date.now() - freshData.timestamp
|
||||
if (dataAge > 300000) { // >5 minutes old
|
||||
console.log(`⚠️ Stale data (${(dataAge/60000).toFixed(1)}min old)`)
|
||||
// Apply same fallback logic
|
||||
}
|
||||
```
|
||||
|
||||
### What If Cache Overflows?
|
||||
**Not an issue:** Map with 3 symbols = 180 KB memory (negligible)
|
||||
|
||||
### What If Database Grows Too Large?
|
||||
**Solution (Phase 2):**
|
||||
```typescript
|
||||
// Daily cleanup job
|
||||
async function cleanupOldMarketData() {
|
||||
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||||
|
||||
await prisma.marketDataSnapshot.deleteMany({
|
||||
where: { timestamp: { lt: thirtyDaysAgo } }
|
||||
})
|
||||
|
||||
console.log('🗑️ Cleaned up market data older than 30 days')
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **User Decision:** Start with Phase 1 (cache-only) or implement both phases?
|
||||
2. **TradingView Upgrade:** Confirm Pro/Premium subscription for unlimited alerts
|
||||
3. **Symbol Priority:** Start with SOL-PERP only or all 3 symbols?
|
||||
4. **Create Alert:** I'll provide exact Pine Script + webhook config
|
||||
5. **Deploy:** Test for 24 hours before revenge system integration
|
||||
|
||||
**Recommendation:** Start with Phase 1 (cache-only) on SOL-PERP, validate for 1 week, then expand.
|
||||
|
||||
326
docs/ENHANCEMENT_6_ANALYSIS_GUIDE.md
Normal file
326
docs/ENHANCEMENT_6_ANALYSIS_GUIDE.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# Enhancement #6 - SL Distance Analysis Guide
|
||||
|
||||
**Status:** DATA COLLECTION ENABLED (Nov 27, 2025)
|
||||
**Purpose:** Gather revenge trade data to determine optimal SL distance multiplier
|
||||
|
||||
---
|
||||
|
||||
## What We're Tracking Now
|
||||
|
||||
### Database Fields Added
|
||||
```sql
|
||||
-- In StopHunt table:
|
||||
slDistanceAtEntry Float? -- Distance from entry to stop zone (absolute value)
|
||||
revengeOutcome String? -- "TP1", "TP2", "SL", "TRAILING_SL"
|
||||
revengePnL Float? -- Actual P&L from revenge trade
|
||||
revengeFailedReason String? -- "stopped_again" if re-stopped
|
||||
originalATR Float? -- ATR at original stop-out
|
||||
```
|
||||
|
||||
### What Gets Calculated
|
||||
```typescript
|
||||
// When revenge trade executes:
|
||||
const slDistance = direction === 'long'
|
||||
? currentPrice - stopHuntPrice // LONG: Room below entry
|
||||
: stopHuntPrice - currentPrice // SHORT: Room above entry
|
||||
|
||||
// Stored: Math.abs(slDistance)
|
||||
// Logged: Distance in $ and in ATR multiples
|
||||
// Example: "$1.48 distance (2.47× ATR)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Analysis Queries (After 20+ Revenge Trades)
|
||||
|
||||
### Query 1: SL Distance vs Outcome
|
||||
```sql
|
||||
-- Do tighter entries get re-stopped more often?
|
||||
SELECT
|
||||
CASE
|
||||
WHEN "slDistanceAtEntry" / "originalATR" < 1.0 THEN '<1× ATR (Very Tight)'
|
||||
WHEN "slDistanceAtEntry" / "originalATR" < 1.5 THEN '1-1.5× ATR (Tight)'
|
||||
WHEN "slDistanceAtEntry" / "originalATR" < 2.0 THEN '1.5-2× ATR (Moderate)'
|
||||
WHEN "slDistanceAtEntry" / "originalATR" < 2.5 THEN '2-2.5× ATR (Safe)'
|
||||
ELSE '2.5×+ ATR (Very Safe)'
|
||||
END as distance_tier,
|
||||
|
||||
COUNT(*) as total_trades,
|
||||
|
||||
-- Win rate
|
||||
ROUND(100.0 * SUM(CASE
|
||||
WHEN "revengeOutcome" IN ('TP1', 'TP2', 'TRAILING_SL') THEN 1
|
||||
ELSE 0
|
||||
END) / COUNT(*), 1) as win_rate,
|
||||
|
||||
-- Re-stopped rate
|
||||
ROUND(100.0 * SUM(CASE
|
||||
WHEN "revengeFailedReason" = 'stopped_again' THEN 1
|
||||
ELSE 0
|
||||
END) / COUNT(*), 1) as restopped_rate,
|
||||
|
||||
-- Average P&L
|
||||
ROUND(AVG("revengePnL"), 2) as avg_pnl,
|
||||
|
||||
-- Total P&L
|
||||
ROUND(SUM("revengePnL"), 2) as total_pnl
|
||||
|
||||
FROM "StopHunt"
|
||||
WHERE "revengeExecuted" = true
|
||||
AND "slDistanceAtEntry" IS NOT NULL
|
||||
AND "originalATR" IS NOT NULL
|
||||
GROUP BY distance_tier
|
||||
ORDER BY MIN("slDistanceAtEntry" / "originalATR");
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
```
|
||||
distance_tier | total | win_rate | restopped_rate | avg_pnl | total_pnl
|
||||
-----------------------+-------+----------+----------------+---------+-----------
|
||||
<1× ATR (Very Tight) | 5 | 40.0 | 60.0 | -25.50 | -127.50
|
||||
1-1.5× ATR (Tight) | 8 | 62.5 | 25.0 | 15.25 | 122.00
|
||||
1.5-2× ATR (Moderate) | 12 | 75.0 | 16.7 | 42.30 | 507.60
|
||||
2-2.5× ATR (Safe) | 7 | 85.7 | 14.3 | 68.45 | 479.15
|
||||
2.5×+ ATR (Very Safe) | 3 | 100.0 | 0.0 | 92.30 | 276.90
|
||||
```
|
||||
|
||||
**Decision Logic:**
|
||||
- If <1× ATR has high re-stop rate (>50%): Filter needed
|
||||
- If 1.5-2× ATR has best risk/reward: Use 1.5× multiplier
|
||||
- If 2-2.5× ATR has highest win rate: Use 2.0× multiplier
|
||||
- If Very Safe (2.5×+) rarely happens: Lower multiplier to catch more
|
||||
|
||||
---
|
||||
|
||||
### Query 2: Missed Opportunities
|
||||
```sql
|
||||
-- How many revenge opportunities didn't execute because reversal wasn't deep enough?
|
||||
SELECT
|
||||
COUNT(*) as total_stop_hunts,
|
||||
|
||||
-- How many reversed but didn't enter
|
||||
SUM(CASE
|
||||
WHEN "revengeExecuted" = false
|
||||
AND "revengeWindowExpired" = true
|
||||
AND ("lowestPriceAfterStop" < "originalEntryPrice" -- LONG reversed
|
||||
OR "highestPriceAfterStop" > "originalEntryPrice") -- SHORT reversed
|
||||
THEN 1 ELSE 0
|
||||
END) as missed_reversals,
|
||||
|
||||
-- Average distance of missed reversals
|
||||
ROUND(AVG(CASE
|
||||
WHEN "revengeExecuted" = false
|
||||
AND "revengeWindowExpired" = true
|
||||
THEN ABS("lowestPriceAfterStop" - "stopHuntPrice")
|
||||
END), 2) as avg_missed_distance
|
||||
|
||||
FROM "StopHunt"
|
||||
WHERE "originalATR" IS NOT NULL;
|
||||
```
|
||||
|
||||
**Tells us:** If we filter too strictly (e.g., 3× ATR), how many opportunities do we lose?
|
||||
|
||||
---
|
||||
|
||||
### Query 3: Time to Re-Stop
|
||||
```sql
|
||||
-- How quickly do re-stopped revenge trades fail?
|
||||
SELECT
|
||||
CASE
|
||||
WHEN EXTRACT(EPOCH FROM ("Trade"."exitTime" - "StopHunt"."revengeTime")) < 300 THEN '<5min (Instant)'
|
||||
WHEN EXTRACT(EPOCH FROM ("Trade"."exitTime" - "StopHunt"."revengeTime")) < 900 THEN '5-15min (Fast)'
|
||||
WHEN EXTRACT(EPOCH FROM ("Trade"."exitTime" - "StopHunt"."revengeTime")) < 1800 THEN '15-30min (Moderate)'
|
||||
ELSE '30min+ (Slow)'
|
||||
END as time_to_restop,
|
||||
|
||||
COUNT(*) as count,
|
||||
|
||||
ROUND(AVG("StopHunt"."slDistanceAtEntry" / "StopHunt"."originalATR"), 2) as avg_atr_multiple
|
||||
|
||||
FROM "StopHunt"
|
||||
INNER JOIN "Trade" ON "StopHunt"."revengeTradeId" = "Trade"."id"
|
||||
WHERE "StopHunt"."revengeFailedReason" = 'stopped_again'
|
||||
GROUP BY time_to_restop
|
||||
ORDER BY MIN(EXTRACT(EPOCH FROM ("Trade"."exitTime" - "StopHunt"."revengeTime")));
|
||||
```
|
||||
|
||||
**Insight:** If most re-stops happen <5min, they're wicks. Wider SL distance helps.
|
||||
|
||||
---
|
||||
|
||||
### Query 4: Direction-Specific Analysis
|
||||
```sql
|
||||
-- Do LONGs vs SHORTs need different SL distances?
|
||||
SELECT
|
||||
"direction",
|
||||
|
||||
COUNT(*) as total,
|
||||
|
||||
ROUND(AVG("slDistanceAtEntry" / "originalATR"), 2) as avg_atr_multiple,
|
||||
|
||||
ROUND(100.0 * SUM(CASE
|
||||
WHEN "revengeFailedReason" = 'stopped_again' THEN 1 ELSE 0
|
||||
END) / COUNT(*), 1) as restopped_rate,
|
||||
|
||||
ROUND(AVG("revengePnL"), 2) as avg_pnl
|
||||
|
||||
FROM "StopHunt"
|
||||
WHERE "revengeExecuted" = true
|
||||
AND "slDistanceAtEntry" IS NOT NULL
|
||||
GROUP BY "direction";
|
||||
```
|
||||
|
||||
**Possible outcome:** SHORTs need wider distance (more volatile reversals)
|
||||
|
||||
---
|
||||
|
||||
## Decision Matrix (After 20+ Trades)
|
||||
|
||||
### Scenario A: Tight Entries Work
|
||||
**Data shows:** 1-1.5× ATR has 70%+ win rate, low re-stop rate
|
||||
**Decision:** Use 1.5× ATR multiplier (or no filter at all)
|
||||
**Trade-off:** More revenge opportunities, slightly higher re-stop risk
|
||||
|
||||
### Scenario B: Moderate Distance Optimal
|
||||
**Data shows:** 1.5-2× ATR has best risk/reward
|
||||
**Decision:** Use 1.75× or 2.0× ATR multiplier
|
||||
**Trade-off:** Balanced approach (recommended starting point)
|
||||
|
||||
### Scenario C: Wide Distance Required
|
||||
**Data shows:** <2× ATR has >40% re-stop rate
|
||||
**Decision:** Use 2.5× or 3.0× ATR multiplier
|
||||
**Trade-off:** Fewer opportunities, but much higher win rate
|
||||
|
||||
### Scenario D: Direction-Specific
|
||||
**Data shows:** LONGs work at 1.5×, SHORTs need 2.5×
|
||||
**Decision:** Implement separate multipliers per direction
|
||||
**Trade-off:** More complex but optimized
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan (After Data Collection)
|
||||
|
||||
### Step 1: Review Data (After 20 Revenge Trades)
|
||||
```bash
|
||||
# Run Query 1 in database
|
||||
docker exec trading-bot-postgres psql -U postgres -d trading_bot_v4 -c "
|
||||
[Query 1 from above]
|
||||
"
|
||||
|
||||
# Save results to CSV
|
||||
# Analyze in spreadsheet or share with me
|
||||
```
|
||||
|
||||
### Step 2: Calculate Optimal Multiplier
|
||||
```python
|
||||
# Simple calculation:
|
||||
optimal_multiplier = (
|
||||
sum(distance * pnl for each tier) /
|
||||
sum(pnl for each tier)
|
||||
)
|
||||
|
||||
# Weight by win rate:
|
||||
optimal_multiplier_weighted = (
|
||||
sum(distance * win_rate * count for each tier) /
|
||||
sum(win_rate * count for each tier)
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Implement Filter
|
||||
```typescript
|
||||
// In stop-hunt-tracker.ts shouldExecuteRevenge()
|
||||
const slDistance = stopHunt.direction === 'long'
|
||||
? currentPrice - stopHunt.stopHuntPrice
|
||||
: stopHunt.stopHuntPrice - currentPrice
|
||||
|
||||
const minSafeDistance = stopHunt.originalATR * 2.0 // Use data-driven value
|
||||
|
||||
if (Math.abs(slDistance) < minSafeDistance) {
|
||||
console.log(`⚠️ SL distance too tight: ${Math.abs(slDistance).toFixed(2)} < ${minSafeDistance.toFixed(2)}`)
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: A/B Test (Optional)
|
||||
```typescript
|
||||
// Randomly assign filter on/off
|
||||
const useFilter = Math.random() > 0.5
|
||||
|
||||
if (useFilter && Math.abs(slDistance) < minSafeDistance) {
|
||||
await prisma.stopHunt.update({
|
||||
where: { id: stopHunt.id },
|
||||
data: {
|
||||
notes: 'Would have been filtered by SL distance check',
|
||||
filterTestGroup: 'A'
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare groups after 40 trades (20 each)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring Dashboard
|
||||
|
||||
### Key Metrics to Track
|
||||
1. **Re-Stop Rate:** % of revenge trades that hit SL immediately
|
||||
2. **Average SL Distance:** Median ATR multiple at entry
|
||||
3. **Win Rate by Distance:** Scatterplot (x=distance, y=outcome)
|
||||
4. **Missed Opportunities:** Reversals that didn't execute
|
||||
|
||||
### Alert Thresholds
|
||||
- **High re-stop rate:** >30% revenge trades fail = tighten filter
|
||||
- **Low execution rate:** <50% stop hunts execute = loosen filter
|
||||
- **Negative total P&L:** Sum of revenge P&L < 0 = pause system
|
||||
|
||||
---
|
||||
|
||||
## Timeline
|
||||
|
||||
**Week 1-2:** Data collection (first 5-10 revenge trades)
|
||||
**Week 3:** Preliminary analysis (spot obvious patterns)
|
||||
**Week 4:** Full analysis (20+ trades, statistical significance)
|
||||
**Week 5:** Implement optimal filter based on data
|
||||
**Week 6+:** Monitor improvement, adjust if needed
|
||||
|
||||
**Current Status:** ✅ Data collection enabled, awaiting first revenge trades
|
||||
|
||||
---
|
||||
|
||||
## Example Real-World Analysis
|
||||
|
||||
### Hypothetical Results After 25 Revenge Trades
|
||||
```
|
||||
Distance Tier | Trades | Win Rate | Avg P&L | Total P&L
|
||||
---------------------|--------|----------|---------|----------
|
||||
<1× ATR | 3 | 33% | -$45 | -$135
|
||||
1-1.5× ATR | 7 | 57% | $12 | $84
|
||||
1.5-2× ATR | 9 | 78% | $58 | $522
|
||||
2-2.5× ATR | 4 | 75% | $62 | $248
|
||||
2.5×+ ATR | 2 | 100% | $89 | $178
|
||||
```
|
||||
|
||||
**Conclusion from data:**
|
||||
- <1× ATR: High failure rate, negative P&L → FILTER OUT
|
||||
- 1-1.5× ATR: Break-even, mediocre win rate → QUESTIONABLE
|
||||
- 1.5-2× ATR: **SWEET SPOT** - 9 trades, 78% WR, +$522
|
||||
- 2-2.5× ATR: Good but fewer opportunities
|
||||
- 2.5×+ ATR: Perfect WR but rarely happens
|
||||
|
||||
**Optimal setting:** 1.5× ATR minimum distance
|
||||
**Expected impact:** Block 10 bad trades, keep 15 good trades = +$606 net improvement
|
||||
|
||||
---
|
||||
|
||||
## Questions for Future AI Agents
|
||||
|
||||
1. **What's the optimal multiplier?** → Check Query 1 results
|
||||
2. **Are we missing too many opportunities?** → Check Query 2 results
|
||||
3. **Do re-stops happen instantly or slowly?** → Check Query 3 results
|
||||
4. **Do LONGs vs SHORTs need different filters?** → Check Query 4 results
|
||||
5. **Should we implement the filter?** → If re-stop rate >30%, YES
|
||||
|
||||
**Remember:** Data > theory. Don't implement filter until data proves it helps.
|
||||
|
||||
@@ -392,6 +392,11 @@ export class StopHuntTracker {
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
// Calculate SL distance at entry (for Enhancement #6 analysis)
|
||||
const slDistance = stopHunt.direction === 'long'
|
||||
? currentPrice - stopHunt.stopHuntPrice // LONG: Room below entry
|
||||
: stopHunt.stopHuntPrice - currentPrice // SHORT: Room above entry
|
||||
|
||||
// Mark revenge as executed
|
||||
await this.prisma.stopHunt.update({
|
||||
where: { id: stopHunt.id },
|
||||
@@ -400,10 +405,12 @@ export class StopHuntTracker {
|
||||
revengeTradeId: result.trade?.id,
|
||||
revengeEntryPrice: currentPrice,
|
||||
revengeTime: new Date(),
|
||||
slDistanceAtEntry: Math.abs(slDistance), // Store absolute distance
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`✅ REVENGE TRADE EXECUTED: ${result.trade?.id}`)
|
||||
console.log(`📊 SL Distance: $${Math.abs(slDistance).toFixed(2)} (${stopHunt.originalATR ? `${(Math.abs(slDistance) / stopHunt.originalATR).toFixed(2)}× ATR` : 'no ATR'})`)
|
||||
console.log(`🔥 LET'S GET OUR MONEY BACK!`)
|
||||
|
||||
// Send special Telegram notification
|
||||
|
||||
@@ -237,6 +237,7 @@ model StopHunt {
|
||||
revengeTime DateTime? // When revenge executed
|
||||
revengeWindowExpired Boolean @default(false)
|
||||
revengeExpiresAt DateTime // 4 hours after stop hunt
|
||||
slDistanceAtEntry Float? // Distance from entry to stop zone (for Enhancement #6 analysis)
|
||||
|
||||
// Monitoring state
|
||||
highestPriceAfterStop Float? // Track if stop hunt reverses
|
||||
|
||||
Reference in New Issue
Block a user