galaxis-po/docs/plans/2026-02-16-stock-name-display-plan.md
ayuriel b92f8f298b docs: add stock name display implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 12:47:13 +09:00

17 KiB

Stock Name Display Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Replace stock code displays with stock name as primary text across all portfolio-related views, with stock codes available via tooltip.

Architecture: Backend schemas and API endpoints that currently lack stock names (SnapshotHoldingResponse, TransactionResponse, backtest TransactionItem) get name fields added, with name resolution via the existing RebalanceService.get_stock_names(). Frontend swaps display pattern from ticker (primary) + name (subtitle) to name (primary) with title={ticker} tooltip.

Tech Stack: FastAPI + SQLAlchemy (backend), Next.js 15 + React 19 + TypeScript (frontend)


Task 1: Add name field to SnapshotHoldingResponse schema

Files:

  • Modify: backend/app/schemas/portfolio.py:114-122

Step 1: Add name field to schema

In backend/app/schemas/portfolio.py, add name field to SnapshotHoldingResponse:

class SnapshotHoldingResponse(BaseModel):
    ticker: str
    name: str | None = None
    quantity: int
    price: FloatDecimal
    value: FloatDecimal
    current_ratio: FloatDecimal

    class Config:
        from_attributes = True

Step 2: Commit

git add backend/app/schemas/portfolio.py
git commit -m "feat: add name field to SnapshotHoldingResponse schema"

Task 2: Add name field to TransactionResponse schema

Files:

  • Modify: backend/app/schemas/portfolio.py:72-76

Step 1: Add name field to schema

In backend/app/schemas/portfolio.py, add name field to TransactionResponse:

class TransactionResponse(TransactionBase):
    id: int
    name: str | None = None

    class Config:
        from_attributes = True

Step 2: Commit

git add backend/app/schemas/portfolio.py
git commit -m "feat: add name field to TransactionResponse schema"

Task 3: Add name field to backtest TransactionItem schema

Files:

  • Modify: backend/app/schemas/backtest.py:119-131

Step 1: Add name field to schema

In backend/app/schemas/backtest.py, add name field to TransactionItem:

class TransactionItem(BaseModel):
    """Single transaction."""
    id: int
    date: date
    ticker: str
    name: str | None = None
    action: str
    shares: int
    price: FloatDecimal
    commission: FloatDecimal

    class Config:
        from_attributes = True

Step 2: Commit

git add backend/app/schemas/backtest.py
git commit -m "feat: add name field to backtest TransactionItem schema"

Task 4: Populate snapshot holdings with stock names in snapshot API

Files:

  • Modify: backend/app/api/snapshot.py:53-128 (create_snapshot endpoint)
  • Modify: backend/app/api/snapshot.py:131-168 (get_snapshot endpoint)

Step 1: Update imports

Add RebalanceService import at top of backend/app/api/snapshot.py:

from app.services.rebalance import RebalanceService

Step 2: Update create_snapshot endpoint to include names

In the create_snapshot function, after getting prices and before creating snapshot, resolve names and include them in the response. Replace the return statement (lines 113-128):

    # Get stock names
    name_service = RebalanceService(db)
    names = name_service.get_stock_names(tickers)

    return SnapshotResponse(
        id=snapshot.id,
        portfolio_id=snapshot.portfolio_id,
        total_value=snapshot.total_value,
        snapshot_date=snapshot.snapshot_date,
        holdings=[
            SnapshotHoldingResponse(
                ticker=h.ticker,
                name=names.get(h.ticker),
                quantity=h.quantity,
                price=h.price,
                value=h.value,
                current_ratio=h.current_ratio,
            )
            for h in snapshot.holdings
        ],
    )

Step 3: Update get_snapshot endpoint to include names

