Skip to content

Repository files navigation

Swing Bot

A NautilusTrader 1.231.0 hourly SMA continuation strategy for a fixed US-stock universe (see config/universe.toml), with Interactive Brokers contract discovery, historical data ingestion, backtesting, paper trading, and explicitly gated live trading.

This software can place real orders. Validate data and results, run in an IB paper account first, and read docs/runbook.md before enabling live mode.

The evidence-grounded strategy audit evaluates the current backtests, high-CAGR claims, established strategy families, and a capital-preservation redesign specification. It does not change the current implementation.

⚠️ Legal & Financial Disclaimer

This software is for educational and research purposes only. Do not use this code to make actual financial decisions with real money.

Not Financial Advice

The code, documentation, and algorithms provided in this repository do not constitute financial advice, investment advice, trading advice, or any other sort of advice. You should not treat any of the repository's content as such.

Risk of Loss

Trading equities involves a high degree of risk, particularly when executing active strategies like day trading, swing trading, or momentum trading. Market volatility can lead to substantial financial losses. You could lose some or all of your initial investment. Always conduct your own due diligence and consult with a licensed financial advisor before making any investment decisions.

Software "As Is"

This trading bot is provided "as is" and "as available" without warranty of any kind, either express or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The authors and contributors make no representations about the accuracy, reliability, or completeness of the software.

Technical Limitations & Bugs

Algorithmic trading depends on complex systems, including third-party broker APIs, external charting webhooks, and live market data feeds. System failures, network outages, rate limits, or bugs in this code can result in unintended trades, orphaned orders, and significant financial loss.

Past Performance

Any backtesting results or simulated performance metrics included in this repository are hypothetical. Past performance of any trading system, indicator, or methodology is not indicative of future results. Live market conditions, including slippage and liquidity constraints, will often yield different outcomes than historical tests.

Assumption of Liability

Under no circumstances will the authors, contributors, or copyright holders be held liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. By running this bot, you assume all responsibility for any trading losses you may incur.

Setup

Requirements: Python 3.13, uv, Docker Compose 2.24 or newer, an IB account with the required market-data permissions, and API access enabled.

uv sync --extra dev
cp .env.example .env
mkdir -p secrets
printf '%s' 'your-ib-password' > secrets/tws_password.txt
chmod 600 secrets/tws_password.txt

Set TWS_USERID and the paper TWS_ACCOUNT in .env. Start Gateway and the paper bot:

make paper
make status
make logs

The dashboard is available at http://localhost. It shows broker net liquidation, the persisted equity curve, closed trades, and open positions. Pause blocks new entries without touching open positions. Flatten first pauses entries, cancels the strategy's working orders, and submits reduce-only GTC limit exits with a 5% marketable price collar. A command is acknowledged only after the bot process consumes it.

The dashboard binds only to 127.0.0.1:80. Set DASHBOARD_PASSWORD in .env to enable HTTP Basic authentication; DASHBOARD_USERNAME defaults to admin. Do not expose the dashboard publicly without an authenticated TLS reverse proxy.

IB Gateway may require a mobile 2FA approval during login.

Market Data Subscriptions

The bot requests real-time data and does not fall back to delayed quotes. The IBKR user logged into Gateway therefore needs Level 1 (top-of-book) US equity market data for every primary listing exchange in data/contracts.json. The robust choice is consolidated US equity coverage for all networks used by the universe:

  • Network A (CTA): NYSE-listed securities.
  • Network B (CTA): NYSE American, NYSE Arca, and other regional listings.
  • Network C (UTP): Nasdaq-listed securities.

The current universe includes Nasdaq stocks and may include exchange-listed ETFs. Run discover-contracts first and use each contract's primary_exchange to determine whether Network A or B is also required. IBKR package names, prices, professional status rules, and waivers vary by account and region; confirm the resulting A/B/C coverage under Client Portal > Settings > Market Data Subscriptions. A common non-professional bundle may combine these feeds, but the exchange coverage is what matters to the bot.

IBKR currently includes free streaming Cboe One and IEX data for US stocks and ETFs, but it is non-consolidated. Do not rely on it for this strategy: incomplete trades and quotes can alter hourly signals, minute execution bars, extended-hours prices, and backtest results. Historical API requests generally require the corresponding market-data entitlement at download time. Once the catalog has been downloaded, backtests run offline and require no active subscription.

Paper accounts do not provide a separate full data entitlement. Configure the paper user to share the live account's subscriptions and check IBKR's concurrent-session restrictions. Errors such as IB 354 or 10167, delayed timestamps, or missing bars indicate that the logged-in user lacks usable real-time coverage. This stock strategy does not require Level 2/order-book, options, futures, news, or fundamental-data subscriptions.

