Projects

↩ Warp back to the vault ↩ View all projects            

Pip: Case Study

Overview

Pip logo, smiling face with money symbols as eye

Pip is the Discord bot for instant currency and crypto conversion, rate alerts, and foreign exchange market analysis. It runs as a full two-tier suite: free tools for conversion and market data, and a premium tier for statistical analysis, portfolio tracking, watchlists, alerts, and more. 200+ currencies including crypto. Verified by Discord, fast, and online 24/7.

Stack: Python, discord.py, aiohttp, systemd, Oracle Cloud (Ubuntu 22.04)

Problem

Within the Discord community, existing currency converter bots had orphaned slash commands with no live backend, dead API integrations, or hadn't been online in months. There was also nothing that treated currency as a subject worth exploring beyond a simple lookup: no market context, no trend data, no tools for people who actually care about FX beyond converting travel money.

Product

Pip is organized into a free tier and a premium tier.

 

The free tier covers everyday currency needs:

  • /convert calculates a one-to-one conversion
  • /multiconvert converts a single amount into up to 15 currencies at once
  • /compare shows how a conversion has changed since a past date (defaulting to the start of the current quarter)
  • /topmovers displays the strongest and weakest currencies against a base in the last 24 hours
  • /strength ranks currencies by movement against a configurable basket
  • /spotlight shows a random major pair with its 30-day trend
  • /setbase and /choosebasket save preferences across commands

 

The premium tier goes further:

  • /graph generates a PNG line chart for any pair over a custom date range,
  • /volatility returns statistical volatility and trend classification
  • /correlate calculates the Pearson correlation between two currency pairs
  • /risk surfaces drawdown and range metrics
  • /streak shows currencies on the longest current winning or losing streaks against a basket
  • /grid produces a heatmap-style percent-change matrix across a basket
  • /watchlist lets users save pairs and view them with daily and weekly change
  • /portfolio tracks holdings and their current value in any base currency
  • /portfolio_compare shows how a portfolio has moved over N days
  • /alert sets rate-change or percent-change notifications via DM with support for recurring alerts, cooldowns, AND-logic grouping, and reusable templates
  • /digest sends a daily DM summary of watchlist movement, top movers, and portfolio total at a scheduled UTC time
  • /export delivers a CSV of saved data as a direct message

 

All outputs are formatted in monospace code blocks so they copy cleanly into spreadsheets, docs, or other chats. Currency fields autocomplete as you type, supporting both currency codes and full country or currency names.

 

The bot is publicly invitable, runs 24/7 on a free Oracle Cloud VM, and currently lives in a handful of private and community servers.

Architecture

Pip is intentionally minimal: one Python file, one external API, and one systemd service.

 

Frontend: Discord's slash command system handles UI entirely, so there's no web server, message parsing, or command prefix logic.

 

Backend: discord.py manages the WebSocket connection to Discord's gateway. Slash commands fire async HTTP requests to a free, no-key currency API (fawazahmed0/exchange-api) and formats the result into embeds. The currency API has a primary CDN host and a Cloudflare Pages fallback, so the bot tries both before erroring, protecting against single-host downtime.

 

Caching: The list of supported currencies is fetched at startup and held in memory, so autocomplete doesn't hit the network on every keystroke.

 

Hosting: Pip is hosted on an Ubuntu VM through Oracle Cloud's Always Free tier. systemd manages the process by auto-starting on boot, auto-restarting on crash within 10 seconds, and capturing all logs to journald

Engineering Decisions

Defer before you fetch. Discord requires a response within 3 seconds or the interaction errors out. Calling interaction.response.defer() immediately, making the API call, then following up keeps every command safe from timeout regardless of network latency.

 

Validate before you defer. Cheap validation, such as negative amounts, same source and target, and unsupported codes, happens before the defer call so users get instant ephemeral error messages without a loading state. Only legitimate requests pay the deferral cost.

 

Pick the right API for the job. I initially used Frankfurter API (ECB-based with ~30 currencies) for its reliability, then swapped to fawazahmed0's API for broader coverage, including crypto and long-tail currencies beyond Frankfurter's scope. The swap was a 30-line diff because the fetch logic was isolated in two functions. 

 

Cache the cheap stuff. Currency codes barely change, so loading them once at startup makes autocomplete feel instant and saves thousands of needless requests over the bot's lifetime. 

 

As the command set expanded, keeping the command structure coherent became its own design problem. Grouping related commands (portfolio, watchlist, digest, alert) into slash command groups kept autocomplete manageable and made the bot easier to discover incrementally.

Deployment

I'll skip the part where Oracle's UI fought me on assigning a public IP for an hour, except to say the workaround involved Cloud Shell, several hardcoded OCIDs, and a healthy amount of swearing.

 

This is how the final deployment shaped up:

 

  1. Provision a free Oracle VM (AMD E5.Flex with 12 GB RAM, well over what's needed)

  2. SSH in, install Python and a venv, copy over bot.py, requirements.txt, and a .env file containing the Discord token
  3. Write a systemd unit file that runs the bot under the venv's Python interpreter
  4. systemctl enable and systemctl start, watch the logs to confirm it connected
  5. Configure the Discord Developer Portal: enable Public Bot, set up the install link with bot and applications.commands scopes, set permissions to Send Messages and Use Slash Commands

 

The bot has been running uninterrupted since deploy with no crashes, no manual interventions, and no late-night SSH sessions to revive it!

Lessons Learned

Use per-guild slash command sync during development. Discord caches globally-registered commands for up to an hour, so iterating on command shapes means lots of waiting. Syncing to a single test server instead is instant. I learned this by staring at Discord for far too long, wondering what I'd broken.

 

Default values can hide your parameters in Discord's UI. Setting amount: float = 1.0 made the field optional, which tucked it into a secondary menu rather than displaying it inline with the currency fields. The amount input wasn't where users expected it, so they skipped right past it. Removing the default fixed it. UX bugs in framework-driven UIs often live in defaults you didn't think were UI decisions.

 

Discord's permission model has more layers than you think. Inviting Pip to a server involved three separate decisions in the Developer Portal: public bot toggle, guild install context, and default install scopes. Getting any one wrong broke the invite flow. Cloud and platform integration is rarely "one switch." Every integration is a small system of settings that have to align, and finding the misalignment usually means reading docs you wish you'd read first.

Takeaways

The architecture held up as the command set grew because the core decisions, isolated fetch logic, deferred responses, and memory caching were made with extension in mind. The hardest parts were never the currency math but rather the platform integration details: Discord's permission layers, systemd on Oracle's networking quirks, and designing a command surface that stays approachable as it scales.

 

This is my first Discord bot, but not my last. I'm planning a second one to complement my capstone project, Anchor, with everything I learned from Pip baked in from day one: per-guild command sync during development, monitoring from the beginning, and caching where it actually matters. 

 

The forex pip is the smallest unit of price movement, small in scope but the foundation everything else builds on, so this feels like a fitting first foray.

↩ Warp back to the vault ↩ View all projects