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}"
|
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
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user