feat: add position limits and enhance currency formatting
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m22s

This commit is contained in:
2026-03-19 22:05:15 -04:00
parent 82b1953c8b
commit d0dc52f82b
3 changed files with 38 additions and 0 deletions
+4
View File
@@ -16,6 +16,10 @@ services:
PRICE_UPDATE_INTERVAL_MINUTES: "${PRICE_UPDATE_INTERVAL_MINUTES:-15}" PRICE_UPDATE_INTERVAL_MINUTES: "${PRICE_UPDATE_INTERVAL_MINUTES:-15}"
ZOMBIE_ZERO_COUNT: "${ZOMBIE_ZERO_COUNT:-1000}" ZOMBIE_ZERO_COUNT: "${ZOMBIE_ZERO_COUNT:-1000}"
HASHTAG_ACTIVE_HOURS: "${HASHTAG_ACTIVE_HOURS:-24}" HASHTAG_ACTIVE_HOURS: "${HASHTAG_ACTIVE_HOURS:-24}"
MAX_POSITION_SHARES: "${MAX_POSITION_SHARES:-100}"
MAX_POSITION_VALUE: "${MAX_POSITION_VALUE:-1000}"
FUND_MAX_POSITION_SHARES: "${FUND_MAX_POSITION_SHARES:-1000}"
FUND_MAX_POSITION_VALUE: "${FUND_MAX_POSITION_VALUE:-10000}"
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
+25
View File
@@ -3,8 +3,14 @@ import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth' import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma' import { prisma } from '@/lib/prisma'
import { calcTrade } from '@/lib/pricing' import { calcTrade } from '@/lib/pricing'
import { formatCurrency } from '@/lib/utils'
import { z } from 'zod' import { z } from 'zod'
const MAX_POSITION_SHARES = parseInt(process.env.MAX_POSITION_SHARES ?? '100', 10)
const MAX_POSITION_VALUE = parseInt(process.env.MAX_POSITION_VALUE ?? '1000', 10)
const FUND_MAX_POSITION_SHARES = parseInt(process.env.FUND_MAX_POSITION_SHARES ?? '1000', 10)
const FUND_MAX_POSITION_VALUE = parseInt(process.env.FUND_MAX_POSITION_VALUE ?? '10000', 10)
const tradeSchema = z.object({ const tradeSchema = z.object({
hashtagId: z.string().min(1), hashtagId: z.string().min(1),
type: z.enum(['BUY_LONG', 'SELL_LONG', 'BUY_SHORT', 'SELL_SHORT']), type: z.enum(['BUY_LONG', 'SELL_LONG', 'BUY_SHORT', 'SELL_SHORT']),
@@ -63,6 +69,25 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: 'Insufficient balance.' }, { status: 400 }) return NextResponse.json({ error: 'Insufficient balance.' }, { status: 400 })
} }
if (type === 'BUY_LONG' || type === 'BUY_SHORT') {
const maxShares = fundId ? FUND_MAX_POSITION_SHARES : MAX_POSITION_SHARES
const maxValue = fundId ? FUND_MAX_POSITION_VALUE : MAX_POSITION_VALUE
const newTotalShares = (existingPosition?.shares ?? 0) + shares
const newTotalValue = newTotalShares * hashtag.currentPrice
if (newTotalShares > maxShares) {
return NextResponse.json(
{ error: `Position limit: max ${maxShares.toLocaleString()} shares per hashtag.` },
{ status: 400 },
)
}
if (newTotalValue > maxValue) {
return NextResponse.json(
{ error: `Position limit: max ${formatCurrency(maxValue)} position value per hashtag.` },
{ status: 400 },
)
}
}
if (type === 'SELL_LONG') { if (type === 'SELL_LONG') {
if (!existingPosition || existingPosition.shares < shares) { if (!existingPosition || existingPosition.shares < shares) {
return NextResponse.json({ error: 'Insufficient shares to sell.' }, { status: 400 }) return NextResponse.json({ error: 'Insufficient shares to sell.' }, { status: 400 })
+9
View File
@@ -6,6 +6,15 @@ export function cn(...inputs: ClassValue[]) {
} }
export function formatCurrency(value: number): string { export function formatCurrency(value: number): string {
const abs = Math.abs(value)
if (abs >= 10_000) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
maximumFractionDigits: 2,
}).format(value)
}
return new Intl.NumberFormat('en-US', { return new Intl.NumberFormat('en-US', {
style: 'currency', style: 'currency',
currency: 'USD', currency: 'USD',