Khurram

Building a Paper-Trading Bot, Part 3: A Live Dashboard on Apache

This is Part 3 of a 3-part series. Part 1 tells the story of building this bot and what went wrong along the way. Part 2 is the step-by-step setup and full working code for the bot itself — read that first if you don’t have the base project running yet, since this post builds directly on top of it. By the end of Part 2, the bot is trading and logging every trade and daily equity snapshot to CSV files. That’s fine if you’re comfortable reading raw CSVs over SSH, but it gets old fast. This post adds a proper dashboard: a self-updating web page showing your strategy’s performance against simple buy-and-hold, served over Apache on the same Linux box the bot is already running on. What we’re building No server-side app, no database — Apache just serves whatever static HTML file was most recently generated. Simple to set up, simple to reason about, nothing to keep running besides the bot itself. Prerequisites Step 1: A console report script (optional but useful) Before building the HTML version, here’s a simpler console-only report script — useful for a quick check over SSH without needing a browser at all: Save this as report.py in your project directory. Run it anytime: It prints your total return, a buy-and-hold comparison, max drawdown, recent trades, and saves a matplotlib chart (equity_curve_ACN.png) you can pull off the server if you want a quick visual without setting up the full dashboard. Step 2: The HTML dashboard generator This is the main piece — a script that builds a complete, self-contained HTML report from your log files. It’s a long file because the HTML/CSS/JavaScript for the chart is embedded directly in the Python script as a template, which keeps the whole dashboard to a single generated file with no separate assets to manage. Save this as generate_html_report.py. A few things worth understanding about what it does: Generate your first report: This creates a web/ACN/index.html file inside your project directory. Open it locally first (copy it to your own machine, or just cat it) to confirm it looks right before wiring up Apache. Step 3: Point Apache at the report The simplest approach is an Apache Alias, which maps a URL path directly to your project’s web/ folder without needing to copy files into /var/www/html or mess with permissions there. Create a new Apache config file: Replace YOUR_USERNAME with your actual Linux username. Then enable the config and reload Apache: Visit http://your-server-ip/tradingbot/ACN/ in a browser — you should see the dashboard. If you get a 403 Forbidden error This is the most common snag, and it’s almost always permissions. Apache runs as the www-data user, and it needs traversal permission on every directory in the path to your file — not just the final folder. Home directories are often locked down by default, which blocks www-data even though the Apache config itself is correct. Check the whole permission chain: If any directory in that list is missing execute (x) permission for “others,” grant just enough to allow traversal — not full access: This only allows Apache to pass through those directories to reach the specific file — it doesn’t let it list or read anything else inside them. Step 4: Keep the report fresh with cron Right now the dashboard only shows whatever was true the moment you ran the generator script. Set up a cron job to regenerate it automatically: Add a line to regenerate the report every 15 minutes (matching the bot’s default polling interval): Two details that matter here: Step 5: Running more than one symbol If you’re paper-trading multiple symbols (see Part 2 for running two main.py instances at once), generate a report per symbol into its own subfolder: With the Apache Alias from Step 3, these become reachable at /tradingbot/ACN/ and /tradingbot/SPY/ respectively. Add a second cron line for the second symbol, same pattern as Step 4. A note on security This setup has no authentication — anyone who can reach the URL can see your (paper) trading performance. That’s a reasonable trade-off on a private home network, but not something to expose directly to the public internet. If you need external access, put it behind a reverse proxy with basic auth, or at minimum restrict by IP address directly in the Apache config: Replace the example IP range with whatever network you actually trust. Wrapping up the series That’s the full build: a modular trading bot with a tested strategy and real risk management (Part 2), built on the lessons from actually running it and hitting real bugs (Part 1), now with a live dashboard to watch it work (this post). The whole project — bot, backtester, and dashboard — is maybe 1,500 lines of Python, and every part of it earned its place by something that broke during actual use, not by anticipating problems in advance. That’s probably the biggest lesson of the whole series: build the smallest version that works, run it for real, and let what actually breaks tell you what to build next.

Building a Paper-Trading Bot, Part 3: A Live Dashboard on Apache Read More »

Building a Paper-Trading Bot, Part 2: Full Setup and Code on Linux

This is Part 2 of a 3-part series. Part 1 is the story of what went wrong and what I learned building this — worth reading first for context. This post is the step-by-step build: Linux setup and the complete working code. Part 3 covers adding a live performance dashboard served over Apache. This is a complete walkthrough for building a paper-trading bot in Python using the Alpaca API, running on a Linux server. By the end you’ll have a bot that pulls market data, generates buy/sell signals from a trend-following strategy, manages risk with a volatility-scaled stop-loss, executes paper trades automatically, and logs everything for review. Everything below is real, working code — this is the actual project I run in production (well, paper-production) on my own Ubuntu server. What you’ll end up with Prerequisites Step 1: Get free Alpaca paper trading API keys Paper trading uses simulated money against real market data, so you can test everything safely before ever considering real funds. Step 2: Set up the project directory and virtual environment That last command activates the virtual environment — you should see (venv) appear at the start of your terminal prompt. You’ll need to run source venv/bin/activate again every time you open a new terminal session to work on this project; it only applies to the current shell. Step 3: Install dependencies Create a requirements.txt file: Then install: Step 4: Store your API keys securely Create a .env file in the project root: Replace the placeholders with the keys from Step 1. Never commit this file to version control — if you’re using git, add .env to your .gitignore. Step 5: Build the project, file by file Here’s the full project structure we’re building: config.py — central settings Everything the rest of the project needs is configured here: API keys, strategy parameters, and risk settings. A few of the defaults worth understanding before you change them: data_feed.py — pulling market data This wraps Alpaca’s data API so the rest of the code doesn’t need to know the API details. strategy.py — the actual trading logic This is the heart of the bot: turning price data into a BUY, SELL, or HOLD decision. A quick note on the design here, since it’s easy to get subtly wrong: signals are evaluated as a current state (is the short average above the long average right now, and does ADX confirm a trend right now) rather than only firing on the exact bar a crossover happens. An earlier version of this logic only fired on the crossing bar itself — which sounds equivalent but isn’t: if ADX takes a few days to confirm a trend after the crossover already happened, an event-based check misses the entry entirely, since the “crossing” moment has already passed by the time confirmation arrives. Checking the state fresh on every bar avoids that. risk.py — position sizing and stop-loss The stop-loss uses ATR (Average True Range) instead of a flat percentage. Here’s why that matters: a flat 2% stop sounds reasonable until you apply it to a volatile stock — a 2% intraday swing can be completely normal noise for a volatile name, so a flat stop just gets you shaken out repeatedly by nothing. Scaling the stop to each symbol’s own recent volatility means a calm stock gets a tight stop and a volatile one gets appropriately more room. executor.py — talking to Alpaca Two things worth calling out: trade_logger.py — recording everything This logs to per-symbol CSV files (trade_log_ACN.csv, equity_log_SPY.csv, etc.) so you can run multiple symbols from the same project directory without their logs overwriting each other. Step 6: Backtest before running anything live This is the step I’d encourage you not to skip, whatever strategy you end up using. Here’s the backtesting script: backtest.py Run it like this: The most important line in that output is the buy-and-hold comparison. A strategy can show a positive return and still be a bad idea — the only way to know is to compare it against what you’d have gotten just buying and holding the same stock over the same period. Don’t skip this comparison; it’s the single most useful sanity check in the whole project. You can also tune the strategy’s parameters directly from the command line without touching the code: Step 7: Run the live paper-trading loop Once you’re comfortable with the backtest results, here’s the live trading script: main.py Run it with: This polls every 15 minutes by default, checks the strategy’s signal, and submits paper orders through Alpaca when conditions are met. Each cycle prints the current indicator values (moving averages, ADX, current position) so you can watch it work in real time instead of guessing what it’s doing. Step 8: Keep it running with tmux SSH sessions die, laptops sleep — you don’t want the bot to stop just because your terminal disconnected. tmux solves this: Detach with Ctrl+B then D (press and release Ctrl+B, then press D separately — it’s a two-step sequence, not a single chord). The bot keeps running in the background. Reattach anytime with: Step 9: Check in on it periodically A few habits worth building once this is running unattended: A couple of hard-earned lessons Don’t gate your stop-loss on the same trend filter as your entry. I initially removed the ADX confirmation from the exit condition, reasoning the ATR stop already handled downside risk. Backtesting showed this let the strategy bail on ordinary pullbacks within an otherwise healthy trend, then re-buy shortly after — generating more whipsaw losses than it prevented. Requiring trend confirmation on both entry and exit, symmetrically, fixed it. Always check for pending orders, not just filled positions, before submitting a new one. This is the bug that cost me fourteen unintended paper trades in one morning. If an order is submitted while the market is closed, it sits queued rather than executing — and a polling loop that only checks “do I already hold a position” has no

Building a Paper-Trading Bot, Part 2: Full Setup and Code on Linux Read More »

