DEV Community

Janckins
Janckins

Posted on

I Got Tired of 10 Browser Tabs for Crypto Trading, So I Built an Open-Source Desktop App

Every morning started the same way. CoinGecko in tab one. Etherscan gas tracker in tab two. CoinGlass for funding rates in tab three. Then five separate exchange tabs to check balances. Maybe Dexscreener if I was feeling adventurous. By 9 AM, my browser was consuming 4 GB of RAM and I'd already lost track of which tab had what.

I looked at paid alternatives. Coinigy runs $19/month. Altrady is $31/month. Both are cloud-based, both are closed-source, and both require you to hand over your API keys to someone else's server. For a tool that touches your exchange accounts, that felt... not great.

So I did what any reasonable developer would do: I mass over-engineered my own solution instead. πŸ› οΈ

Meet CryptoRadar

CryptoRadar is a free, open-source desktop application for Windows that consolidates the data I was manually checking across a dozen websites into a single window. It's built with Python and Qt6, runs entirely on your local machine, and doesn't phone home to anything except the exchange APIs you explicitly configure.

It's not a trading bot. It doesn't execute trades for you. It's a monitoring and analysis tool β€” think of it as a dashboard that replaces your tab graveyard.

Let me walk through what it actually does and, more importantly, why each piece exists.

The Feature Walkthrough

Spread Scanner β€” "Where's the Arbitrage?"

The core reason I built this. The spread scanner pulls real-time bid/ask prices for 16 coins across 10 CEX exchanges (Binance, Kraken, OKX, Bybit, Coinbase, KuCoin, Gate.io, MEXC, HTX, Bitget) and highlights the best buy→sell pair.

But here's the thing most basic price comparison tools get wrong: they show you a 2% spread and you think "free money!" β€” until you account for the 0.1% trading fee on each side, the withdrawal fee, and the network transfer cost. Suddenly your 2% is 0.3%. Or negative.

CryptoRadar calculates the net profit after all those costs. The number you see is the number you'd actually pocket. Auto-refreshes every 10, 30, or 60 seconds depending on how anxious you're feeling.

Gas Fee Monitor β€” "Which Network Won't Eat My Profits?"

If you're moving USDT between exchanges for an arb trade, the network you choose matters a lot. An Ethereum transfer might cost $3.50 right now but $0.80 in two hours. BSC might be $0.10. Solana might be $0.002.

The gas monitor tracks 8 networks (Ethereum, BSC, Polygon, Arbitrum, Optimism, Avalanche, Solana, TRON) in real time, shows you the actual USD cost to send a USDT transfer on each one, and recommends the cheapest option. It also shows congestion levels and a sparkline history so you can spot patterns β€” like Ethereum gas always spiking during US market hours.

Funding Rate Monitor β€” "Who's Paying Whom?"

Perpetual futures funding rates are one of those things that seem boring until you realize people make consistent money off the discrepancies. This panel tracks funding rates across Binance, Bybit, OKX, and Bitget for 12+ pairs.

The useful part: it flags anomalies when the absolute rate exceeds 0.05% or 0.1%, shows cross-exchange differentials (so you can spot funding arb opportunities), displays annualized rates for context, and counts down to the next funding payment. When ETH funding is +0.08% on Bybit but +0.01% on OKX, you want to know about that.

Portfolio Dashboard β€” "How Much Do I Actually Have?"

I trade across multiple exchanges. Checking each one individually is tedious and error-prone (I once forgot I had $400 sitting in KuCoin for three weeks). The portfolio dashboard aggregates balances from up to 10 exchanges via read-only API keys, shows a breakdown by coin and by exchange, and calculates total USD valuation.

You can export everything to CSV for your own spreadsheets or tax prep. Nothing fancy, just the data in one place.

Trade Journal β€” "Am I Actually Making Money?"

This is the feature I resisted building the longest because I didn't want to confront my actual win rate. The journal lets you log arbitrage trades (CEX↔CEX, DEX↔CEX, or funding rate plays), auto-calculates fees, and tracks stats: win rate, average profit per trade, best/worst trades, and a cumulative P&L chart.

Turns out, knowing whether your strategy actually works is kind of important. Who knew.

Smart Alerts β€” "Tell Me When It Matters"

Custom conditions that trigger Windows desktop notifications. Spread exceeds X%, gas drops below X gwei, funding rate spikes past X%, price crosses $X. There's a cooldown system so you don't get hammered with 50 notifications in a row, and an alert history log so you can review what you missed.

Clipboard Guard β€” "Did Something Just Swap My Address?"

This one sounds paranoid until you learn that clipboard-hijacking malware (clippers) is disturbingly common in crypto. The clipboard guard monitors your clipboard every 500ms, detects crypto addresses (BTC, ETH, TRON, SOL formats), and if the address suddenly changes to one you didn't copy β€” it alerts you and restores the original. You can also save trusted addresses to a personal address book.

I debated including this because it's not really a "trading" feature, but after a friend lost $2,000 to a clipper, it felt irresponsible not to. πŸ˜…

Tech Stack: Why These Choices

Python 3.11 β€” Not the fastest language, but the crypto library ecosystem is unmatched. When you need to talk to 10 different exchanges, you want the language where someone already solved that problem.

