DEV Community

maymay5692
maymay5692

Posted on

I Built My Own Crypto Signal Bot Instead of Paying $40/month — 35% Win Rate, Still Profitable

Disclaimer: This article is for educational purposes only. It does not constitute financial advice. Always do your own research and comply with your local regulations before trading.

Crypto signal services charge $40-55/month. You open Telegram, get a daily "BUY" or "SELL", and hope for the best. Ask them how the signal is generated? Silence. Ask to see the backtest data? Nothing.

I was already running a trading bot. Python, ccxt, the usual stack. One day I looked at the signals my bot was producing and thought — wait, this is a signal service. I just wasn't sending it anywhere.

So I wired it up to Telegram. Total cost: $0. Win rate: 35%. And it's profitable.

Here's how.

Why I Built This

I started with a simple EMA crossover bot trading BTC/USDT on Bitget. $33 starting capital. It worked, but I wanted to know if there was something better.

So I built a backtesting engine and ran 50 strategies against 37 months of BTC/USDT daily data (Jan 2023 – Feb 2026). Every single result — Sharpe ratio, max drawdown, win rate, trade count — is public on GitHub. No cherry-picking.

The top 3 strategies became my signal sources:

Strategy What It Does Sharpe
EMA Crossover Trades when short-term and long-term moving averages cross 1.30
Parabolic SAR Detects trend reversals 1.25
MACD Measures trend strength and direction 1.17

Each strategy independently generates BUY/SELL/HOLD. When 2 out of 3 agree, that becomes the consensus signal. Simple majority vote.

This is where it clicked. The paid signal services? They're doing the same thing — pulling data from an exchange, running it through some strategy, and pushing the result to Telegram. There's no secret sauce. The difference is whether you can see what's inside.

I wanted to see what's inside. So I built my own.

Architecture

The whole system runs on a daily cron job. No cloud, no Docker, just a Python script.

cron (daily, 00:15 UTC)
  │
  ▼
Bitget API → fetch OHLCV data (via ccxt)
  │
  ▼
3 strategies analyze independently
  │
  ▼
Majority vote → BUY / SELL / HOLD
  │
  ▼
Telegram Bot API → push to channel
Enter fullscreen mode Exit fullscreen mode

The signal module lives in src/signal/ with a clean separation:

src/signal/
├── config.py       # Environment config (frozen dataclass)
├── generator.py    # Runs all 3 strategies, produces consensus
├── formatter.py    # Formats messages (free vs premium)
├── sender.py       # Sends to Telegram (async, retries, dry_run)
├── notifier.py     # Orchestrates the flow
└── main.py         # Entry point for cron
Enter fullscreen mode Exit fullscreen mode

The free channel gets the consensus signal. The premium channel gets the full breakdown — individual strategy outputs, indicator values, circuit breaker status.

Free channel output:

🟢 BTC/USDT Signal
Consensus: BUY
Price: $96,543

Full strategy breakdown → Premium channel
Enter fullscreen mode Exit fullscreen mode

Premium channel output:

🟢 BTC/USDT Signal Alert
Consensus: BUY (2/3 strategies agree)
Price: $96,543.21

Strategy Breakdown:
  🟢 EMA Crossover: BUY
     EMA(12)=96800 / EMA(26)=96200
  🟢 MACD: BUY
     MACD=245 Signal=180
  🟡 Parabolic SAR: HOLD
     SAR=95100 trend=UP

CB Status: NORMAL
Time: 2026-03-28 00:15 UTC
Enter fullscreen mode Exit fullscreen mode

The SignalGenerator class handles the consensus logic:

def generate(self, ohlcv: pd.DataFrame) -> AggregatedSignal:
    signals = [
        self._ema_crossover(ohlcv),
        self._parabolic_sar(ohlcv),
        self._macd(ohlcv),
    ]

    buy_count = sum(1 for s in signals if s.direction == "BUY")
    sell_count = sum(1 for s in signals if s.direction == "SELL")

    if buy_count >= 2:
        consensus = "BUY"
    elif sell_count >= 2:
        consensus = "SELL"
    else:
        consensus = "HOLD"

    return AggregatedSignal(
        consensus=consensus,
        strategies=signals,
    )