Building a Paper-Trading Bot, Part 1: What I Learned From Getting It Wrong Three Times

This is Part 1 of a 3-part series on building a paper-trading bot from scratch. This post is the story — what I built and what went wrong along the way. Part 2 is a step-by-step tutorial with the full working code if you want to build it yourself. Part 3 covers adding a live performance dashboard served over Apache. I set out to build a trading bot. I ended up learning more about testing discipline, benchmark comparisons, and the subtle ways “correct-looking” code can still be wrong than I did about trading itself. This is the story of that build — the architecture, the backtests that punctured my assumptions, and two live bugs that only showed up once real automation was running unattended. If you’re thinking about building something similar, I’d rather show you where it actually went sideways than pretend it was smooth from commit one. The starting point The goal was modest on purpose: get a Python bot paper-trading on Alpaca, with a strategy simple enough to reason about — a moving-average crossover. Before writing a line of strategy logic, I laid out the pipeline as separate, swappable pieces: Keeping these separate turned out to matter a lot more than I expected. Every bug I hit later was isolated to one module, and I could reason about (and fix) each one without touching the others. The first version of the strategy was a plain SMA crossover: buy when a 20-day moving average crosses above a 50-day average, sell on the reverse. Dead simple, and a reasonable scaffold to prove the plumbing worked end to end. Backtest #1: humbled by buy-and-hold The first backtest ran fine — positive return, plausible-looking trades. It felt like a win right up until I added one more line of output: what would buy-and-hold have returned over the same period? That single comparison changed everything. On AAPL over a 730-day window, the strategy returned about 25%. Buy-and-hold returned nearly 47%. The strategy wasn’t just “okay” — it was leaving almost half its potential return on the table, entirely because it sat in cash during a strong bull run, waiting for confirmation signals that a buy-and-hold investor never needed. Lesson: a backtest without a benchmark isn’t really a backtest — it’s just a number. Positive returns feel good in isolation, but “good” is only meaningful relative to the simplest alternative: doing nothing. Adding a trend filter — and testing it honestly The obvious next step was gating the crossover on trend strength, using ADX (Average Directional Index) — only trust a crossover if ADX confirms the market is actually trending, not just noisy. That cut down on whipsaw trades in choppy periods. But here’s the thing about a filter like this: it has a real cost as well as a real benefit, and you only see the cost by testing it somewhere it’s supposed to hurt. So instead of only testing on one stock, I ran the same strategy across four very different regimes: The pattern that emerged was completely coherent, and completely different from what a single-symbol test would have shown: the strategy underperformed buy-and-hold by 20-30 percentage points on AAPL and SPY (the cost of waiting for confirmation in a market that just kept going up), but beat buy-and-hold by over 25 percentage points on ACN — it correctly stepped aside during the worst of a real decline. That’s not a strategy that “works” or “doesn’t work.” It’s a strategy with a specific, identifiable regime it’s good for. Knowing that is worth infinitely more than a single aggregate number from one stock. The stop-loss that made things worse Position risk was still an open problem — a naive flat 2% stop-loss sounded reasonable on paper. It was not reasonable in practice. Backtesting it on COIN revealed why: nine stop-loss exits in under a year, several just days apart. A 2% move is ordinary daily noise for a stock as volatile as COIN — the stop wasn’t protecting against real reversals, it was getting shredded by the stock’s normal breathing room, forcing repeated re-entries at worse prices each time. The fix was to scale the stop distance to each symbol’s own volatility using ATR (Average True Range) instead of a flat percentage — entry_price – (2.5 × ATR at entry). A volatile stock naturally gets a wider stop; a calm one gets a tighter one. Re-running the same COIN backtest after the change: stop-loss exits dropped from nine to two, and the damage from each was cut by more than half. Lesson: risk parameters that “feel” reasonable in isolation can be actively harmful without accounting for the asset’s own behavior. Test the thing you added, not just the thing you started with. Going live surfaced a bug backtesting couldn’t catch Backtesting works because you’re replaying finalized, settled data. Live trading exposed a class of bug that simply doesn’t exist in that world: timing. The original signal logic only fired a BUY on the exact bar a crossover happened, gated by whether ADX confirmed a trend on that same bar. In backtesting, this looked fine. Live, it quietly broke: ADX often takes a few days to climb above the trend threshold after a price move starts. If the crossover happened while ADX was still catching up, the strategy correctly held off — and then never revisited the decision, because the “crossing” moment had already passed by the time ADX confirmed anything. I only caught this because I was watching the live indicator values directly and noticed SMA20 sitting comfortably above SMA50, with ADX freshly past the trend threshold — every condition for a buy, and yet nothing fired. The fix was switching from event-based signals (“did the cross happen on this exact bar?”) to state-based signals (“is the market currently in a qualifying state?”) — evaluated fresh every cycle instead of only at the moment of crossing. Re-running backtests after the fix produced a nuance worth sitting with: the fix helped one symbol

