Toss Securities Holdings Dashboard | Dev Journal #25

🌐 ν•œκ΅­μ–΄λ‘œ 보기

πŸ’‘ What Phase 7-7 covers

  • Goal: Wire up a live Toss Securities holdings dashboard with a one-click take-profit/stop-loss setup form.
  • Key decision: A single get_display_name() helper normalizes every table to “Samsung Electronics (005930)” instead of a bare ticker.
  • Verification: 130 pytest cases passing, including the new python-multipart form routes, plus a full DRY_RUN buyβ†’take-profit/stop-loss sell cycle test.

Without a proper Toss Securities holdings dashboard, checking what I actually held meant opening the Toss app every time β€” a separate step from the bot itself. Mixing up ticker codes for Samsung Electronics vs. SK Hynix got old fast too. This post closes that gap with live holdings and a one-click take-profit/stop-loss form.

1. Toss Securities API: Live Holdings & One-Click Setup (dashboard.py)

toss_portfolio.fetch_holdings() pulls quantity, average cost, current price, unrealized P&L, and return rate for every position, rendered at the top of the dashboard. The next problem was workflow: setting a take-profit target meant switching to a separate strategy page every time. So I put an inline form directly on each holdings row.

<form action="/api/strategies" method="post">
  <input type="hidden" name="symbol" value="{symbol}">
  <input type="number" name="target_price" placeholder="Target buy price">
  <input type="number" name="take_profit_pct" placeholder="Take-profit %" step="0.1">
  <input type="number" name="stop_loss_pct" placeholder="Stop-loss %" step="0.1">
  <button type="submit">Save</button>
</form>

FastAPI needs python-multipart to parse form submissions. I skipped that dependency at first and hit RuntimeError: Form data requires "python-multipart" to be installed. straight out of the box. A one-line pip install python-multipart fixed it, but I made sure to pin it in requirements.txt right away so it wouldn’t break again on a fresh deploy. If the form-handling flow is unclear, FastAPI’s official Form data guide covers it well.

Two routes handle it: POST /api/strategies for saving and POST /api/strategies/delete for removing a config. The save handler does an upsert into strategy_configs β€” update if a config already exists for that symbol, insert otherwise.

2. Stock Portfolio Display: Name Caching (db.py)

Every table used to show a bare 6-digit ticker. I added a stock_names cache table in SQLite, populated via save_stock_name(code, name) whenever the API returns a name.

def get_display_name(code: str) -> str:
    """005930 -> 'Samsung Electronics (005930)'. Falls back to the code if uncached."""
    name = db.get_stock_name(code)
    return f"{name} ({code})" if name else code

Applying that one helper across holdings, active strategy configs, recent trades, and recent notifications made the stock portfolio visualization noticeably easier to scan β€” no more mentally mapping ticker codes to companies.

3. Config Sync & Take-Profit/Stop-Loss Automation (auto_trader.py)

Changing a target price on the web UI is useless if the bot needs a restart to pick it up. So the monitoring loop now re-reads the latest config from the DB every cycle (60 seconds by default) β€” edits made through the form apply on the next tick.

With DRY_RUN = True, I set a virtual position β€” Samsung Electronics (005930) β€” with a target buy price of β‚©70,000. When the simulated price dropped to that level, the bot triggered a virtual buy, then tracked the return: +5% triggered a virtual take-profit sell, -3% triggered a virtual stop-loss sell. Either outcome logs a SELL row to the trades table and fires a notification.

Virtual account example: 10 shares of Samsung Electronics (005930) at β‚©70,000 avg cost β†’ take-profit sell triggers at β‚©73,500 (simulated data only, no real account figures shown).

4. Dual-Channel Notifications & Security Diagnostics

WARNING-level events and above now go out over both Telegram and SMTP email, so one channel failing doesn’t mean silence. security_check.py non-destructively verifies that .env isn’t tracked in Git, that all five safety-brake conditions are active, and that OAuth2 tokens and account connectivity are healthy β€” without touching balances or live order state.

5. Verification: 130 pytest cases

The new form routes, caching helper, and virtual buy-sell cycle are all covered. Once the python-multipart dependency was fixed, the form-related tests went green too.

$ pytest -v
...
130 passed in 8.42s

FAQ

Q1. Why is python-multipart required?

FastAPI needs it to parse HTML <form> submissions. Without it you’ll get RuntimeError: Form data requires "python-multipart" to be installed.

Q2. Does DRY_RUN mode place real orders?

No. With DRY_RUN=True, no live orders are sent β€” only logs and DB records for a simulated trade cycle.

Q3. How often does the stock-name cache refresh?

It updates on every API fetch via save_stock_name(). There’s no separate batch refresh job.

Q4. Is the source code for this series public?

Not currently β€” it’s a private learning project, and I share the core logic here as I go.

πŸ‘‰ Next part isn’t published yet. See the full series index above.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top