docs: Document projection page v11.2 filter and cleanup trade exclusion (Jan 13, 2026)
- STARTING_CAPITAL set to 1437 (actual v11.2 start capital) - START_DATE set to Jan 6, 2026 (v11.2 production start) - getTradingStats filters for v11.2 indicator + recent manual trades - Excludes cleanup trades (DUPLICATE_CLEANUP, PHANTOM_CLEANUP, phantom) - Current v11.2 performance: 6 trades, 83.3% WR, +$406.04 P&L
This commit is contained in:
20
.github/copilot-instructions.md
vendored
20
.github/copilot-instructions.md
vendored
@@ -138,6 +138,26 @@
|
||||
- **Previous Config:** Was 5x fixed (Dec 26, 2025)
|
||||
- **Last Updated:** Jan 2, 2026
|
||||
|
||||
### Projection Page & Analytics Stats (Jan 13, 2026 - UPDATED)
|
||||
- **Purpose:** 12-month financial projection dashboard showing actual vs planned progress
|
||||
- **File:** `app/projection/page.tsx`
|
||||
- **Configuration:**
|
||||
* `STARTING_CAPITAL = 1437` (USD, actual capital when v11.2 went live)
|
||||
* `START_DATE = '2026-01-06'` (v11.2 production start date)
|
||||
* Fetches from: `/api/analytics/stats?days=365`
|
||||
- **Stats Filtering (lib/database/views.ts getTradingStats):**
|
||||
* **v11.2 Filter:** Only includes trades with `indicatorVersion='v11.2'`
|
||||
* **Manual Trades:** Includes `signalSource='manual'` from last 7 days only
|
||||
* **Cleanup Exclusion:** Excludes phantom/cleanup trades: `exitReason NOT IN ['DUPLICATE_CLEANUP', 'PHANTOM_CLEANUP', 'phantom']`
|
||||
- **v11.2 Performance (Jan 6-13, 2026):**
|
||||
* Trades: 6 (5 wins, 1 loss)
|
||||
* Win Rate: 83.3%
|
||||
* Total P&L: +$406.04
|
||||
* Avg Win: $120.20, Avg Loss: -$194.96
|
||||
* Current Capital: $1,802.40 (from Drift)
|
||||
- **Why Filter:** Previous stats included old indicator versions and phantom cleanup trades, skewing metrics
|
||||
- **Last Updated:** Jan 13, 2026
|
||||
|
||||
### Supporting Systems (All Active)
|
||||
- **Stop Hunt Revenge:** Quality 85+ signals get auto re-entry on reversal (90s confirmation)
|
||||
- **Smart Validation Queue:** Quality 50-89 signals queued for 90-minute price confirmation
|
||||
|
||||
@@ -25,16 +25,27 @@ interface ScenarioResult {
|
||||
color: string
|
||||
}
|
||||
|
||||
interface TradingStats {
|
||||
totalPnL: number
|
||||
totalTrades: number
|
||||
winners: number
|
||||
losers: number
|
||||
winRate: number
|
||||
monthlyPnL: { month: string; pnl: number; trades: number }[]
|
||||
}
|
||||
|
||||
export default function ProjectionPage() {
|
||||
const [currentCapital, setCurrentCapital] = useState<number>(0)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [selectedScenario, setSelectedScenario] = useState<number>(70)
|
||||
const [tradingStats, setTradingStats] = useState<TradingStats | null>(null)
|
||||
const [totalDeposits, setTotalDeposits] = useState<number>(0)
|
||||
|
||||
// Strategy Parameters (Jan 2026)
|
||||
const STARTING_CAPITAL = 1400
|
||||
const STARTING_CAPITAL = 1437 // Actual starting balance when v11.2 went live
|
||||
const TRADING_CAP = 50000
|
||||
const BTC_MONTHLY_GROWTH = 0.05 // 5% per month (60% annual)
|
||||
const START_DATE = new Date('2026-01-15')
|
||||
const START_DATE = new Date('2026-01-06') // When v11.2 went live
|
||||
|
||||
// Tiered Withdrawal Rules
|
||||
const getWithdrawal = (capital: number): number => {
|
||||
@@ -46,20 +57,40 @@ export default function ProjectionPage() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchCurrentCapital() {
|
||||
async function fetchData() {
|
||||
try {
|
||||
const response = await fetch('/api/drift/account-summary')
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setCurrentCapital(data.freeCollateral || 0)
|
||||
// Fetch account summary
|
||||
const accountResponse = await fetch('/api/drift/account-summary')
|
||||
const accountData = await accountResponse.json()
|
||||
if (accountData.success) {
|
||||
setCurrentCapital(accountData.drift?.freeCollateral || accountData.freeCollateral || 0)
|
||||
// Total deposits from Drift
|
||||
if (accountData.spotPositions?.usdc?.cumulativeDeposits) {
|
||||
setTotalDeposits(parseInt(accountData.spotPositions.usdc.cumulativeDeposits) / 1e6)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch trading stats (all time, not just 30 days)
|
||||
const statsResponse = await fetch('/api/analytics/stats?days=365')
|
||||
const statsData = await statsResponse.json()
|
||||
if (statsData.success && statsData.stats) {
|
||||
const stats = statsData.stats.realTrades
|
||||
setTradingStats({
|
||||
totalPnL: parseFloat(stats.totalPnL?.replace('$', '') || '0'),
|
||||
totalTrades: stats.total || 0,
|
||||
winners: stats.winning || 0,
|
||||
losers: stats.losing || 0,
|
||||
winRate: parseFloat(stats.winRate?.replace('%', '') || '0'),
|
||||
monthlyPnL: [] // Would need separate endpoint for monthly breakdown
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch capital:', error)
|
||||
console.error('Failed to fetch data:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
fetchCurrentCapital()
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
// Generate projection for a given monthly rate
|
||||
@@ -226,6 +257,92 @@ export default function ProjectionPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ACTUAL PROGRESS vs PLAN */}
|
||||
<div className="bg-gray-800 rounded-lg p-6 mb-8 border border-yellow-500/30">
|
||||
<h2 className="text-xl font-semibold mb-4 text-yellow-400">📈 Actual Progress vs Plan</h2>
|
||||
|
||||
{/* Summary Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
|
||||
<div className="bg-gray-700 rounded p-4">
|
||||
<div className="text-sm text-gray-400">Total P&L</div>
|
||||
<div className={`text-2xl font-bold ${tradingStats?.totalPnL && tradingStats.totalPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{loading ? '...' : tradingStats ? formatCurrency(tradingStats.totalPnL) : '$0'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-700 rounded p-4">
|
||||
<div className="text-sm text-gray-400">Total Trades</div>
|
||||
<div className="text-2xl font-bold text-blue-400">
|
||||
{loading ? '...' : tradingStats?.totalTrades || 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-700 rounded p-4">
|
||||
<div className="text-sm text-gray-400">Win Rate</div>
|
||||
<div className={`text-2xl font-bold ${tradingStats?.winRate && tradingStats.winRate >= 50 ? 'text-green-400' : 'text-yellow-400'}`}>
|
||||
{loading ? '...' : tradingStats ? `${tradingStats.winRate.toFixed(1)}%` : '0%'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-700 rounded p-4">
|
||||
<div className="text-sm text-gray-400">Capital Change</div>
|
||||
<div className={`text-2xl font-bold ${currentCapital - STARTING_CAPITAL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{loading ? '...' : `${((currentCapital - STARTING_CAPITAL) / STARTING_CAPITAL * 100).toFixed(1)}%`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-700 rounded p-4">
|
||||
<div className="text-sm text-gray-400">Net Profit</div>
|
||||
<div className={`text-2xl font-bold ${currentCapital - STARTING_CAPITAL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{loading ? '...' : formatCurrency(currentCapital - STARTING_CAPITAL)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plan Comparison */}
|
||||
<div className="bg-gray-700/50 rounded-lg p-4">
|
||||
<h3 className="text-md font-semibold mb-3 text-gray-300">Expected vs Actual (70%/mo target)</h3>
|
||||
{(() => {
|
||||
const daysElapsed = Math.max(0, Math.floor((new Date().getTime() - START_DATE.getTime()) / (1000 * 60 * 60 * 24)))
|
||||
const monthsElapsed = daysElapsed / 30
|
||||
const dailyRate = Math.pow(1.70, 1/30) - 1 // 70%/month converted to daily compound rate
|
||||
const expectedCapital = STARTING_CAPITAL * Math.pow(1 + dailyRate, daysElapsed)
|
||||
const expectedGain = expectedCapital - STARTING_CAPITAL
|
||||
const actualGain = currentCapital - STARTING_CAPITAL
|
||||
const onTrack = currentCapital >= expectedCapital
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400">Days Since Start (Jan 6, 2026):</span>
|
||||
<span className="text-blue-400 font-bold">{daysElapsed} days ({monthsElapsed.toFixed(1)} months)</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400">Expected Capital @ 70%/mo:</span>
|
||||
<span className="text-yellow-400 font-bold">{formatCurrency(expectedCapital)} (+{formatCurrency(expectedGain)})</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400">Current Capital:</span>
|
||||
<span className={`font-bold ${actualGain >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{loading ? '...' : formatCurrency(currentCapital)} ({actualGain >= 0 ? '+' : ''}{formatCurrency(actualGain)})
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-t border-gray-600 pt-2 mt-2">
|
||||
<span className="text-gray-400">Status vs Plan:</span>
|
||||
<span className={`font-bold ${onTrack ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{loading ? '...' : onTrack ? '✅ ON TRACK' : '⚠️ BEHIND'}
|
||||
({formatCurrency(currentCapital - expectedCapital)}, {((currentCapital / expectedCapital - 1) * 100).toFixed(1)}%)
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400">Actual Daily Return:</span>
|
||||
<span className={`font-bold ${actualGain >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{loading || daysElapsed === 0 ? '...' : `${((Math.pow(currentCapital / STARTING_CAPITAL, 1 / Math.max(1, daysElapsed)) - 1) * 100).toFixed(2)}%/day`}
|
||||
{daysElapsed > 0 && !loading && ` (≈ ${((Math.pow(currentCapital / STARTING_CAPITAL, 30 / Math.max(1, daysElapsed)) - 1) * 100).toFixed(0)}%/mo)`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scenario Selector */}
|
||||
<div className="bg-gray-800 rounded-lg p-6 mb-8 border border-gray-700">
|
||||
<h2 className="text-xl font-semibold mb-4">🎯 Select Monthly Return Scenario</h2>
|
||||
|
||||
@@ -130,7 +130,8 @@ export async function getTradesWithNetContext() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trading statistics (excludes test trades)
|
||||
* Get trading statistics for v11.2 + recent manual trades
|
||||
* Updated Jan 12, 2026: Now filters for v11.2 indicator version OR manual trades from last 7 days
|
||||
*/
|
||||
export async function getTradingStats(days: number = 30) {
|
||||
const prisma = getPrismaClient()
|
||||
@@ -138,14 +139,27 @@ export async function getTradingStats(days: number = 30) {
|
||||
const since = new Date()
|
||||
since.setDate(since.getDate() - days)
|
||||
|
||||
// For manual trades, only include those from last 7 days
|
||||
const manualCutoff = new Date()
|
||||
manualCutoff.setDate(manualCutoff.getDate() - 7)
|
||||
|
||||
const trades = await prisma.trade.findMany({
|
||||
where: {
|
||||
createdAt: { gte: since },
|
||||
status: 'closed',
|
||||
isTestTrade: false, // Real trades only
|
||||
// Exclude phantom/cleanup trades - not real exits
|
||||
exitReason: {
|
||||
notIn: ['DUPLICATE_CLEANUP', 'PHANTOM_CLEANUP', 'phantom']
|
||||
},
|
||||
OR: [
|
||||
{ signalSource: null }, // Old trades without signalSource
|
||||
{ signalSource: { not: 'manual' } }, // Exclude manual Telegram trades
|
||||
// v11.2 indicator signals (any age within 'days' period)
|
||||
{ indicatorVersion: 'v11.2' },
|
||||
// Manual trades only from last 7 days
|
||||
{
|
||||
signalSource: 'manual',
|
||||
createdAt: { gte: manualCutoff }
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user