Enter fullscreen mode Exit fullscreen mode

Nothing fancy. Three strategies vote, majority wins. If EMA and MACD both say BUY but SAR says HOLD, the consensus is BUY. When all three agree, that's a high-conviction signal.

Key Code: The Telegram Integration

The Telegram side is surprisingly simple. python-telegram-bot does the heavy lifting.

from telegram import Bot

class TelegramSender:
    def __init__(self, bot_token: str, channel_id: str, dry_run: bool = True):
        self._bot = Bot(token=bot_token)
        self._channel_id = channel_id
        self._dry_run = dry_run

    async def send(self, message: str) -> None:
        if self._dry_run:
            print(f"[DRY_RUN] Would send to {self._channel_id}:\n{message}")
            return

        await self._bot.send_message(
            chat_id=self._channel_id,
            text=message,
            parse_mode="Markdown",
        )
Enter fullscreen mode Exit fullscreen mode

Setting up the bot took 3 minutes:

  1. Message @BotFather on Telegram → /newbot
  2. Get the bot token
  3. Create a channel, add the bot as admin
  4. Drop the token and channel ID into .env
TELEGRAM_BOT_TOKEN=your_token_here
TELEGRAM_FREE_CHANNEL_ID=-100xxxxxxxxxx
SIGNAL_DRY_RUN=true
Enter fullscreen mode Exit fullscreen mode

I ran it with DRY_RUN=true first, confirmed the output format looked right, then flipped it to false. The whole pipeline — data fetch, strategy analysis, consensus, Telegram delivery — runs in under 10 seconds.

Honest Numbers: Why 35% Win Rate Still Works

This is the part most people get stuck on.

"35% win rate means you lose 7 out of 10 trades. How is that profitable?"

The answer: risk-reward ratio.

When the bot loses, it loses small. The stop-loss is tight. When it wins, it wins big. The take-profit is set at 3x+ the stop-loss distance.

Here's what that looks like over 10 trades:

7 losses × -$1.00  = -$7.00
3 wins   × +$3.50  = +$10.50
─────────────────────────────
Net:                  +$3.50
Enter fullscreen mode Exit fullscreen mode

This is expected value at work. Win rate alone tells you nothing. A strategy with 80% win rate can still lose money if the wins are tiny and the losses are huge. And a 35% win rate strategy can print money if the risk-reward ratio is favorable.

My backtest data across 37 months confirms this pattern holds. The EMA crossover strategy specifically returned 491% with a Sharpe ratio of 1.30 — at a 35% win rate.

But I'll be honest about the rough patches. Right after going live, I hit 8 consecutive losses. That's statistically normal for a 35% win rate system, but it doesn't feel normal when you're watching it happen. The strategy wasn't broken — it was just a ranging market. When the trend came back, so did the profits.

The mental game is the hardest part of running a low-win-rate system. You have to trust the math even when your gut says to pull the plug.

Here's the key backtest comparison:

Metric EMA Crossover Parabolic SAR MACD
Return 491% 456% 428%
Sharpe 1.30 1.25 1.17
Win Rate 35% 36% 36%
Max Drawdown -34% -37% -33%
Trades 34 94 84

None of these have a win rate above 36%. All three are profitable over 37 months. The common thread: they cut losses early and let winners run.

What's Next

The bot is live. The free Telegram channel is running. Every day, BTC/USDT consensus signals go out.

What I'm working on now:

  • Multi-pair expansion — Adding ETH/USDT and SOL/USDT to the signal feed. More pairs = more signals = more useful data for subscribers
  • Premium channel launch — Full strategy breakdowns, individual indicator values, circuit breaker status. Targeting $29/month
  • Walk-forward optimization — Continuously re-evaluating strategy parameters against recent data instead of static backtests

The whole thing is open source. You can read the strategy code, run the backtests yourself, and verify every number I've shared here.

Building this taught me something. Signal services aren't magic. They're a data pipeline with a Telegram endpoint. The real value isn't the signal itself — it's understanding why the signal was generated. When you build it yourself, you get that for free.

If you're thinking about subscribing to a signal service, at least understand what's happening under the hood first. And if you know some Python, maybe just build your own.

The win rate is 35%. The math still works.

Top comments (0)