Commit 6e27521a authored by Anthony Jacob's avatar Anthony Jacob
Browse files

add extra info to portfolio detail

parent 85a78839
Loading
Loading
Loading
Loading
+53 −33
Original line number Diff line number Diff line
@@ -28,27 +28,12 @@ export default function PortfolioViewDetailClient(props: {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
    });
    const getEntryPrice = (position: any) => {
        const qty = getPositionQuantity(position);
        const unitPrice = Number(position.buyUnitPrice);
        const fees = Number(position.buyFees ?? 0);
        const taxes = Number(position.buyTaxes ?? 0);
        if (qty > 0) {
            return unitPrice + (fees + taxes) / qty;
        }
        return unitPrice;
    };
    const getEntryPrice = (position: any) => Number(position.buyUnitPrice);
    const getCurrentPrice = (position: any) => Number(position.currentPrice);
    const getPositionValue = (position: any) => getPositionQuantity(position) * getCurrentPrice(position);
    const getOpenPositionFeesAndTaxes = (position: any) => Number(position?.buyFees ?? 0) + Number(position?.buyTaxes ?? 0)


    const getPositionValue = (position: any) => {
        const explicitValue = Number(position?.currentValue ?? position?.marketValue ?? position?.value ?? 0);
        if (Number.isFinite(explicitValue) && explicitValue !== 0) {
            return explicitValue;
        }
        const qty = getPositionQuantity(position);
        const price = getCurrentPrice(position);
        return qty * price;
    };

    const fetchPortfolioDetail = async () => {
        setIsLoading(true);
@@ -137,26 +122,33 @@ export default function PortfolioViewDetailClient(props: {
        }
    };

    const activePortfolioValue = Number(portfolio?.totalOpenPositionsValue ?? 0);
    const profitLosses = Number(activePortfolioValue + Number(portfolio?.currentCash ?? 0) - Number(portfolio?.initialCash ?? 0));
    const activePortfolioValue = Number(portfolio?.totalOpenPositionsCurrentValue ?? 0);
    const profitLosses = activePortfolioValue - Number(portfolio?.totalOpenPositionsOriginalBuyValue ?? 0);
    const profitLossesPercent = (profitLosses / Number(portfolio?.initialCash ?? 0)) * 100;
    const totalProfitLosses = activePortfolioValue + Number(portfolio?.currentCash ?? 0) - Number(portfolio?.initialCash ?? 0);


    const groupedPositionsList = Object.entries(positionsByTicker).map(([ticker, items]) => {
        const positions = Array.isArray(items) ? items : [];
        const summary = positions.reduce((acc, position) => {
            const qty = getPositionQuantity(position);
            const previousQuantity = acc.quantity;
            const entryPrice = getEntryPrice(position);
            const currentPrice = getCurrentPrice(position);
            const positionValue = getPositionValue(position);
            const pnl = Number(positionValue - qty * entryPrice);
            const feesAndTaxes = getOpenPositionFeesAndTaxes(position);
            const pnlFeesAndTaxesIncl = pnl - feesAndTaxes;

            acc.quantity += qty;
            acc.entryValue += qty * entryPrice;
            acc.AvgEntryValue = ((acc.AvgEntryValue * previousQuantity) + (qty * entryPrice)) / (previousQuantity + qty);
            acc.currentValue += positionValue;
            acc.pnl += pnl;
            acc.lastPrice = currentPrice || acc.lastPrice;
            acc.feesAndTaxes += feesAndTaxes;
            acc.pnlFeesAndTaxesIncl += pnlFeesAndTaxesIncl;
            return acc;
        }, { ticker, name: items[0]?.name || '', quantity: 0, entryValue: 0, currentValue: 0, pnl: 0, lastPrice: 0 });
        }, { ticker, name: items[0]?.name || '', quantity: 0, AvgEntryValue: 0, currentValue: 0, pnl: 0, lastPrice: 0, feesAndTaxes: 0, pnlFeesAndTaxesIncl: 0 });

        return summary;
    });
