zombie alerts
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m22s

This commit is contained in:
2026-03-19 18:38:15 -04:00
parent 54ecf35cf3
commit c3b0055572
+27 -1
View File
@@ -4,7 +4,11 @@ import { authOptions } from '@/lib/auth'
import { redirect } from 'next/navigation' import { redirect } from 'next/navigation'
import { formatCurrency, formatNumber, formatPnl, pnlColor } from '@/lib/utils' import { formatCurrency, formatNumber, formatPnl, pnlColor } from '@/lib/utils'
import Link from 'next/link' import Link from 'next/link'
import { Coins } from 'lucide-react' import { Coins, AlertTriangle } from 'lucide-react'
import { formatDistanceToNow } from 'date-fns'
const ZOMBIE_ZERO_COUNT = parseInt(process.env.ZOMBIE_ZERO_COUNT ?? '1000', 10)
const PRICE_UPDATE_INTERVAL_MINUTES = parseInt(process.env.PRICE_UPDATE_INTERVAL_MINUTES ?? '60', 10)
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -50,6 +54,7 @@ export default async function PositionsPage() {
tag: true, tag: true,
displayTag: true, displayTag: true,
currentPrice: true, currentPrice: true,
zeroCount: true,
priceHistory: { priceHistory: {
orderBy: { recordedAt: 'asc' }, orderBy: { recordedAt: 'asc' },
take: 20, take: 20,
@@ -98,6 +103,18 @@ export default async function PositionsPage() {
const currentValue = pos.hashtag.currentPrice * pos.shares const currentValue = pos.hashtag.currentPrice * pos.shares
const pnlPct = costBasis > 0 ? (pnl / costBasis) * 100 : 0 const pnlPct = costBasis > 0 ? (pnl / costBasis) * 100 : 0
const sparkPrices = pos.hashtag.priceHistory.map((h) => h.price) const sparkPrices = pos.hashtag.priceHistory.map((h) => h.price)
const isZombieWarning = pos.hashtag.zeroCount >= ZOMBIE_ZERO_COUNT * 0.9
const zombieSince = isZombieWarning
? formatDistanceToNow(
new Date(Date.now() - pos.hashtag.zeroCount * PRICE_UPDATE_INTERVAL_MINUTES * 60 * 1000),
)
: null
const zombieEta = isZombieWarning
? formatDistanceToNow(
new Date(Date.now() + (ZOMBIE_ZERO_COUNT - pos.hashtag.zeroCount) * PRICE_UPDATE_INTERVAL_MINUTES * 60 * 1000),
{ addSuffix: true },
)
: null
return ( return (
<div <div
@@ -114,6 +131,15 @@ export default async function PositionsPage() {
> >
#{pos.hashtag.displayTag} #{pos.hashtag.displayTag}
</Link> </Link>
{isZombieWarning && zombieSince && zombieEta && (
<span
title={`No activity detected for ${zombieSince}. Estimated auto-liquidation ${zombieEta}.`}
className="inline-flex items-center gap-1 text-xs font-medium px-1.5 py-0.5 rounded bg-amber-500/15 text-amber-400 border border-amber-500/20 mt-0.5"
>
<AlertTriangle className="h-3 w-3" />
No activity for {zombieSince} · Liquidates {zombieEta}
</span>
)}
<span <span
className={`text-xs font-medium px-1.5 py-0.5 rounded ${ className={`text-xs font-medium px-1.5 py-0.5 rounded ${
pos.positionType === 'LONG' pos.positionType === 'LONG'