feat: add max position limits to TradePanel and page components for enhanced trading controls
Build Images and Deploy / Update-PROD-Stack (push) Failing after 31s
Build Images and Deploy / Update-PROD-Stack (push) Failing after 31s
This commit is contained in:
@@ -11,11 +11,13 @@ interface Props {
|
|||||||
shortPosition: { shares: number; avgBuyPrice: number } | null
|
shortPosition: { shares: number; avgBuyPrice: number } | null
|
||||||
fundId?: string
|
fundId?: string
|
||||||
fundName?: string
|
fundName?: string
|
||||||
|
maxPositionShares: number
|
||||||
|
maxPositionValue: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tab = 'BUY_LONG' | 'SELL_LONG' | 'BUY_SHORT' | 'SELL_SHORT'
|
type Tab = 'BUY_LONG' | 'SELL_LONG' | 'BUY_SHORT' | 'SELL_SHORT'
|
||||||
|
|
||||||
export function TradePanel({ hashtag, balance, longPosition, shortPosition, fundId, fundName }: Props) {
|
export function TradePanel({ hashtag, balance, longPosition, shortPosition, fundId, fundName, maxPositionShares, maxPositionValue }: Props) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [tab, setTab] = useState<Tab>('BUY_LONG')
|
const [tab, setTab] = useState<Tab>('BUY_LONG')
|
||||||
const [shares, setShares] = useState('')
|
const [shares, setShares] = useState('')
|
||||||
@@ -25,12 +27,19 @@ export function TradePanel({ hashtag, balance, longPosition, shortPosition, fund
|
|||||||
const sharesNum = parseFloat(shares) || 0
|
const sharesNum = parseFloat(shares) || 0
|
||||||
const cost = sharesNum * hashtag.currentPrice
|
const cost = sharesNum * hashtag.currentPrice
|
||||||
|
|
||||||
const maxBuyShares = hashtag.currentPrice > 0 ? Math.floor((balance / hashtag.currentPrice) * 100) / 100 : 0
|
// For buys: max is the lowest of (balance cap, shares cap, value cap) minus existing position
|
||||||
|
const existingBuyShares = tab === 'BUY_LONG' ? (longPosition?.shares ?? 0) : (shortPosition?.shares ?? 0)
|
||||||
|
const remainingShareCap = Math.max(0, maxPositionShares - existingBuyShares)
|
||||||
|
const remainingValueCap = Math.max(0, maxPositionValue - existingBuyShares * hashtag.currentPrice)
|
||||||
|
const sharesFromValueCap = hashtag.currentPrice > 0 ? remainingValueCap / hashtag.currentPrice : 0
|
||||||
|
const sharesFromBalance = hashtag.currentPrice > 0 ? balance / hashtag.currentPrice : 0
|
||||||
|
const maxBuyShares = Math.floor(Math.min(remainingShareCap, sharesFromValueCap, sharesFromBalance) * 100) / 100
|
||||||
|
|
||||||
const maxSellShares =
|
const maxSellShares =
|
||||||
tab === 'SELL_LONG' ? longPosition?.shares ?? 0 : shortPosition?.shares ?? 0
|
tab === 'SELL_LONG' ? longPosition?.shares ?? 0 : shortPosition?.shares ?? 0
|
||||||
|
|
||||||
const canAfford =
|
const canAfford =
|
||||||
tab === 'BUY_LONG' || tab === 'BUY_SHORT' ? cost <= balance : sharesNum <= (maxSellShares ?? 0)
|
tab === 'BUY_LONG' || tab === 'BUY_SHORT' ? cost <= balance && sharesNum <= maxBuyShares : sharesNum <= (maxSellShares ?? 0)
|
||||||
|
|
||||||
async function handleTrade() {
|
async function handleTrade() {
|
||||||
if (sharesNum <= 0) return
|
if (sharesNum <= 0) return
|
||||||
@@ -129,6 +138,9 @@ export function TradePanel({ hashtag, balance, longPosition, shortPosition, fund
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Max
|
Max
|
||||||
|
{(tab === 'BUY_LONG' || tab === 'BUY_SHORT') && maxBuyShares === 0
|
||||||
|
? ' (limit reached)'
|
||||||
|
: null}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import { AutoRefresh } from '@/components/AutoRefresh'
|
|||||||
|
|
||||||
const ZOMBIE_ZERO_COUNT = parseInt(process.env.ZOMBIE_ZERO_COUNT ?? '1000', 10)
|
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)
|
const PRICE_UPDATE_INTERVAL_MINUTES = parseInt(process.env.PRICE_UPDATE_INTERVAL_MINUTES ?? '60', 10)
|
||||||
|
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)
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
@@ -128,7 +132,7 @@ export default async function HashtagPage({ params, searchParams }: Props) {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<AutoRefresh intervalMs={30_000} />
|
<AutoRefresh intervalMs={30_000} />
|
||||||
{/* Header */}}
|
{/* Header */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-end justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-end justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">#{hashtag.displayTag}</h1>
|
<h1 className="text-3xl font-bold">#{hashtag.displayTag}</h1>
|
||||||
@@ -193,6 +197,8 @@ export default async function HashtagPage({ params, searchParams }: Props) {
|
|||||||
shortPosition={activeShort ? { shares: activeShort.shares, avgBuyPrice: activeShort.avgBuyPrice } : null}
|
shortPosition={activeShort ? { shares: activeShort.shares, avgBuyPrice: activeShort.avgBuyPrice } : null}
|
||||||
fundId={fundContext?.id}
|
fundId={fundContext?.id}
|
||||||
fundName={fundContext?.name}
|
fundName={fundContext?.name}
|
||||||
|
maxPositionShares={fundContext ? FUND_MAX_POSITION_SHARES : MAX_POSITION_SHARES}
|
||||||
|
maxPositionValue={fundContext ? FUND_MAX_POSITION_VALUE : MAX_POSITION_VALUE}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-surface-card border border-surface-border rounded-xl p-6 text-center">
|
<div className="bg-surface-card border border-surface-border rounded-xl p-6 text-center">
|
||||||
|
|||||||
Reference in New Issue
Block a user