IB error 162 with "Trading TWS session is connected from a different IP address" means IBKR has assigned the username's trading or shared market-data session to another public IP. It does not prove that another visible TWS or Gateway process is running: a live/paper entitlement session, IBKR Mobile, Client Portal, a hosted integration, or a stale server-side session can be involved. A restart may complete password authentication without requesting 2FA, so the absence of a prompt is not diagnostic. Error 366 immediately afterward is only the failed request's cancellation cleanup. Follow the session-recovery procedure in docs/runbook.md; while diagnosing, add --retries 0 to download-data to avoid repeating a request IBKR will continue to reject. Changing the API client ID does not affect this account-level allocation.

See IBKR's current market data pricing and API market data requirements before subscribing, because offerings can change.

Data And Backtest

Resolve the exact IB contracts before downloading data. Never guess exchange or conId values.

uv run swing-bot discover-contracts --output data/contracts.json
uv run swing-bot download-data --contracts data/contracts.json --catalog data/catalog \
  --start 2025-01-01T00:00:00-05:00 --end 2026-08-01T00:00:00-05:00 \
  --hourly-chunk-days 30 --minute-chunk-days 30
uv run swing-bot validate-data --catalog data/catalog \
  --start 2025-01-01T00:00:00-05:00 --end 2026-08-01T00:00:00-05:00
uv run swing-bot backtest --contracts data/contracts.json --catalog data/catalog \
  --start 2026-01-01T00:00:00-05:00 --end 2026-08-01T00:00:00-05:00 \
  --output reports/2026

The equivalent configurable Make target is:

make backtest
make backtest BACKTEST_START=2026-01-01T00:00:00-05:00 \
  BACKTEST_END=2026-08-20T20:00:00-04:00 BACKTEST_OUTPUT=reports/2026-final

The backtest writes JSON, CSV, and report.html artifacts. It sends only minute bars to the simulated exchange and aggregates them into completed one-hour bars for signals. Data includes the US pre-market, regular session, and post-market returned by IB. It automatically loads up to 60 calendar days of earlier minute bars without permitting warmup-period entries. Keep at least 120 earlier hourly equivalents in the catalog so both moving averages can initialize.

Downloads are resumable. Each completed hourly or minute request is immediately stored in data/catalog.download-cache; rerunning the same command skips those requests, including completed intervals with no data. The final data/catalog is replaced only after all requested units pass validation, so an interrupted download does not damage the previous catalog. Remove data/catalog.download-cache to force a complete refresh. Hourly and minute bars use separate chunk controls. --chunk-days remains an alias for --minute-chunk-days. Catalogs created by the earlier daily/RTH strategy must be downloaded again because they do not contain hourly extended-hours bars.

IB may return code 162 with HMDS query returned no data for intervals before a symbol listed or after it stopped trading. The downloader treats that exact response as an empty interval and continues immediately. Other code 162 responses, including different-IP session conflicts, retain their normal failure behavior. A symbol still needs at least 220 hourly bars in the completed catalog to pass warmup validation.

Paper And Live

Paper is the default and requires an account ID beginning with DU:

make paper
make logs

Live operation requires all of the following: the live Compose override, a non-DU account, port 4001, TRADING_MODE=live, and the exact acknowledgement token.

TWS_ACCOUNT=U0000000 \
LIVE_TRADING_ACK=I_UNDERSTAND_LIVE_ORDERS_ARE_REAL \
make live

Use make stop for a graceful stop. It cancels unfilled entry orders, preserves broker-held trailing stops for open positions, and does not liquidate positions. See the runbook for shutdown and incident procedures.

To receive Telegram notifications for bot lifecycle events, entries, and closed trades, set both TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID in .env, then recreate the bot container. Notification delivery is asynchronous and a Telegram outage does not block trading callbacks.

To find the chat ID:

  1. Create a bot with Telegram's @BotFather and copy its API token.
  2. For a private chat, open the new bot and send it a message. For a group, add the bot to the group and send a message or command mentioning it.
  3. Export the token locally and request the bot's recent updates:
export TELEGRAM_BOT_TOKEN='token-from-botfather'
curl --silent "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates"

Use the result[].message.chat.id value as TELEGRAM_CHAT_ID. Group chat IDs are normally negative. If result is empty, send the bot another message and retry. Keep the bot token secret; anyone with it can control the bot.

Configuration

Run local validation with uv run pytest and uv run ruff check ..

License

This project is licensed under the MIT License.

About

A NautilusTrader `1.231.0` hourly SMA continuation strategy for a fixed US-stock universe with Interactive Brokers contract discovery, historical data ingestion, backtesting, paper trading, and explicitly gated live trading.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages