Khurram

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 »