Automated Trading: How 500 Lines of JavaScript Funded My Steam Library
The story of my automated arbitrage bot that funded every game I’ve purchased for years. Discover how automation powered my Steam collection and how you can too.
Steam Trading Card Bots
In the vast ecosystem of Steam, Valve’s digital distribution platform, a unique economy emerged around collectible trading cards. For years, I operated a Node.js trading bot that exchanged items like Team Fortress 2 keys for complete sets of these digital cards. What started as a weekend coding project eventually funded my entire Steam library. Here’s the story of how it worked and the fascinating ecosystem behind it.
Understanding Steam Trading Cards
Steam introduced trading cards in 2013 as collectible digital items that transformed the platform’s social experience. These virtual cards became the foundation of an entire ecosystem of collecting, trading, and crafting.
How The System Works
Each supported game on Steam offered its own unique set of collectible cards:
- Most games featured between 5-15 unique cards per complete set
- Cards featured artwork from the game, making them visually appealing collectibles
- Each card had varying rarity and market value based on the game’s popularity
The Collection Process
The genius of Valve’s system was that it encouraged social interaction and marketplace use:
- Initial Acquisition: Players would automatically receive card drops while playing eligible games
- Incomplete Sets: Most games would only drop about half of the full set to any single user
- Social Component: The remaining cards needed to be acquired through trading with other players
- Marketplace Option: Players could also buy specific cards from the Steam marketplace
- Crafting Reward: Once a user collected a complete set, they could “craft” these cards into a game badge
This deliberate design created natural scarcity that drove trading activity and marketplace transactions. Players couldn’t simply earn complete sets through gameplay alone, they needed to engage with the wider Steam community or marketplace to complete their collections.
The Purpose: Profile Customisation and Levelling
The badges crafted via a completed trading card set formed the backbone of Steam’s profile levelling system. When users crafted a badge by combining a full set of trading cards, they would:
- Earn 100 XP toward their Steam level
- Unlock a random emoticon from the game
- Receive a profile background from that game
- Occasionally receive discount coupons for other Steam games
Levelling up on Steam unlocked meaningful benefits, including:
- One new profile showcase every 10 levels, letting users highlight rare achievements, favourite games, artwork, or custom collections
- Increased friend list capacity, starting at 250 friends and growing by +5 friends per level
- Greater visibility in community hubs and higher ranking on friends lists
- Higher Steam levels also increased your chances of receiving “booster packs” (free random card drops)
For many players, levelling up became a way to personalise their profile, showcase their rare items, and stand out in the Steam community - turning card collecting and badge crafting into a massive trading and crafting economy. Over time, a high Steam level became more than just a functional advantage; it became a status symbol. Really, it was all about the flex - showing off your dedication, collection, and time invested to everyone who visited your profile.
What Are Steam Trading Card Bots?
Steam trading card bots are automated accounts programmed to facilitate trades between users and the bot itself. These bots served as virtual marketplaces where players could exchange valuable virtual items, typically TF2 keys, CS:GO skins, or other commodities, for complete sets of Steam trading cards.
The core functionality was surprisingly simple: users would initiate a trade with the bot, offer a predetermined “currency” (often TF2 keys), and in return, the bot would automatically provide complete sets of trading cards from various games.
The Economics That Made It Work
The economics behind this trading system created a perfect arbitrage opportunity. Here’s how the numbers broke down:
The Value Chain
- Currency: TF2 keys maintained a stable value of approximately ~$3.65 AUD each
- Product: Complete card sets individually cost $0.30-0.70 on the Steam marketplace
- Wholesale: My bot purchased bulk cards from farmers at wholesale prices
- Retail: The bot would then sell those complete sets at 15-30 sets per TF2 key
Why It Worked
Users seeking to level up their Steam accounts found this system incredibly cost-effective. Rather than:
- Spending $0.30-0.70 × 15 sets = $4.50-10.50 buying individual sets on the marketplace
- They could trade a single ~$3.65 TF2 key for 15+ complete sets through my bot
This pricing strategy created a win-win scenario:
- For users: Getting sets at 40-75% below market price
- For me: Creating profit through volume and arbitrage between wholesale and retail prices
The margin between my bulk acquisition cost and what users would pay in keys created a sustainable profit cycle. Over time, this simple arbitrage bot generated enough virtual currency that I was able to withdraw and sell the keys, effectively funding my entire Steam library without spending additional money out of pocket.
The Card Farming Connection
These trading bots didn’t exist in isolation, they were part of a larger ecosystem that included “card farmers.” Card farming referred to the practice of obtaining Steam trading cards with minimal effort, often through automated processes.
When a Steam user plays a game, they’re eligible to receive a limited number of card drops (typically half of a game’s set). Once received, these cards could be traded or sold on the Steam marketplace.
This led to the rise of specialised farming accounts, many operated out of regions where the value proposition made economic sense (Russia was particularly known for this). These farmers would:
- Purchase extremely cheap games via back channels or games that are free for a limited time
- Use specialised software like “Idle Master” or “ASF (ArchiSteamFarm)” to simulate playing these games to receive card drops
- Collect the cards and either sell them directly on the marketplace or more often than not trade them in bulk to bots like mine
The most efficient operations would run thousands of accounts simultaneously, creating a constant stream of new trading cards entering the economy every time a new game became available to the farming community.
How It All Started
I used one of these bots for the first time on 15 April 2017. I used it to get to level 50 on Steam, and then thought I wanted my own bot. Five days later, I traded the excess sets to my bot account.
I’ve always had a natural curiosity when discovering new systems like this. There’s something captivating about understanding the mechanisms behind them, figuring out how they operate, and finding ways to optimise or improve them. This particular curiosity led me down a rabbit hole that would consume me for years.
On 7 February 2018, I purchased a Chroma Case Key to test trading. I manually traded 5 times. This validated that you could make some profit without being one of the big players. At this point, I started to look into how to make the bot.
Years ago, I had made what was effectively a Steam RAT (remote access trojan) in C#. I took the Steam API and used the chat hooks to trigger random functionality, inspired by the YouTube video Programming Drunk PC prank application in C# .NET : #Codegasm 4 - @Barnacules. My little RAT would shake the mouse, close their current application, and so on. So I was familiar with Steam bots, but the libraries I used for the RAT wouldn’t work since they had no trading functionality.
From my research, it appeared that all of the big bots were using DoctorMcKay’s Steam libraries in NodeJS. I started working it out…
By early March, I was ready to start experimenting with real items. I purchased an additional 7 keys and ran my first sets of automated trades in mid-March 2018. Testing continued throughout March until 29 March 2018, when I purchased another 2 keys to build stock for the soft launch. This meant when it launched, it had 10 keys worth of stock. After the soft launch, my trade logs became filled with me withdrawing keys, buying sets, and depositing the sets, as suppliers were hard to come by at the start.
In late 2018, I added an additional 28 keys that I received for doing various freelance jobs for other bot owners. This primarily involved adding functionality from my bot to other people’s bots or creating custom scripts for them. Progress up until this point was really slow, but this is when the snowball effect started taking place.
My Setup
When I finally got the code set just right, I started optimising the setup. I ran 2 bots simultaneously, hosted on a Raspberry Pi so I could run them 24/7 with minimal cost.
Raspberry Pi Running Both Bots
In reality, I probably didn’t need two bots given the volume I was experiencing, but it allowed me to take overstock on one without having larger farmers clear all of my inventory of keys without getting sufficient variety in sets.
How Users Use the Bot
The user experience was designed to be as simple and familiar as possible. I studied how the established bots operated and replicated their command structure to ensure users wouldn’t need to learn a new system. The interaction was entirely through Steam’s built-in chat functionality.
When users wanted to make a trade, they would:
- Add the bot as a friend on Steam (if they hadn’t already)
- Send a chat message with their desired purchase using simple commands
- Receive an automated trade offer within seconds
- Accept the trade to complete the transaction
The command structure was intentionally straightforward, users would type commands like:
buy 1
- Purchasex
sets using 1 CS:GO key (the default currency)buytf 1
- Purchasex
sets using 1 TF2 keybuyhydra 1
- Purchasex
sets using 1 CS:GO Hydra Key (removed from the generic buy due to the fall in value of Hydra Keys)buygems 1
- Purchase 1 sets usingx
Steam Gems
For whatever reason, CS:GO keys had become the default currency in the broader bot ecosystem, so buy
without any suffix defaulted to CS:GO keys. All other currencies required their specific suffix to be added to the command. All x
would be replaced by a defined exchange rate which will be dicussed in the next paragraph.
Bot Message Commands For Trading
Behind the scenes, each command triggered a series of validation checks implemented through a cascade of if statements:
- Rate limiting: Has this user been making too many requests recently?
- Input validation: Is the number legitimate? (Not a string, not absurdly high, not zero or negative, ect)
- Trade conflict checking: Does the user already have pending trades with the bot?
- Inventory verification: Do I have enough sets in stock to fulfill this order?
- Payment verification: Does the user have enough of the specified currency in their inventory?
If all checks passed, the bot would automatically generate and send a trade offer. If any check failed, the bot would respond with a helpful error message explaining what went wrong and how to fix it. This automated validation system prevented most issues before they could occur, keeping the trading process smooth and eliminating the need for manual intervention.
Day-to-Day Operation of the Bot
Running the bot day-to-day was surprisingly hands-off, requiring minimal intervention once it was up and running. The bot’s operations were mostly monitored out of curiosity rather than necessity. However, it was always fascinating to see the constant stream of activity-trade logs, messages, and updates coming in at all hours of the day, proving that the bot was consistently humming away.
Bot backend trade logs & bot messages alerting of new sales
Each trade made by the bot was logged into its dedicated directory. For every day, a new text file was created with the trade details, including the time of the trade, the Steam ID of the user, and the item details. The trade logs were appended daily, making it easy to track the bot’s activities over time. On the flip side, there were also directories for chat logs, which captured the bot’s communication and provided insight into the flow of interactions.
What was really cool was witnessing the bot’s ability to make a few cents to a few dollars on every trade, all without manual input. While the bot operated automatically, receiving messages about sales and trades was an exciting reminder that the system was functioning smoothly. However, in hindsight, I wish I’d kept more detailed records of the bot’s inventory. A weekly or monthly summary, for example, would have been great to track the exact amount of items or the total number of trades completed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"allowBuy":true,
"pricecs":20,
"allowBuyTf":true,
"pricetf":20,
"allowBuyHydra":true,
"pricehydra":5,
"allowBuyGems":true,
"pricegems":400,
"allowSell":true,
"pricesell":21,
"allowSellGems":true,
"sellgems":375,
"allowSellTf":true,
"priceselltf":21,
"allowSellHydra":true,
"pricesellHydra":11,
"maxStock":30,
"maxKeys":50,
"maxBuy":100,
"maxSell":50,
"maxLevelCheck":999,
}
The real challenge came with items that had unstable market values. In the above example, certain items like Hydra keys could fluctuate significantly in price. When these price swings occurred, the bot’s selling rate would sometimes need to be adjusted to reflect the new market conditions. If the rates weren’t updated regularly, the bot risked clearing out its stock and holding items that were worth less than their original value. Fortunately, in the long run, most of these items appreciated in value, so any misses with pricing caused minimal damage.
Bot Statistics
The following stats aren’t lifetime numbers - they’re based on the best continuous archive I could find, covering the period from 26 February 2019 to 22 May 2021 (2,824 of the 4,167 total trades). Over time I changed the way the bot recorded and formatted its data, but this archive offers a really solid, normalised snapshot of the bot’s performance during those years.
During this window, the bot sold 57,101 sets across all currencies. TF2 keys were by far the most common payment method, with 2,761 TF2 keys received for 48,892 sets. CS:GO keys were much rarer, with only 150 CS:GO keys used to buy 3,519 sets - likely a reflection of their limited supply compared to the incredibly stable and accessible nature of TF2 keys. On top of that, the bot received 1,885,647 gems in exchange for 4,690 sets.
Across this period, the bot traded with 582 unique users. Just 10 users (less than 2% of the total), were responsible for 23% of all set purchases, buying 13,362 sets between them. The largest individual buyer snapped up 2,885 sets, paying with 164 TF2 keys, and the rest of the top 10 consistently purchased hundreds to over a thousand sets each. These users were true whales and a major source of volume for the bot. Looking at the profiles of these users, it appears most of them were also selling sets, so it’s likely they were using my bots to get inventory for their service. This is fine though I still made the same margin no matter who bought my sets.
Cashing Out
I couldn’t find a way to export how much I had pulled out of the bot, but I could see my market history from my main account. Again, I couldn’t find a way to export this data, so I reverted to my old trick of when a website doesn’t provide the functionality I want: I hack their DOM. I wrote a quick and dirty DOM script to export all of the market pages one by one.
After analysing this data and removing the irrelevant sales, I found that:
- A very rough approximate would put items used by bot at ~300$ (primarily spent on inventory expansions and levels on the bot for more friend slots)
- In total I withdrew just over $1,150 worth of items from the bot
- So far I’ve spent a little over $450 of items
- At the moment I’ve got 175 TF2 keys waiting for a rainy day to be sold
This process was rather slow due to the low amount of capital injected, the snowball effect of having more stock would have been huge. Steam instituted a trade lock on items such as keys for a week after each trade. This meant after every sale my bot made at the start, I would be stuck for a whole week. Over time as I got more stock, this was less of a limiting factor and I was more held back by the limited number of suppliers I had at my desired price. If I had started with a higher initial investment, the total return would likely have been far higher.
Conclusion
Over the 3 years, 5 months, and 14 days the bot ran, I started with an initial investment of $130 and ended up withdrawing $1,150 worth of items, which meant a total profit of around $1,020. That works out to an average profit of about $0.24 per trade, based on the 4,167 trades made. If you break it down further, that’s an average profit of about $0.28 per day when extrapolated over the entire period.
This means I achieved a return of around 785%. To put that into perspective, the annualised return, or compound annual growth rate (CAGR), works out to approximately 84.5% per year. That means if I had reinvested the profits each year, the bot would have been growing at nearly nine times the rate of a typical long-term stock market return.
If you compare the $0.28 per day passive income to another passive income method like dividends, it’s a pretty good way of leveraging my money, and even better yet, I learnt a language out of it. JavaScript has become my go-to language for day-to-day programming, and it’s immensely useful as seen by all of the DOM hacking I do.
It is important to note that these kinds of returns aren’t likely to be sustainable today. I ran the bot at what might have been the peak of the Steam set market, when there was more opportunity and less competition. Nowadays, returns are much smaller due to more restrictions and increased market saturation.