Hey everyone, it's your friendly neighborhood senior dev here. I'm 38, working as a full-time engineer during the week, and tinkering with AI-powered algorithmic trading bots on the weekends.
Today, I want to share a story about a subtle but potentially catastrophic bug I found in my bot. Seriously, thank goodness I caught this before deploying with real capital. The TL;DR: My Discord notifications for order confirmations weren't firing, and the culprit was a forgotten .env load across multiple Python scripts.
I think this is a pretty common pitfall when you're working on personal projects with several interconnected Python scripts.
What Happened: A "Silent Failure" Uncovered by DRY_RUN
Over the weekend, I was running my usual DRY_RUN tests for my forex bot. My bot's architecture splits responsibilities: planner.py handles strategy logic, and executor.py executes actual trades on the exchange.
Looking at the console logs, executor.py seemed to be working perfectly. I saw logs like [DRY_RUN] Order placed: .... But the Discord notifications, which are supposed to arrive after an order, simply weren't showing up.
Initially, I thought it might be a Discord issue or just a delay. But after 30 minutes, still nothing. This felt wrong.
The thought of this happening with real money sent shivers down my spine:
- "I thought I placed the order, but it never went through."
- "I was sure I closed that position, but it's still open."
Bugs in notification systems are notorious for creating these kinds of silent failures, and they're genuinely scary.
The Investigation: Aha! Found You...
My first step was to isolate the problem. I directly invoked notify.py, the script responsible for sending notifications. It worked perfectly, sending a test message to Discord. This strongly suggested the issue was upstream, likely within executor.py, which calls notify.py.
I took a closer look at executor.py's logs. And there it was: the webhook URL, which should have been passed to the notification function, was None. Bingo.
But why None? I store my webhook URL in a .env file, and other scripts, like planner.py, were successfully reading it. So I compared the code for planner.py and executor.py.
And I immediately spotted the difference. At the beginning of planner.py, there was a clear load_dotenv() call:
# ...
from dotenv import load_dotenv
load_dotenv() # Load environment variables
# ... planner logic ...
However, executor.py, the script in question, was missing this load_dotenv() call.
This meant that when I ran the entire flow starting from planner.py, planner.py would load the .env variables, making them available to executor.py. But if I ran executor.py directly for testing, or if it was called from a different entry point, no one was loading the .env file. Consequently, the webhook URL was never set, and notifications failed silently.
My code had an implicit dependency, and that's a dangerous path. If this were a team project, it would definitely be caught in code review. But when you're working solo, these kinds of things can easily slip through.
The Fix: Resolving Dependencies Where They're Used
Once the cause was clear, the fix was straightforward. I added load_dotenv() to executor.py before calling the notification logic.
Before (simplified):
// Before: Calling notification function without loading .env
from .notify import hub
hub.notify_investment('Execution result...') # Fails because webhook is not set
In this setup, when the hub module initializes, os.environ.get('DISCORD_WEBHOOK_INVESTMENT') returns None, leading to a silent notification failure.
After (simplified):
// After: Loading .env before notification
import os
from dotenv import load_dotenv, find_dotenv
from .notify import hub
# Find .env file and load environment variables.
# This ensures the webhook URL is set in os.environ.
if 'DISCORD_WEBHOOK_INVESTMENT' not in os.environ: # Only load if not already set
load_dotenv(find_dotenv())
hub.notify_investment('Execution result...') # Notification sent successfully
I used find_dotenv() to ensure that the .env file is located correctly, regardless of the current working directory during execution. This guarantees that executor.py can always find and load the webhook URL, no matter how it's invoked.
It's a fundamental principle, but it's always good to be reminded: code that depends on certain features (like environment variables) should take responsibility for resolving those dependencies (loading them) where they are used.
Key Takeaways and Lessons Learned
This incident taught me three important lessons:
DRY_RUN Tests Are God-Tier
The value of discovering "normal-looking anomalies" without any financial cost is immense. Things that look like they're working are the most dangerous. Never skip DRY_RUN tests before production deployment.Eliminate "Implicit Assumptions" Between Scripts
When you split code into multiple files, it's easy to develop implicit assumptions like, "Oh, that other file will initialize it." For project-wide settings like.env, it's better design to explicitly load them at each entry point or at the beginning of the modules that rely on them.Consider Assertions for Critical Operations
While I caught this with logs, for even more robustness, it might be worth adding an assertion likeassert os.environ.get('DISCORD_WEBHOOK_INVESTMENT') is not Noneright before critical operations like placing an order or sending a notification. This can catch misconfigurations immediately.
Operating a personal bot is a continuous battle against these subtle bugs. But each one I squash makes the system stronger. It was another weekend where my bot got a little smarter.
Oji / AI Algo Trading Engineer
X: @oji_ai_dev
Top comments (0)