In the get_snapshot function, resolve names before returning. Replace lines 153-168:

    # Get stock names
    tickers = [h.ticker for h in snapshot.holdings]
    name_service = RebalanceService(db)
    names = name_service.get_stock_names(tickers)

    return SnapshotResponse(
        id=snapshot.id,
        portfolio_id=snapshot.portfolio_id,
        total_value=snapshot.total_value,
        snapshot_date=snapshot.snapshot_date,
        holdings=[
            SnapshotHoldingResponse(
                ticker=h.ticker,
                name=names.get(h.ticker),
                quantity=h.quantity,
                price=h.price,
                value=h.value,
                current_ratio=h.current_ratio,
            )
            for h in snapshot.holdings
        ],
    )

Step 4: Commit

git add backend/app/api/snapshot.py
git commit -m "feat: include stock names in snapshot API responses"

Task 5: Populate transactions with stock names in portfolio API

Files:

  • Modify: backend/app/api/portfolio.py:218-234

Step 1: Update imports

At top of backend/app/api/portfolio.py, RebalanceService is already imported (line 21). No change needed.

Step 2: Update get_transactions endpoint

Replace the get_transactions function body to resolve names:

@router.get("/{portfolio_id}/transactions", response_model=List[TransactionResponse])
async def get_transactions(
    portfolio_id: int,
    current_user: CurrentUser,
    db: Session = Depends(get_db),
    limit: int = 50,
):
    """Get transaction history for a portfolio."""
    _get_portfolio(db, portfolio_id, current_user.id)
    transactions = (
        db.query(Transaction)
        .filter(Transaction.portfolio_id == portfolio_id)
        .order_by(Transaction.executed_at.desc())
        .limit(limit)
        .all()
    )

    # Resolve stock names
    tickers = list({tx.ticker for tx in transactions})
    service = RebalanceService(db)
    names = service.get_stock_names(tickers)

    return [
        TransactionResponse(
            id=tx.id,
            ticker=tx.ticker,
            name=names.get(tx.ticker),
            tx_type=tx.tx_type.value,
            quantity=tx.quantity,
            price=tx.price,
            executed_at=tx.executed_at,
            memo=tx.memo,
        )
        for tx in transactions
    ]

Step 3: Commit

git add backend/app/api/portfolio.py
git commit -m "feat: include stock names in transaction API responses"

Task 6: Populate backtest transactions with stock names

Files:

  • Modify: backend/app/api/backtest.py:209-242

Step 1: Add import

Add RebalanceService import at top of backend/app/api/backtest.py:

from app.services.rebalance import RebalanceService

Step 2: Update get_transactions endpoint

Replace the return logic in the backtest get_transactions endpoint:

    transactions = (
        db.query(BacktestTransaction)
        .filter(BacktestTransaction.backtest_id == backtest_id)
        .order_by(BacktestTransaction.date, BacktestTransaction.id)
        .all()
    )

    # Resolve stock names
    tickers = list({t.ticker for t in transactions})
    name_service = RebalanceService(db)
    names = name_service.get_stock_names(tickers)

    return [
        TransactionItem(
            id=t.id,
            date=t.date,
            ticker=t.ticker,
            name=names.get(t.ticker),
            action=t.action,
            shares=t.shares,
            price=t.price,
            commission=t.commission,
        )
        for t in transactions
    ]

Step 3: Commit

git add backend/app/api/backtest.py
git commit -m "feat: include stock names in backtest transaction responses"

Task 7: Update portfolio-card.tsx - show name instead of ticker

Files:

  • Modify: frontend/src/components/portfolio/portfolio-card.tsx

Step 1: Add name to Holding interface

interface Holding {
  ticker: string;
  name: string | null;
  current_ratio: number | null;
}

Step 2: Update pieData mapping to use name

Change line 64 from name: h.ticker to:

name: h.name || h.ticker,

Step 3: Add title attribute to holdings preview badges

Change the badge span (line 144-149) to include a title:

<span
  key={index}
  className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground"
  title={pieData[index]?.name !== holdings[index]?.name ? holdings.find(h => (h.name || h.ticker) === item.name)?.ticker : undefined}
>
  {item.name}
</span>

Actually, simpler approach - since pieData is derived from holdings, we can track the original ticker. Let's use a simpler approach: map pieData to include the original ticker, then use title={item.ticker}:

Change the pieData mapping (lines 60-67):

const pieData = holdings
  .filter((h) => h.current_ratio !== null && h.current_ratio > 0)
  .slice(0, 6)
  .map((h, index) => ({
    name: h.name || h.ticker,
    ticker: h.ticker,
    value: h.current_ratio ?? 0,
    color: CHART_COLORS[index % CHART_COLORS.length],
  }));

Then update the badge (lines 143-149):

{pieData.slice(0, 4).map((item, index) => (
  <span
    key={index}
    className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground"
    title={item.ticker}
  >
    {item.name}
  </span>
))}

Step 4: Also update HoldingWithValue in portfolio list page

In frontend/src/app/portfolio/page.tsx, add name to the HoldingWithValue interface (line 12-15):

interface HoldingWithValue {
  ticker: string;
  name: string | null;
  current_ratio: number | null;
}

Step 5: Commit

git add frontend/src/components/portfolio/portfolio-card.tsx frontend/src/app/portfolio/page.tsx
git commit -m "feat: show stock names in portfolio cards"

Task 8: Update portfolio detail page - fix holdings ticker subtitle removal + transactions name

Files:

  • Modify: frontend/src/app/portfolio/[id]/page.tsx

Step 1: Add name to Transaction interface

interface Transaction {
  id: number;
  ticker: string;
  name: string | null;
  tx_type: string;
  quantity: number;
  price: number;
  executed_at: string;
}

Step 2: Update holdings table - remove ticker subtitle, add title tooltip

Change lines 343-348 from:

<td className="px-4 py-3">
  <div className="font-medium text-sm">{holding.name || holding.ticker}</div>
  {holding.name && (
    <div className="text-xs text-muted-foreground">{holding.ticker}</div>
  )}
</td>

To:

<td className="px-4 py-3">
  <span className="font-medium text-sm" title={holding.ticker}>{holding.name || holding.ticker}</span>
</td>

Step 3: Update transactions table - show name instead of ticker

Change line 457 from:

<td className="px-4 py-3 text-sm font-medium">{tx.ticker}</td>

To:

<td className="px-4 py-3 text-sm font-medium" title={tx.ticker}>{tx.name || tx.ticker}</td>

Step 4: Update Target vs Actual section - add title tooltip

Change line 522 from:

<span className="font-medium">{holding?.name || target.ticker}</span>

To:

<span className="font-medium" title={target.ticker}>{holding?.name || target.ticker}</span>

Step 5: Commit

git add frontend/src/app/portfolio/[id]/page.tsx
git commit -m "feat: show stock names as primary display in portfolio detail"

Task 9: Update rebalance page - name as primary display

Files:

  • Modify: frontend/src/app/portfolio/[id]/rebalance/page.tsx

Step 1: Fetch stock names for price input labels

The rebalance page needs names for the price input section. The targets and holdings don't have names, but the rebalance result does. We need to store a name map. Add state and fetch names when targets/holdings load.

Add nameMap state after existing state declarations (after line 61):

const [nameMap, setNameMap] = useState<Record<string, string>>({});

In the init function, after setting targets and holdings, fetch portfolio detail to get names:

// Fetch stock names from portfolio detail
try {
  const detail = await api.get<{ holdings: { ticker: string; name: string | null }[] }>(`/api/portfolios/${portfolioId}/detail`);
  const names: Record<string, string> = {};
  for (const h of detail.holdings) {
    if (h.name) names[h.ticker] = h.name;
  }
  setNameMap(names);
} catch {
  // Names are optional, continue without
}

Step 2: Update price input labels to use name

Change line 183 from:

{ticker} {target ? `(목표 ${target.target_ratio}%)` : ''} - 보유 {getHoldingQty(ticker)}

To:

{nameMap[ticker] || ticker} {target ? `(목표 ${target.target_ratio}%)` : ''} - 보유 {getHoldingQty(ticker)}

Step 3: Update results table - name as primary

Change lines 299-304 from:

<td className="px-3 py-3">
  <div className="font-medium">{item.ticker}</div>
  {item.name && (
    <div className="text-xs text-muted-foreground">{item.name}</div>
  )}
</td>

To:

<td className="px-3 py-3">
  <span className="font-medium" title={item.ticker}>{item.name || item.ticker}</span>
</td>

Step 4: Also update nameMap from rebalance results

After the calculate function sets the result, update nameMap with any names from the result:

In the calculate function, after setResult(data) (line 117), add:

// Update name map from results
const newNames = { ...nameMap };
for (const item of data.items) {
  if (item.name) newNames[item.ticker] = item.name;
}
setNameMap(newNames);

Step 5: Commit

git add frontend/src/app/portfolio/[id]/rebalance/page.tsx
git commit -m "feat: show stock names as primary display in rebalance page"

Task 10: Update portfolio history page - show names in snapshot

Files:

  • Modify: frontend/src/app/portfolio/[id]/history/page.tsx

Step 1: Add name to SnapshotDetail holdings interface

Change the holdings type in SnapshotDetail interface (lines 23-29):

holdings: {
  ticker: string;
  name: string | null;
  quantity: number;
  price: string;
  value: string;
  current_ratio: string;
}[];

Step 2: Update snapshot detail modal table

Change line 428 from:

<td className="px-4 py-2 text-sm text-foreground">
  {holding.ticker}
</td>

To:

<td className="px-4 py-2 text-sm text-foreground" title={holding.ticker}>
  {holding.name || holding.ticker}
</td>

Step 3: Commit

git add frontend/src/app/portfolio/[id]/history/page.tsx
git commit -m "feat: show stock names in portfolio history snapshots"

Task 11: Update strategy pages - name as primary (3 files)

Files:

  • Modify: frontend/src/app/strategy/multi-factor/page.tsx:209-211
  • Modify: frontend/src/app/strategy/quality/page.tsx:174-176
  • Modify: frontend/src/app/strategy/value-momentum/page.tsx:190-192

Step 1: Update multi-factor page

Change lines 209-211 from:

<td className="px-4 py-3">
  <div className="font-medium">{stock.ticker}</div>
  <div className="text-xs text-muted-foreground">{stock.name}</div>
</td>

To:

<td className="px-4 py-3">
  <span className="font-medium" title={stock.ticker}>{stock.name || stock.ticker}</span>
</td>

Step 2: Update quality page

Same change at lines 174-176.

Step 3: Update value-momentum page

Same change at lines 190-192.

Step 4: Commit

git add frontend/src/app/strategy/multi-factor/page.tsx frontend/src/app/strategy/quality/page.tsx frontend/src/app/strategy/value-momentum/page.tsx
git commit -m "feat: show stock names as primary display in strategy pages"

Task 12: Update backtest detail page - name as primary

Files:

  • Modify: frontend/src/app/backtest/[id]/page.tsx

Step 1: Add name to TransactionItem interface

interface TransactionItem {
  id: number;
  date: string;
  ticker: string;
  name: string | null;
  action: string;
  shares: number;
  price: number;
  commission: number;
}

Step 2: Update holdings tab display

Change lines 392-394 from:

<td className="px-4 py-3">
  <div className="font-medium">{h.ticker}</div>
  <div className="text-xs text-muted-foreground">{h.name}</div>
</td>

To:

<td className="px-4 py-3">
  <span className="font-medium" title={h.ticker}>{h.name || h.ticker}</span>
</td>

Step 3: Update transactions tab display

Change line 425 from:

<td className="px-4 py-3 text-sm font-medium">{t.ticker}</td>

To:

<td className="px-4 py-3 text-sm font-medium" title={t.ticker}>{t.name || t.ticker}</td>

Step 4: Commit

git add frontend/src/app/backtest/[id]/page.tsx
git commit -m "feat: show stock names as primary display in backtest detail"

Task 13: Verify frontend build

Step 1: Run frontend build to check for TypeScript errors

cd frontend && npm run build

Expected: Build succeeds with no type errors.

Step 2: If build fails, fix any TypeScript errors and re-run

Step 3: Commit any fixes

git add -A && git commit -m "fix: resolve build errors from stock name display changes"