Building a Paper-Trading Bot, Part 1: What I Learned From Getting It Wrong Three Times Read More »

Zyxel NWA50BE PRO Review: Upgrading from Cisco 240AC to Wi-Fi 7

After several years of running a three-pack of Cisco Business CBW240AC 802.11ac access points, I decided it was time for an upgrade. The Cisco 240ACs had served me well. They were reliable, offered good coverage throughout the house and, for their generation, delivered a solid wireless experience. However, wireless technology has moved on considerably since I installed them. My old Cisco access points were based on Wi-Fi 5 (802.11ac), whereas my replacement is the Zyxel NWA50BE PRO, a Wi-Fi 7 access point capable of up to BE6500 / 6.5Gbps of aggregate wireless throughput and equipped with a 2.5GbE Ethernet interface. Zyxel NWA50BE Pro Wi-Fi 7 Access Point This is the access point I purchased and installed as part of my upgrade from the Cisco 240AC to Wi-Fi 7. If you’re interested in the NWA50BE Pro, you can check the current price and availability on Amazon UK. Check Price on Amazon UK Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. But there was another reason this particular upgrade was interesting for me. Stepping outside my comfort zone As an Infrastructure Engineering professional, I’ve spent much of my career working with enterprise networking and infrastructure products. Cisco, HPE, Aruba and other established enterprise vendors are the names I’m familiar with and, more importantly, the platforms I’ve trusted. So when I started looking for a replacement for my Cisco access points, moving to Zyxel wasn’t an obvious decision for me. I’ll be honest — I was nervous about it. Zyxel is a brand I’d obviously come across before, but it wasn’t a vendor I had personally relied on in the same way I had relied on Cisco and HPE in professional environments. I therefore spent quite a bit of time researching the NWA50BE PRO before making the decision. I looked at the specifications, management platform, firmware support, security features, VLAN capabilities, PoE requirements, cloud management and, importantly, what the real-world experience was like for people actually using the hardware. I also wanted to make sure that it would integrate properly with the network architecture I already had at home. That meant considering how it would work alongside my pfSense firewall, my network segmentation, multiple SSIDs and existing PoE switching infrastructure. There was a certain amount of hesitation before pressing the order button. Would it feel like a step backwards coming from Cisco? Would the management platform be good enough? Would the firmware and support be there? Would it actually perform as advertised? These were all questions I had before making the switch. As it turned out, I needn’t have worried. The Zyxel NWA50BE PRO has genuinely surprised me. In fact, after installing and configuring the three access points, I was amazed by how capable the product is. The hardware feels well thought out, the management experience is straightforward and the performance has been excellent. Most importantly, it doesn’t feel like I compromised by moving away from the enterprise vendors I’m accustomed to using. If anything, the experience has made me reconsider where Zyxel fits into the networking market. My Home Network Setup Before looking at the access points themselves, it’s worth explaining how I’ve built my home network. I’m a big believer in keeping the network properly segmented, even at home. Rather than putting every device onto a single flat network, I’m using my own custom pfSense firewall as the core of the network. The wireless network is separated into two distinct environments: These are presented to wireless clients using two separate SSIDs. My wireless setup SSID 1 – Trusted My primary wireless network for trusted devices. SSID 2 – Guest A separate guest network with restricted access to my trusted network. The Zyxel access points are therefore not simply providing wireless connectivity. They are sitting within a properly segmented network architecture, with pfSense handling the routing, firewalling and network separation. This is one of the reasons I particularly like using business-oriented networking equipment at home. It gives me the flexibility to build the network in the way I want rather than relying entirely on whatever functionality happens to be built into a consumer router. What You Need for a Similar Home Network Setup If you’re considering building something similar to my home network, here’s a rough idea of the equipment I’m using — or, more accurately, the type of equipment you would need. It’s important to point out that my own hardware has been in use for several years, and some of the exact models I have are no longer readily available. Rather than recommending obsolete hardware, I’ve included links to current alternatives that are similar in terms of functionality and capability. I’ve also deliberately gone for equipment that sits towards the higher end of what you might consider for a home network. You absolutely don’t need to spend this much to get good Wi-Fi at home. There are plenty of cheaper alternatives available, particularly if you don’t need VLANs, multiple SSIDs, PoE or advanced firewall functionality. However, if you’re interested in building a network similar to mine, these are the types of components I’d look at. 🛜 Wi-Fi Access Points The most important part of my upgrade is the three Zyxel NWA50BE PRO Wi-Fi 7 access points. I’m using three APs to provide coverage throughout my home, with the APs connected back to my wired network using Ethernet and powered via PoE. Zyxel NWA50BE PRO BE6500 Wi-Fi 7 Access Point Check Price on Amazon UK Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. The three APs are configured to provide both my trusted and guest SSIDs, with the