PySide6 (Qt6) β€” I needed a proper desktop GUI with tables, charts, tabs, and system tray integration. Tkinter is too limited. Electron would've ballooned the app to 300+ MB and defeated the purpose of replacing browser tabs. Qt gave me native-feeling widgets and excellent table performance for streaming data.

ccxt β€” This is the real MVP. One unified API for 100+ exchanges. Instead of writing 10 different API integrations with 10 different authentication schemes and response formats, I write one:

import ccxt

exchange = ccxt.binance({'enableRateLimit': True})
ticker = exchange.fetch_ticker('BTC/USDT')
spread = ticker['ask'] - ticker['bid']
Enter fullscreen mode Exit fullscreen mode

Same code works for Kraken, OKX, Bybit β€” just swap the class name. ccxt handles the differences under the hood.

pyqtgraph β€” For the sparkline charts and P&L graphs. Matplotlib is great for static plots but painfully slow for real-time updates. pyqtgraph is built for exactly this use case.

SQLite β€” Local database, zero setup, single file. Perfect for storing trade journal entries, alert history, and cached data. No need for PostgreSQL when your user count is exactly one.

cryptography (Fernet/AES) β€” For encrypting stored API keys. More on this in the security section.

PyInstaller β€” Bundles everything into a standalone .exe so users don't need Python installed.

Challenges & Lessons Learned

Rate limiting nearly broke me. When you're polling 10 exchanges every 10 seconds for 16 coins, you hit rate limits fast. ccxt's built-in enableRateLimit helps, but it's not enough when you're running parallel requests. I ended up implementing a per-exchange request queue with adaptive backoff:

async def fetch_with_backoff(exchange, symbol, max_retries=3):
    for attempt in range(max_retries):
        try:
            return await exchange.fetch_order_book(symbol, limit=5)
        except ccxt.RateLimitExceeded:
            wait = (2 ** attempt) + random.uniform(0, 1)
            await asyncio.sleep(wait)
    return None
Enter fullscreen mode Exit fullscreen mode

Still occasionally get 429s from MEXC. Some exchanges are more generous than others.

Qt threading is its own circle of hell. Qt's golden rule: never touch GUI elements from a background thread. Violate this and you get random segfaults with zero useful error messages. I learned this the hard way β€” twice β€” before moving all exchange API calls to QThread workers that communicate with the main thread exclusively through Qt signals:

class SpreadWorker(QThread):
    data_ready = Signal(dict)
    error_occurred = Signal(str)

    def run(self):
        results = self._fetch_all_exchanges()
        self.data_ready.emit(results)  # GUI thread picks this up safely
Enter fullscreen mode Exit fullscreen mode

API key encryption is a UX problem, not just a crypto problem. Fernet encryption is straightforward β€” the hard part is key management. Where do you store the encryption key? If it's hardcoded, it's useless. If it's derived from a user password, you need to handle password changes, forgotten passwords, and key derivation properly. I settled on PBKDF2 key derivation from a user-set master password, with the salt stored locally. It's not perfect, but it's miles better than plaintext keys in a JSON config file (which is what I had in v0.1 β€” don't judge me).

Security: Why Local-Only Matters

For a tool that handles exchange API keys and shows your portfolio balances, the security model is everything. Here's the approach:

  • 100% local execution. No cloud, no servers, no accounts. Your data never leaves your machine.
  • API keys encrypted at rest using Fernet symmetric encryption (AES-128-CBC under the hood), derived from a master password via PBKDF2.
  • Read-only API keys only. The portfolio dashboard needs "read balances" permission β€” nothing else. No trade or withdrawal permissions required.
  • Zero telemetry. No analytics, no crash reporting, no phoning home. The only outbound connections are to the exchange APIs and gas price endpoints you explicitly use.
  • Open source. You don't have to trust me. Read the code. The encryption implementation is in src/security/, the API calls are in src/exchanges/. If something looks wrong, open an issue.

This is the main reason I didn't just use a cloud-based alternative. When your API keys are on someone else's server, you're trusting their security team, their infrastructure, their employees, and their future business decisions. I'd rather trust my own laptop.

What's Next

The current version is stable and does what I need daily, but there's a lot more I want to build:

  • DEX integration β€” pull prices from Uniswap, Jupiter, PancakeSwap for DEX↔CEX arb scanning
  • Triangular arbitrage detection β€” scan for profitable three-way paths within a single exchange
  • Telegram alerts β€” push notifications to your phone when you're away from your desk
  • macOS and Linux builds β€” PySide6 is cross-platform in theory; in practice, I need to test and package properly
  • More coins and pairs β€” currently 16 coins, want to expand based on what people actually trade

I'm building this for myself first, but I'd genuinely love feedback from other traders and developers. If you spot a bug, have a feature idea, or think my architecture choices are terrible β€” please open an issue or PR. I'm one person and my blind spots are considerable.

Try It Out

GitHub: https://github.com/billy-crypto-coder/crypto-arbitrage-terminal

License: MIT β€” fork it, modify it, sell it, I don't care.

If it saves you even one browser tab, I'll consider it a success. And if you find it useful, a ⭐ on the repo would make my day β€” it helps other people find the project too.

Top comments (0)