Passive Income

Building a Crypto Signal Service: From GitHub to $2k/Month

How to build and monetize a crypto signal service โ€” automated alerts, subscriber management, Telegram channels, and the business model that scales to real income.

A
AI Agents Hubยท2026-01-10ยท4 min readยท744 words

Builder of AI agents, crypto trading bots, and open-source automation tools. Sharing practical guides on how to build, deploy, and profit from AI and DeFi technology.

A crypto signal service delivers trading alerts to subscribers. The model is simple: build reliable signals, package them attractively, charge for access. Here's the full blueprint.

What a Signal Service Looks Like

Free tier (public Telegram channel):

  • 1-2 signals/week to attract subscribers
  • Educational content to build authority

Paid tier (private Telegram group, $29/month):

  • 5-10 signals/week
  • Entry, stop-loss, and take-profit levels
  • Live trade updates
  • Strategy explanation

Step 1: Build the Signal Generator

Start with one proven strategy. Don't overcomplicate it.

import ccxt
import pandas as pd
import os
from dotenv import load_dotenv

load_dotenv()

exchange = ccxt.binance({'enableRateLimit': True})

def get_signals(symbols: list[str], timeframe: str = '4h') -> list[dict]:
    """Generate trading signals for a list of symbols."""
    signals = []
    
    for symbol in symbols:
        try:
            klines = exchange.fetch_ohlcv(symbol, timeframe, limit=100)
            df = pd.DataFrame(klines, columns=['ts', 'open', 'high', 'low', 'close', 'volume'])
            
            # EMA crossover + RSI filter
            df['ema20'] = df['close'].ewm(span=20).mean()
            df['ema50'] = df['close'].ewm(span=50).mean()
            
            # RSI
            delta = df['close'].diff()
            gain = delta.clip(lower=0).rolling(14).mean()
            loss = (-delta.clip(upper=0)).rolling(14).mean()
            df['rsi'] = 100 - (100 / (1 + gain / loss))
            
            latest = df.iloc[-1]
            prev = df.iloc[-2]
            
            # Bullish crossover + RSI < 65 (not overbought)
            bullish = (latest['ema20'] > latest['ema50'] and 
                      prev['ema20'] <= prev['ema50'] and
                      latest['rsi'] < 65)
            
            # Bearish crossover + RSI > 35 (not oversold)
            bearish = (latest['ema20'] < latest['ema50'] and
                      prev['ema20'] >= prev['ema50'] and
                      latest['rsi'] > 35)
            
            if bullish:
                atr = (df['high'] - df['low']).rolling(14).mean().iloc[-1]
                signals.append({
                    'symbol': symbol,
                    'signal': 'LONG',
                    'price': latest['close'],
                    'stop_loss': latest['close'] - 2 * atr,
                    'take_profit': latest['close'] + 3 * atr,
                    'rr_ratio': '1:1.5',
                    'timeframe': timeframe,
                    'rsi': round(latest['rsi'], 1),
                })
            elif bearish:
                atr = (df['high'] - df['low']).rolling(14).mean().iloc[-1]
                signals.append({
                    'symbol': symbol,
                    'signal': 'SHORT',
                    'price': latest['close'],
                    'stop_loss': latest['close'] + 2 * atr,
                    'take_profit': latest['close'] - 3 * atr,
                    'rr_ratio': '1:1.5',
                    'timeframe': timeframe,
                    'rsi': round(latest['rsi'], 1),
                })
        except Exception as e:
            print(f"Error for {symbol}: {e}")
    
    return signals

WATCHLIST = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'AVAX/USDT', 'MATIC/USDT']
signals = get_signals(WATCHLIST, '4h')

Step 2: Format and Send Signals

import requests
import os

def format_signal(signal: dict) -> str:
    """Format a signal as a professional Telegram message."""
    direction_emoji = "๐ŸŸข๐Ÿ“ˆ" if signal['signal'] == 'LONG' else "๐Ÿ”ด๐Ÿ“‰"
    symbol_clean = signal['symbol'].replace('/USDT', '')
    
    return f"""{direction_emoji} <b>{signal['signal']} SIGNAL: {symbol_clean}</b>

๐Ÿ’ฐ Entry: ${signal['price']:,.4f}
๐Ÿ›‘ Stop Loss: ${signal['stop_loss']:,.4f}
๐ŸŽฏ Take Profit: ${signal['take_profit']:,.4f}
โšก R:R Ratio: {signal['rr_ratio']}
๐Ÿ“Š RSI: {signal['rsi']}
โฐ Timeframe: {signal['timeframe'].upper()}

<i>Always manage risk. Max 2% per trade.</i>
<i>This is not financial advice.</i>"""

def send_to_telegram(message: str, channel_id: str):
    """Send signal to Telegram channel."""
    url = f"https://api.telegram.org/bot{os.getenv('TELEGRAM_TOKEN')}/sendMessage"
    requests.post(url, json={
        "chat_id": channel_id,
        "text": message,
        "parse_mode": "HTML"
    })

# Send to free channel
for signal in signals:
    msg = format_signal(signal)
    send_to_telegram(msg, "@your_free_channel")  # Free channel
    
# Send to paid channel (with extra context)

Step 3: Subscriber Management

Use Telegram's native subscription (@BotFather โ†’ Payment) or a simple database:

import sqlite3

def setup_subscribers_db():
    with sqlite3.connect("subscribers.db") as db:
        db.execute("""
            CREATE TABLE IF NOT EXISTS subscribers (
                telegram_id INTEGER PRIMARY KEY,
                username TEXT,
                plan TEXT DEFAULT 'free',
                paid_until TEXT,
                joined_at TEXT DEFAULT CURRENT_TIMESTAMP
            )
        """)

def is_active_subscriber(telegram_id: int) -> bool:
    with sqlite3.connect("subscribers.db") as db:
        row = db.execute(
            "SELECT paid_until FROM subscribers WHERE telegram_id = ? AND plan = 'paid'",
            (telegram_id,)
        ).fetchone()
        
        if row:
            from datetime import datetime
            return datetime.now() < datetime.fromisoformat(row[0])
    return False

Step 4: Payment Integration

Use Stripe for fiat or accept crypto directly:

Stripe (easiest):

  • Recurring $29/month subscriptions
  • Automatic billing and cancellation
  • Use Stripe's webhook to grant/revoke Telegram access

Crypto payments:

  • Accept USDC/USDT to your wallet
  • Manual or automated verification via on-chain monitoring

The Business Math

Starting point: 0 subscribers

Month 1-3: Build track record publicly. Post signals to free channel. Document performance.

Month 4: Launch paid tier. With a real performance record, 5-10% of free subscribers will convert.

| Free Subscribers | Conversion | Revenue | |-----------------|-----------|---------| | 100 | 5 (5%) | $145/month | | 500 | 25 (5%) | $725/month | | 2,000 | 80 (4%) | $2,320/month | | 5,000 | 150 (3%) | $4,350/month |

Critical success factor: A verifiable track record. Document every signal with entry, stop, and result. Screenshot the channel timestamps. No one pays for signals from an unknown account with no history.

Content Beyond Signals

The best signal services also provide:

  • Weekly market analysis (builds trust and retention)
  • Educational breakdowns of why each signal was taken
  • Monthly performance reviews (transparency)
  • Discord/Telegram community (increases stickiness)

Subscribers pay for confidence that your system works. Everything you do should build that confidence.

Related Articles