Zyxel NWA50BE PRO Review: Upgrading from Cisco 240AC to Wi-Fi 7 Read More »

Alcatel Omniswitch useful commands

These commands have been tested on the 6400 and 6850 models but may also work on others. Reset switch to factory defaults Delete the boot.cfg file from the working and certified directories and then reload the switch   After it reboots type either of the following commands to verify that you have a new config file.   Save configuration The first command will save the primary (working) config and the second will save the secondary (Certified) config.   If you have a stack with 2 or more switches then use the following command to synchronise the config across all slots   Verify the configuration and synchronisation status with the following command.   You should get the results below Quote:CONFIGURATION STATUSRunning CMM              : PRIMARY,CMM Mode                : MONO CMM,Current CMM Slot        : 1,Running configuration    : WORKING,Certify/Restore Status  : CERTIFIEDSYNCHRONIZATION STATUSRunning Configuration    : SYNCHRONIZED, Change system name and session prompt Replace ‘Switch1’ with your own name   Set system location and contact info.   Check the information has been changed with the following command:   Specify Domain name, NTP and DNS servers   System and hardware information System information   Chassis information   Stack information   CPU health   Memory health   VLAN configuration Create new VLAN with description. I’ll be using VLAN 10 for this example.   Remove VLAN with:   Assign switch ports 1 through 24 on slot 1 to VLAN 10 using the following command:   for 802.1q (tagged) port use the following command:   You can remove the VLAN port members with the following command:   To verify the VLAN 10 configuration, use:   To verify that ports 1/1-24 were assigned to VLAN 10 use:   Interface Configuration Show status of all ports:   Show information about all ports:   To show information about a specific port only (slot/port):   Make changes to an interface with the following commands. I’ll be using slot 1 and port 5 in my examples. Disable auto negotiate (enable or disable):   Change duplex (full or half):   Change interface speed (10,100,1000):   Disable or enable a port (up or down):   Label the ports with:   Create a management interface   Verify with:   Remove interface with   Link Aggregation  Run the following command to create a LACP group for ports 19,20,21 and 22. In this example i’ll be using 2 as the ID, 4 as the size and 5 as the key   Now set the VLAN for the LACP group:   POE Start or stop POE on slot 1 (Change slot number accordingly):   Start or stop POE on a port (slot/port):   System services The following services are available: ftp, ssh, telnet, http, secure-http, udp-relay, snmp, all Enable a service:   Disable a service:

Alcatel Omniswitch useful commands Read More »

Ubuntu 14.04 Extend logical or physical disk space​

This worked for me when I needed to extend the disk space in a vmware virtual machine. Once you extend the space in vCenter you need to reboot the VM. Create a partition with available disk space using this command.   Select available space then choose ‘write’ then ‘quit’.   Scan for the new partition with: You may need to restart if the above command doesn’t work. Check for the new partition with:   Create a new volume (I will assumed the new partition found above is sda3)   Show the physical volumes with   Now we can extend the primary disk with the following commands. You should be able to get the VOLUME_GROUP name from the above command.   Check available space with:

Ubuntu 14.04 Extend logical or physical disk space​ Read More »

New Ubuntu disk partition with Parted

The follow process has been tested on Ubuntu versions 18.04 and 20.04 but may also work on newer versions. Run the following command to determine the new path assigned to your new disk:   This should produce an output similar to this: Copy  logical name: /dev/sda      size: 60GiB  logical name: /dev/sdb      size: 100GiB I’ll be partitioning /dev/sdb and using the full available space. Start parted as follows:   Create a new GPT disklabel:   Set the default unit to GB or TB (i’ll be using GB as it’s only 100GB):   Create one partition occupying all the space:   Verify with:   Quit “parted”:   Format the new partition (use one of these commands to format to fat32 or ext4):  

New Ubuntu disk partition with Parted Read More »