Candle API Pagination for Historical Backtesting | Toss Auto-Trading Dev Journal #14

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

πŸ“š Toss Auto-Trading Dev Journal (Phase 4, entry 14 of 14 published)

← Previous: Phase 4 (Backtest engine implementation, #13) | Next: Phase 5 (not yet published)

πŸ’‘ Phase 4 at a glance

  • Goal: Actually fetch and store the historical price data the backtester needs, via Toss Securities’ candle API
  • Key decision: Build a defensive parser before the response schema was confirmed, then validate against a real token afterward
  • Verification: 7 new tests added (3 candle parsing, 2 pagination integration, 2 timestamp preservation), 78 total tests passing

The backtest engine itself β€” buy/sell simulation, performance reports β€” was already finished in the previous Phase 4 entry (#13). The one thing still missing was an answer to “where does the historical price data actually come from?” This post closes that last gap.

Toss Securities does have a candle endpoint

A quick note for readers unfamiliar with the platform: Toss Securities is a Korean retail brokerage, and its Open API lets individual developers automate trading on their own KRW-denominated account. Whether it exposed historical price data at all was the first thing to check. The official docs at developers.tossinvest.com confirmed a real endpoint: GET /api/v1/candles, with parameters symbol, interval (only 1m/1d supported), count (max 200), before (for pagination), and adjusted (adjusted-price flag).

Worth noting: rate limiting for this endpoint is tracked under its own group, MARKET_DATA_CHART, separate from order/balance endpoints β€” so hammering candle requests wouldn’t threaten the trading-critical calls.

Writing the parser before the schema was confirmed

The docs didn’t fully spell out the exact response wrapper. Every other endpoint in this project had followed a {"result": [...]} shape, so get_candles() was written in toss_common.py against that assumption, wrapped in the existing retry decorator.

Alongside it, toss_test.py got a diagnostic function, fetch_candles_raw(), for inspecting the raw response directly β€” the same pattern used earlier to confirm the holdings-endpoint schema. Assumptions get written down, but they don’t get trusted until they’re checked against a real response.

def get_candles(symbol: str, interval: str = "1d", count: int = 200, before: str | None = None):
    """Fetch candles from Toss Securities API (defensive parsing)."""
    params = {"symbol": symbol, "interval": interval, "count": count}
    if before:
        params["before"] = before
    resp = _request_with_retry("GET", "/api/v1/candles", params=params)
    data = resp.json()
    result = data.get("result", {})
    return result.get("candles", []), result.get("nextBefore")

Getting past the 200-item cap

One call alone couldn’t pull enough history for a meaningful backtest. fetch_and_store_historical_prices() was added to backtest.py to loop on the before parameter, collect candles across pages, sort them chronologically, and write them into price_snapshots.

One detail mattered here: when backfilling old data, the stored timestamp can’t just be “now” β€” it has to be when the candle actually occurred, or later simulation math breaks. insert_price_snapshot() in db.py got an optional captured_at parameter so the real candle timestamp could be preserved.

def insert_price_snapshot(conn, symbol: str, price: float, captured_at: str | None = None):
    ts = captured_at or datetime.utcnow().isoformat()
    conn.execute(
        "INSERT INTO price_snapshots (symbol, price, captured_at) VALUES (?, ?, ?)",
        (symbol, price, ts),
    )
    conn.commit()

Mock first, real token second

With the schema unconfirmed, the whole pipeline β€” two-page pagination, chronological sort, DB write with preserved timestamps, and hand-off to the existing run_backtest() β€” was validated against mocks first. Only after that passed did the real account token get used to run toss_test.py and inspect the actual response.

The defensive assumption held exactly: {"result": {"candles": [...], "nextBefore": ...}} matched the live response with zero changes needed. It also confirmed candles come back newest-first, which meant the sorting logic written in advance wasn’t just precaution β€” it was actually necessary. (Sample tickers were masked to placeholder codes such as 005930 and 000660 for testing.)

Phase 0 through 4, done

7 new tests (3 for candle parsing, 2 for pagination integration, 2 for timestamp preservation) brought the suite to 78 passing tests. That closes out all three pieces of Phase 4 β€” historical data collection/storage, simulation, and performance reporting β€” and wraps up the roadmap arc that started back at Phase 0.

Phase 5 is next: alerting and monitoring, likely a Telegram bot command interface or a lightweight web dashboard. Now that real historical data is reachable, comparing strategies like ThresholdBuyStrategy and MovingAverageStrategy against actual price history is also on the list of interesting next experiments.

πŸ’» Check the full code on GitHub

FAQ

Q1. Does the Toss Securities Open API expose historical candle data?

A. Yes β€” GET /api/v1/candles supports daily (1d) and 1-minute (1m) intervals, capped at 200 items per call, so pagination via the before parameter is required for longer histories.

Q2. Does candle fetching share rate limits with trading calls?

A. No, it’s tracked under a separate MARKET_DATA_CHART group, isolated from order/balance rate limits.

Q3. How do you handle an unconfirmed response schema?

A. Write the parser against the most likely shape based on existing endpoints, add a raw-response diagnostic function, then confirm against a real token before trusting it.

Q4. How is the backfill timestamp handled?

A. insert_price_snapshot() takes an optional captured_at parameter so the actual candle time is stored instead of the insert time.

1 thought on “Candle API Pagination for Historical Backtesting | Toss Auto-Trading Dev Journal #14”

  1. Pingback: μΊ”λ“€ API νŽ˜μ΄μ§€λ„€μ΄μ…˜μœΌλ‘œ κ³Όκ±° μ‹œμ„Έ ν™•λ³΄ν•˜κΈ° | ν† μŠ€ μžλ™λ§€λ§€ 개발기 #14 - TS ν€€νŠΈ: 주식 μžλ™λ§€λ§€ μ—°κ΅¬μ†Œ

Leave a Comment

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

Scroll to Top