feat: add position limits and enhance currency formatting
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m22s
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m22s
This commit is contained in:
@@ -16,6 +16,10 @@ services:
|
||||
PRICE_UPDATE_INTERVAL_MINUTES: "${PRICE_UPDATE_INTERVAL_MINUTES:-15}"
|
||||
ZOMBIE_ZERO_COUNT: "${ZOMBIE_ZERO_COUNT:-1000}"
|
||||
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:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -3,8 +3,14 @@ import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { calcTrade } from '@/lib/pricing'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
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({
|
||||
hashtagId: z.string().min(1),
|
||||
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 })
|
||||
}
|
||||
|
||||
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 (!existingPosition || existingPosition.shares < shares) {
|
||||
return NextResponse.json({ error: 'Insufficient shares to sell.' }, { status: 400 })
|
||||
|
||||
@@ -6,6 +6,15 @@ export function cn(...inputs: ClassValue[]) {
|
||||
}
|
||||
|
||||
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', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
|
||||
Reference in New Issue
Block a user