@@ -193,22 +185,30 @@ export default function PortfolioViewDetailClient(props: {
                            <div className="text-2xl font-semibold text-gray-900">{formatPrice(activePortfolioValue)}</div>
                        </div>
                        <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
                            <div className="text-sm text-gray-500">Profit / losses</div>
                            <div className="text-sm text-gray-500">Profit / losses (portfolio without Fees & Taxes)</div>
                            <div className={`text-2xl font-semibold text-gray-900 ${profitLosses >= 0 ? 'text-green-600' : 'text-red-600'}`}>{formatPrice(profitLosses)}  /  {formatPrice(profitLossesPercent)}%</div>
                        </div>
                        <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
                            <div className="text-sm text-gray-500">Fees & Taxes</div>
                            <div className={`text-2xl font-semibold text-gray-900 text-red-600`}>-{formatPrice(portfolio.totalFeesAndTaxes)}</div>
                        </div>
                        <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
                            <div className="text-sm text-gray-500">Total Profit / losses </div>
                            <div className={`text-2xl font-semibold text-gray-900 ${totalProfitLosses >= 0 ? 'text-green-600' : 'text-red-600'}`}>{formatPrice(totalProfitLosses)}</div>
                        </div>

                    </div>

                    <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
                        <div className="">
                            {portfolio.rateLimit && portfolio.rateRemaining && portfolio.usageLimit && portfolio.usageRemaining && (
                                <div className="mb-4 rounded-md bg-blue-50 p-4">
                                <div className="mb-4 rounded-md bg-sky-50 p-4">
                                    <div className="flex">
                                        <div className="flex-shrink-0">
                                            <i className="fa-solid fa-triangle-exclamation text-blue-400"></i>
                                            <i className="fa-solid fa-triangle-exclamation text-sky-400"></i>
                                        </div>
                                        <div className="ml-3">
                                            <h3 className="text-sm font-medium text-blue-800">Stock Data Info</h3>
                                            <h3 className="text-sm font-medium text-sky-800">Stock Data Info</h3>
                                            Rate limit Remaining: {portfolio.rateRemaining} / {portfolio.rateLimit} <br />
                                            Usage limit Remaining: {portfolio.usageRemaining} / {portfolio.usageLimit}
                                        </div>
@@ -268,26 +268,33 @@ export default function PortfolioViewDetailClient(props: {
                                            <th className="border border-gray-200 px-4 py-2 text-right">Last Price</th>
                                            <th className="border border-gray-200 px-4 py-2 text-right">Current Value</th>
                                            <th className="border border-gray-200 px-4 py-2 text-right">P/L</th>
                                            <th className="border border-gray-200 px-4 py-2 text-right">Fees & Taxes</th>
                                            <th className="border border-gray-200 px-4 py-2 text-right">P/L (fees & taxes incl.)</th>
                                            <th className="border border-gray-200 px-4 py-2 text-right"></th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        {groupedPositionsList.map((group: any) => {
                                            const avgEntry = group.quantity ? group.entryValue / group.quantity : 0;

                                            return (
                                                <tr key={group.ticker}>
                                                    <td className="border border-gray-200 px-4 py-2">{`${group.name} (${group.ticker})`}</td>
                                                    <td className="border border-gray-200 px-4 py-2 text-right">{Number(group.quantity).toLocaleString()}</td>
                                                    <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(avgEntry)}</td>
                                                    <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.AvgEntryValue)}</td>
                                                    <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.lastPrice)}</td>
                                                    <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.currentValue)}</td>
                                                    <td className={`border border-gray-200 px-4 py-2 text-right ${group.pnl >= 0 ? 'text-green-600' : 'text-red-600'}`}>
                                                        {formatPrice(group.pnl)}
                                                    </td>

                                                    <td className={`border border-gray-200 px-4 py-2 text-right text-red-600`}>
                                                        -{formatPrice(group.feesAndTaxes)}
                                                    </td>
                                                    <td className={`border border-gray-200 px-4 py-2 text-right ${group.pnlFeesAndTaxesIncl >= 0 ? 'text-green-600' : 'text-red-600'}`}>
                                                        {formatPrice(group.pnlFeesAndTaxesIncl)}
                                                    </td>
                                                    <td className="border border-gray-200 px-4 py-2 text-right">
                                                        <Link href={`/app/portfolios/view/${portfolio.id}/chat?ticker=${group.ticker}`}
                                                            className="rounded-full bg-blue-500 px-3 py-1 text-sm leading-5 font-semibold text-white hover:bg-blue-700 items-center gap-2 cursor-pointer"
                                                            className="rounded-full bg-sky-500 px-3 py-1 text-sm leading-5 font-semibold text-white hover:bg-sky-700 items-center gap-2 cursor-pointer"
                                                        >
                                                            <i className="fa-solid fa-eye mr-2"></i>
                                                            <span>Go to chat history</span>
@@ -347,6 +354,9 @@ export default function PortfolioViewDetailClient(props: {
                                                    <th className="border border-gray-200 px-4 py-2 text-right">Entry Price</th>
                                                    <th className="border border-gray-200 px-4 py-2 text-right">Exit Price</th>
                                                    <th className="border border-gray-200 px-4 py-2 text-right">P/L</th>
                                                    <th className="border border-gray-200 px-4 py-2 text-right">Buy Fees & Taxes</th>
                                                    <th className="border border-gray-200 px-4 py-2 text-right">Sell Fees & Taxes</th>
                                                    <th className="border border-gray-200 px-4 py-2 text-right">P/L (Fees & Taxes Incl.)</th>
                                                    <th className="border border-gray-200 px-4 py-2 text-right">Closed At</th>
                                                </tr>
                                            </thead>
@@ -359,17 +369,27 @@ export default function PortfolioViewDetailClient(props: {
                                                    const sellPrice = Number(position?.sellUnitPrice ?? 0);
                                                    const sellFees = Number(position?.sellFees ?? 0);
                                                    const sellTaxes = Number(position?.sellTaxes ?? 0);
                                                    const pnl = sellPrice * qty - (sellFees + sellTaxes) - (buyPrice * qty + buyFees + buyTaxes);
                                                    const pnl = sellPrice * qty - buyPrice * qty;
                                                    const pnlFeesAndTaxesIncl = sellPrice * qty - (sellFees + sellTaxes) - (buyPrice * qty + buyFees + buyTaxes);
                                                    const closedAt = position?.sellDate || position?.closedAt || position?.closeDate || position?.closedDate;
                                                    return (
                                                        <tr key={position?.id ?? `${getPositionTicker(position)}-${closedAt || Math.random()}`}>
                                                            <td className="border border-gray-200 px-4 py-2">{getPositionTicker(position)}</td>
                                                            <td className="border border-gray-200 px-4 py-2 text-right">{getPositionQuantity(position).toLocaleString()}</td>
                                                            <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(getEntryPrice(position))}</td>
                                                            <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(position?.sellUnitPrice ?? position?.exitPrice ?? position?.closePrice ?? position?.sellPrice ?? 0)}</td>
                                                            <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(position?.sellUnitPrice ?? 0)}</td>
                                                            <td className={`border border-gray-200 px-4 py-2 text-right ${pnl >= 0 ? 'text-green-600' : 'text-red-600'}`}>
                                                                {formatPrice(pnl)}
                                                            </td>
                                                            <td className="border border-gray-200 px-4 py-2 text-right text-red-600">
                                                                -{formatPrice(buyFees + buyTaxes)}
                                                            </td>
                                                            <td className="border border-gray-200 px-4 py-2 text-right text-red-600">
                                                                -{formatPrice(sellFees + sellTaxes)}
                                                            </td>
                                                            <td className={`border border-gray-200 px-4 py-2 text-right ${pnlFeesAndTaxesIncl >= 0 ? 'text-green-600' : 'text-red-600'}`}>
                                                                {formatPrice(pnlFeesAndTaxesIncl)}
                                                            </td>
                                                            <td className="border border-gray-200 px-4 py-2 text-right">
                                                                {closedAt ? new Date(closedAt).toLocaleString() : '-'}
                                                            </td>