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
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
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
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
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,
)
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",
)
Setting up the bot took 3 minutes:
- Message
@BotFatheron Telegram →/newbot - Get the bot token
- Create a channel, add the bot as admin
- Drop the token and channel ID into
.env
TELEGRAM_BOT_TOKEN=your_token_here
TELEGRAM_FREE_CHANNEL_ID=-100xxxxxxxxxx
SIGNAL_DRY_RUN=true
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
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
All the numbers I've shared here come from actual backtests and live trading data. No cherry-picking, no inflated metrics.
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.
Links
- Free Trading Signals — Telegram @crypto_signal_freeeee (BTC/SOL/ETH daily signals, powered by backtested strategies)
- Start Trading — Sign up on MEXC (lowest fees in the industry — signing up through this link supports the project)
Top comments (0)