File-Based Signals for Bot Pause Control | Toss Auto-Trading Dev Journal #16

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

Toss Auto-Trading Dev Journal (Phase 5 | Post 16 of 16)

Previous: #15 Read-Only Dashboard | Next: coming soon

πŸ’‘ Phase 5 (Part 2) Summary

  • Goal: Build the control logic that lets a Telegram bot pause/resume the live trading loop (actual bot wiring comes next)
  • Key decision: Use the mere existence of a signal file β€” no database, no process merging β€” to track pause state
  • Verification: 14 new tests added, full suite of 102 passing

A quick note for readers unfamiliar with the setup: Toss Securities is a Korean brokerage, and this bot trades through its Open API against KRW-denominated stocks. After wiring up a read-only dashboard last time, the obvious next itch was: what if I could actually pause the bot from my phone?

A genuinely different kind of problem

The dashboard only had to read from a database. The Telegram bot’s /pause command is different β€” it needs to reach into an already-running loop in auto_trader.py and stop it. The bot process and the trading loop process had no way to talk to each other. I needed a bridge.

Why a file signal beat the alternatives

I weighed three options: merging the bot and the loop into one process, using a file’s mere existence as a signal, or building a command queue in the database. Merging processes meant too much rework; a DB queue felt like overkill at this scale. A file-based signal won β€” if the file exists, pause; if not, run normally.

One thing I nailed down early: /pause stops the entire watch loop (price checks and buy decisions), but never touches the triple safeguard values like DRY_RUN or the target price.

engine_control.py β€” keeping state as dumb as possible

class EngineControl:
    def __init__(self, signal_path="pause.signal"):
        self.signal_path = signal_path

    def pause(self):
        with open(self.signal_path, "w") as f:
            f.write("paused")

    def resume(self):
        if os.path.exists(self.signal_path):
            os.remove(self.signal_path)

    def is_paused(self):
        return os.path.exists(self.signal_path)

auto_trader.py β€” skip everything while paused

The loop just checks is_paused() each cycle. To avoid alert spam, notifications only fire on the transition, not every cycle.

if engine_control.is_paused():
    if not was_paused:
        notify("⏸ Watch loop paused (000660)")
        was_paused = True
    time.sleep(WATCH_INTERVAL)
    continue

if was_paused:
    notify("β–Ά Watch loop resumed")
    was_paused = False

price = get_current_price("000660")
# buy logic continues as before

bot_commands.py β€” pure functions, no API calls

Since the actual Telegram library isn’t wired in yet, I split out the text-assembly logic for /status and /holdings as pure functions, fully testable without any bot running.

def get_status_text(is_paused, trade_count, alert_count):
    mode = "Paused" if is_paused else "Running"
    return (
        f"Status: {mode}\n"
        f"Total trades: {trade_count}\n"
        f"Total alerts: {alert_count}"
    )

def get_holdings_text(holdings):
    # example holdings: [{"ticker": "005930", "qty": 3, "avg_price": 1}]
    lines = [f"{h['ticker']} x{h['qty']} (avg {h['avg_price']})" for h in holdings]
    return "\n".join(lines) if lines else "No holdings"

Verifying it actually stops

The test I cared most about was in test_auto_trader.py. It wasn’t enough for the code to run β€” I needed to confirm get_current_price() was never called while paused.

def test_price_check_skipped_while_paused(mocker):
    mock_price = mocker.patch("auto_trader.get_current_price")
    mock_is_paused = mocker.patch(
        "engine_control.EngineControl.is_paused",
        side_effect=[True, True, False]
    )
    run_watch_cycle(times=3)
    mock_price.assert_not_called()

5 tests covered pause/resume via signal file creation and deletion, 6 covered the /status and /holdings text assembly, and 3 covered loop integration β€” 14 new tests on top of the existing 88, all 102 passing. I also confirmed none of the safeguard values had shifted.

What’s left β€” wiring up the real bot

This session only finished the “what to stop, what to show” logic. What remains is connecting an actual library like python-telegram-bot so that /status calls bot_commands.get_status_text() and /pause calls engine_control.pause() directly. Once that’s done, Phase 5 (alerting/monitoring) wraps up entirely, and Phase 6 (operational automation) begins.

FAQ

Q1. Why a file signal instead of a database command queue?
A1. A DB queue felt like overkill at this scale; checking a file’s existence was simple and reliable enough for pause/resume state.

Q2. Does /pause affect the safeguard settings?
A2. No. DRY_RUN and the target price are never touched, regardless of the loop’s pause state.

Q3. Is the real Telegram command already working?
A3. Not yet β€” this session only built the control logic. Actual library wiring comes in the next session.

Q4. Won’t alerts spam every cycle while paused?
A4. No, alerts only fire once on the pause/resume transition, not on every cycle.

πŸ‘‰ Browse the full series: Toss Auto-Trading Dev Journal from #0

1 thought on “File-Based Signals for Bot Pause Control | Toss Auto-Trading Dev Journal #16”

  1. Pingback: 파일 μ‹œκ·Έλ„λ‘œ ν…”λ ˆκ·Έλž¨ 봇 μΌμ‹œμ •μ§€ κ΅¬ν˜„ν•˜κΈ° | ν† μŠ€ μžλ™λ§€λ§€ 개발기 #16 - TS ν€€νŠΈ: 주식 μžλ™λ§€λ§€ μ—°κ΅¬μ†Œ

Leave a Comment

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

Scroll to Top