跳转到内容
本页面的翻译由 LLM 生成。如果你发现翻译有误,请在 GitHub 上提交 issue 告诉我们。

创建第一个 Sai Telegram 机器人

按步骤创建查询 Sai 真实数据的 Telegram 机器人,无需机器人开发经验。


完成后,机器人可以:

  • 显示任意钱包的合约交易。
  • 显示全部代币预言机价格。
  • 搜索指定代币价格。
  • 按 BTC、ETH 等资产筛选。
  • 用下一页按钮分页。

无需数据库,全部数据直接来自 Sai GraphQL API。


开始前确认以下条件。

  • Python 3.10+:下载。
    • 在终端执行 python3 --version 检查版本。
  • 代码编辑器:
    • VS Code,适合初学者。
    • PyCharm。
    • Sublime Text。
    • 记事本也可以。
  • 终端: 需要输入一些命令。
  • Telegram 账户,用于测试。
  • 机器人 Token,从官方创建工具 BotFather 获取。
  • Python 基础,条件语句、函数、字典。
  • 终端目录切换和执行命令。
  • 无需 GraphQL 知识,指南会解释。

未安装 Python 时,从上述链接选择操作系统并安装。


第 1 部分:获取机器人 Token,5 分钟

Section titled “第 1 部分:获取机器人 Token,5 分钟”

机器人需要唯一 Token 向 Telegram 表明身份,获取方法如下。

  1. 在手机、桌面或网页打开 Telegram。
  2. 搜索 @BotFather。
  3. 开始对话。

BotFather 会回复欢迎消息和命令。

  1. 发送 /newbot。
  2. BotFather 会询问机器人名称。
    • 输入 My Sai Trading Bot 等名称,用于 Telegram 显示。
  3. 然后询问用户名,必须以 bot 结尾。
    • 输入 my_sai_trading_bot。
    • 也可用 sai_price_bot、trading_bot_sai。

BotFather 会发送类似消息。

Done! Congratulations on your new bot. You'll find it at t.me/my_sai_trading_bot.
You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your bot and it's ready to go public, ping our Bot Support if you'd like a chance to be featured on the @BotStoreBot and the Bot Store within Telegram.
Here's your token:
123456789:ABCdefGHIjklMNOpqrsTUVwxyz

Token 相当于密码,不得公开或提交到 GitHub。


打开终端,创建项目目录。

Terminal window
mkdir sai-telegram-bot
cd sai-telegram-bot

作用:

  • 创建 sai-telegram-bot。
  • 进入目录,后续在此操作。

虚拟环境将机器人依赖与其他 Python 项目隔离。

Mac、Linux:

Terminal window
python3 -m venv venv
source venv/bin/activate

Windows:

Terminal window
python -m venv venv
venv\Scripts\activate

预期结果:

  • 提示符前出现 (venv)。
  • 例如 (venv) user@computer sai-telegram-bot %。

出现 (venv) 表示已激活虚拟环境。

这是项目独立的 Python 环境,安装包不会影响其他项目。

创建所需目录和文件。

Terminal window
mkdir bot
touch bot/__init__.py
touch bot/main.py
touch bot/graphql.py
touch requirements.txt
touch .env
touch env.example

将创建:

sai-telegram-bot/
├── venv/ # Virtual environment (created automatically)
├── bot/
│ ├── __init__.py # Makes 'bot' a Python package
│ ├── main.py # Your bot's main code
│ └── graphql.py # Code to talk to Sai's API
├── requirements.txt # List of packages to install
├── .env # Your secret config (token goes here)
└── env.example # Template for .env

包为 Python 增加功能,本机器人需要以下依赖。

在编辑器创建 requirements.txt,写入以下内容。

python-telegram-bot==21.4
requests==2.32.3
python-dotenv==1.0.1
pytz==2024.1

各依赖作用:

包用途
python-telegram-bot创建 Telegram 机器人
requests向 API 发 HTTP 请求
python-dotenv从 .env 加载保密配置
pytz处理时区

确认显示 (venv),在终端执行。

Terminal window
pip install -r requirements.txt

命令下载并安装全部包,终端会输出日志,等待完成。

成功输出:

Terminal window
Successfully installed python-telegram-bot-21.4 requests-2.32.3 python-dotenv-1.0.1 pytz-2024.1

第 4 部分:配置秘密 Token,5 分钟

Section titled “第 4 部分:配置秘密 Token,5 分钟”

将 Token 存入独立文件,避免在代码中暴露。

此文件展示所需配置,不包含真实秘密值。

# Telegram Bot Configuration
# Get your bot token from @BotFather on Telegram
TELEGRAM_BOT_TOKEN=your_bot_token_here
# Sai GraphQL Endpoint (the server we get data from)
SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query

复制示例文件。

Terminal window
cp env.example .env

打开 .env,将 your_bot_token_here 替换为 BotFather 提供的真实 Token。

TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query

不得分享或提交 .env。使用 Git 时,将其加入 .gitignore。


第 5 部分:构建 GraphQL 客户端,中级

Section titled “第 5 部分:构建 GraphQL 客户端,中级”

GraphQL 客户端负责与 Sai 数据 API 通信,下面逐步说明。

GraphQL 让你向服务器请求指定数据,类似按菜单点菜。

  • REST: 请求交易的全部数据。
  • GraphQL: 只请求 ID、数量和价格,得到所需字段。

此文件处理与 Sai API 的全部通信。

import os
from typing import Any, Dict, List, Optional
import requests
# Get the GraphQL endpoint from .env file, or use default
SAI_GRAPHQL_ENDPOINT = os.environ.get(
"SAI_GRAPHQL_ENDPOINT",
"https://sai-keeper.nibiru.fi/query"
)
class SaiGQLClient:
"""
This class talks to the Sai GraphQL API.
It's like a translator between your bot and Sai's data server.
"""
def __init__(self, endpoint: Optional[str] = None) -> None:
"""
Initialize the client.
Args:
endpoint: URL of the GraphQL server (or None to use default)
"""
self.endpoint = endpoint or SAI_GRAPHQL_ENDPOINT
def query(self, query: str, variables: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""
Send a GraphQL query to the server.
Args:
query: The GraphQL query string (ask what data you want)
variables: Dynamic values to plug into the query
Returns:
Dictionary with the response data
Raises:
RuntimeError if the query fails
"""
# Send POST request (like sending a letter) to GraphQL server
resp = requests.post(
self.endpoint,
json={"query": query, "variables": variables or {}},
timeout=20, # Wait max 20 seconds for response
)
# Check if the HTTP request itself failed
resp.raise_for_status()
# Convert response from JSON format to Python dictionary
try:
payload = resp.json()
except ValueError as e:
# If response wasn't JSON, something went wrong
raise RuntimeError(
f"Invalid JSON response from {self.endpoint}: {resp.text[:200]}"
)
# Check if GraphQL returned an error
if "errors" in payload:
raise RuntimeError(str(payload["errors"]))
# Return just the "data" part of the response
return payload.get("data", {})
def fetch_trades(
self,
trader: str,
is_open: Optional[bool] = None,
limit: int = 100,
base_symbol: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
Get trades for a specific wallet address.
Args:
trader: Wallet address (e.g., "nibiru1abc123...")
is_open:
- True: only open trades
- False: only closed trades
- None: all trades
limit: Max number of trades to return
base_symbol: Filter by token (e.g., "BTC" for Bitcoin only)
Returns:
List of trade dictionaries
"""
# This is a GraphQL query (in plain English: "give me trades")
query = """
query Trades($trader: String!, $isOpen: Boolean, $limit: Int!) {
perp {
trades(
where: { trader: $trader, isOpen: $isOpen }
limit: $limit
order_by: sequence
order_desc: true
) {
id
trader
isOpen
isLong
leverage
openPrice
closePrice
openCollateralAmount
collateralAmount
openBlock { block block_ts }
closeBlock { block block_ts }
state {
positionValue
liquidationPrice
pnlCollateral
pnlPct
}
perpBorrowing {
marketId
baseToken { id name symbol }
quoteToken { id name symbol }
}
}
}
}
"""
# Send the query with the variables
data = self.query(query, {
"trader": trader,
"isOpen": is_open,
"limit": limit
})
# Extract trades from the response
trades = data.get("perp", {}).get("trades", [])
# Filter by symbol if user asked for it (e.g., BTC only)
if base_symbol:
base_symbol_upper = base_symbol.upper()
trades = [
t for t in trades
if (t.get("perpBorrowing", {}).get("baseToken", {}).get("symbol", "") or "").upper() == base_symbol_upper
]
return trades
def fetch_prices(self, limit: int = 200) -> List[Dict[str, Any]]:
"""
Get current prices for all tokens from the oracle.
Args:
limit: Max number of prices to return
Returns:
List of price dictionaries
"""
query = """
query Prices($limit: Int!) {
oracle {
tokenPricesUsd(limit: $limit, order_by: oracle_token_id) {
priceUsd
token { id name symbol }
lastUpdatedBlock { block block_ts }
}
}
}
"""
data = self.query(query, {"limit": limit})
return data.get("oracle", {}).get("tokenPricesUsd", [])
def fetch_price_by_symbol(self, symbol: str) -> Optional[Dict[str, Any]]:
"""
Get price for one specific token.
Args:
symbol: Token symbol (e.g., "BTC", "ETH")
Returns:
Price dictionary if found, None if not
"""
prices = self.fetch_prices(limit=200)
symbol_upper = symbol.upper()
for price in prices:
token = price.get("token") or {}
if (token.get("symbol") or "").upper() == symbol_upper:
return price
return None

核心概念:

  • class SaiGQLClient::将相关函数组织起来的类。
  • def query()::向 API 发送查询。
  • fetch_trades()::获取钱包交易。
  • fetch_prices()::获取价格。
  • $trader 等变量:填入真实值的占位符。

第 6 部分:机器人主逻辑,bot/main.py

Section titled “第 6 部分:机器人主逻辑,bot/main.py”

分步实现机器人功能。

创建 bot/__init__.py,可以为空。

# This file makes 'bot' a Python package

创建 bot/main.py,从导入和设置开始。

import os
import logging
from typing import Optional
from dotenv import load_dotenv
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
Application,
CommandHandler,
CallbackQueryHandler,
ContextTypes,
)
from .graphql import SaiGQLClient
# Set up logging (so you can see what your bot is doing)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

作用:

  • 导入已安装的库。
  • 设置调试日志。
  • 导入 GraphQL 客户端。

添加到 bot/main.py。

def format_trade_open(trade: dict) -> str:
"""
Format an open trade for display in Telegram.
Shows all the important info: position value, profit/loss, etc.
"""
trade_id = trade.get('id', '?')
is_long = trade.get('isLong', False)
side = "🟢 LONG" if is_long else "🔴 SHORT"
# Extract market info (what's being traded)
market = trade.get("perpBorrowing", {})
base_token = market.get("baseToken") or {}
quote_token = market.get("quoteToken") or {}
base = base_token.get("symbol") or base_token.get("name") or "?"
quote = quote_token.get("symbol") or quote_token.get("name") or "?"
# Extract trade details
leverage = trade.get('leverage')
entry_price = trade.get('openPrice')
open_collateral = trade.get('openCollateralAmount')
# Extract state info (for open trades)
state = trade.get('state') or {}
position_value = state.get('positionValue') # How much the position is worth
liquidation_price = state.get('liquidationPrice') # When you get liquidated
pnl = state.get('pnlCollateral') # Profit/Loss in dollars
pnl_pct = state.get('pnlPct') # Profit/Loss in percentage
open_ts = trade.get("openBlock", {}).get("block_ts")
# Build the message
lines = [
f"━━━━━━━━━━━━━━━━",
f"Trade #{trade_id} ✅ OPEN",
f"{base}/{quote} | {side} | {leverage}x leverage",
f"Entry Price: {entry_price}",
]
if liquidation_price:
lines.append(f"Liquidation Price: {liquidation_price}")
# Convert from micro units to dollars
# (API stores values * 1,000,000, we need to divide back)
if position_value is not None:
position_value_usd = position_value / (10 ** 6)
lines.append(f"Position Value: ${position_value_usd:,.2f}")
if pnl is not None:
pnl_usd = pnl / (10 ** 6)
pnl_sign = "+" if pnl_usd >= 0 else ""
lines.append(f"PnL: {pnl_sign}${pnl_usd:,.2f}")
if pnl_pct is not None:
pnl_sign = "+" if pnl_pct >= 0 else ""
lines.append(f"PnL %: {pnl_sign}{pnl_pct:.2f}%")
if open_collateral:
collateral_usd = open_collateral / (10 ** 6)
lines.append(f"Collateral: ${collateral_usd:,.2f}")
if open_ts:
lines.append(f"Opened: {open_ts}")
return "\n".join(lines)
def format_trade_closed(trade: dict) -> str:
"""
Format a closed trade for display.
Shows entry/exit prices and when it was opened/closed.
"""
trade_id = trade.get('id', '?')
is_long = trade.get('isLong', False)
side = "🟢 LONG" if is_long else "🔴 SHORT"
market = trade.get("perpBorrowing", {})
base_token = market.get("baseToken") or {}
quote_token = market.get("quoteToken") or {}
base = base_token.get("symbol") or base_token.get("name") or "?"
quote = quote_token.get("symbol") or quote_token.get("name") or "?"
leverage = trade.get('leverage')
entry_price = trade.get('openPrice')
exit_price = trade.get('closePrice')
open_ts = trade.get("openBlock", {}).get("block_ts")
close_ts = trade.get("closeBlock", {}).get("block_ts")
lines = [
f"━━━━━━━━━━━━━━━━",
f"Trade #{trade_id} ❌ CLOSED",
f"{base}/{quote} | {side} | {leverage}x leverage",
f"Entry Price: {entry_price}",
]
if exit_price:
lines.append(f"Exit Price: {exit_price}")
if open_ts:
lines.append(f"Opened: {open_ts}")
if close_ts:
lines.append(f"Closed: {close_ts}")
return "\n".join(lines)

作用:

  • format_trade_open():显示未平仓交易和盈亏。
  • format_trade_closed():显示已平仓的入场、退出价格。
  • 除以 10^6,将 API 微单位转换为普通美元。

添加到 bot/main.py。

# Popular tokens to show first
POPULAR_TOKENS = ["BTC", "ETH", "USDT", "USDC", "NIBI", "ATOM", "SOL"]
def format_prices(prices: list, start_idx: int = 0, page_size: int = 10) -> tuple:
"""
Format prices for display with pagination (Next buttons).
Args:
prices: List of all prices
start_idx: Which index to start at
page_size: How many prices per page
Returns:
(formatted_text, has_more_pages)
"""
if not prices:
return "No prices found.", False
# Sort prices: popular tokens first, then by ID
def sort_key(p):
token = p.get("token") or {}
symbol = (token.get("symbol") or "").upper()
if symbol in POPULAR_TOKENS:
return (0, POPULAR_TOKENS.index(symbol))
return (1, token.get("id", 9999))
sorted_prices = sorted(prices, key=sort_key)
# Get just this page of prices
end_idx = min(start_idx + page_size, len(sorted_prices))
page_prices = sorted_prices[start_idx:end_idx]
has_more = end_idx < len(sorted_prices)
# Format as text
msg_lines = [f"💰 Oracle Prices ({end_idx} of {len(sorted_prices)})\n"]
for p in page_prices:
token = p.get("token") or {}
symbol = token.get("symbol") or token.get("name") or "Unknown"
price = p.get("priceUsd")
if price is not None:
price_str = f"${price:,.2f}" if price >= 1 else f"${price:.8f}"
msg_lines.append(f"• {symbol}: {price_str}")
return "\n".join(msg_lines), has_more
def format_price_single(price_data: dict) -> str:
"""Format a single price for display."""
token = price_data.get("token") or {}
symbol = token.get("symbol") or token.get("name") or "?"
price = price_data.get("priceUsd")
if price is None:
return f"Price not available for {symbol}"
price_str = f"${price:,.2f}" if price >= 1 else f"${price:.8f}"
return f"💰 {symbol}: {price_str}"

作用:

  • format_prices():多价格格式化和分页。
  • format_price_single():格式化单个价格。
  • BTC、ETH 等热门资产优先。

添加到 bot/main.py。

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /start command - shows welcome message."""
text = (
"👋 Welcome to Sai Bot!\n\n"
"Available commands:\n"
"/trades <address> - View trades for a wallet\n"
"/prices - Show all token prices\n"
"/price <symbol> - Get price for one token\n"
"/help - Show this message\n\n"
"Examples:\n"
"/trades nibiru1abc123def456\n"
"/price BTC\n"
"/prices"
)
await update.effective_message.reply_text(text)
async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /help command."""
await start(update, context)

作用:

  • /start 显示欢迎消息。
  • /help 显示同一消息。

添加到 bot/main.py。

async def trades_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
Handle /trades command.
Usage:
/trades <address>
/trades <address> open
/trades <address> closed
/trades <address> btc
"""
if not context.args:
await update.effective_message.reply_text(
"Usage: /trades <wallet_address> [open|closed] [symbol]\n\n"
"Examples:\n"
"/trades nibiru1abc123...\n"
"/trades nibiru1abc123... open\n"
"/trades nibiru1abc123... btc"
)
return
address = context.args[0].strip()
is_open = None # None = all trades
symbol = None
# Parse additional arguments
for arg in context.args[1:]:
arg_lower = arg.strip().lower()
if arg_lower == "open":
is_open = True
elif arg_lower == "closed":
is_open = False
else:
symbol = arg.strip().upper()
try:
# Get trades from Sai
gql = SaiGQLClient()
trades = gql.fetch_trades(
trader=address,
is_open=is_open,
limit=100,
base_symbol=symbol
)
if not trades:
await update.effective_message.reply_text(
f"❌ No trades found for {address}"
)
return
# Separate open and closed trades
open_trades = [t for t in trades if t.get('isOpen')]
closed_trades = [t for t in trades if not t.get('isOpen')]
# Show open trades first
if open_trades:
for i, trade in enumerate(open_trades[:5]): # Show first 5
await update.effective_message.reply_text(
format_trade_open(trade)
)
# Show closed trades
if closed_trades:
for i, trade in enumerate(closed_trades[:5]): # Show first 5
await update.effective_message.reply_text(
format_trade_closed(trade)
)
except Exception as e:
logger.error(f"Error in trades_cmd: {e}")
await update.effective_message.reply_text(
f"❌ Error: {str(e)}"
)

添加到 bot/main.py。

async def prices_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /prices command - show all prices."""
try:
gql = SaiGQLClient()
prices = gql.fetch_prices(limit=200)
if not prices:
await update.effective_message.reply_text("No prices found.")
return
# Get first page
text, has_more = format_prices(prices, start_idx=0, page_size=10)
# Add keyboard with "Next" button if there are more prices
reply_markup = None
if has_more:
keyboard = [[InlineKeyboardButton("Next →", callback_data="prices_next")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.effective_message.reply_text(text, reply_markup=reply_markup)
except Exception as e:
logger.error(f"Error in prices_cmd: {e}")
await update.effective_message.reply_text(f"❌ Error: {str(e)}")
async def price_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /price <symbol> command - show single price."""
if not context.args:
await update.effective_message.reply_text(
"Usage: /price <symbol>\n\n"
"Examples: /price BTC, /price ETH"
)
return
symbol = context.args[0].strip()
try:
gql = SaiGQLClient()
price_data = gql.fetch_price_by_symbol(symbol)
if not price_data:
await update.effective_message.reply_text(
f"❌ Token '{symbol}' not found"
)
return
text = format_price_single(price_data)
await update.effective_message.reply_text(text)
except Exception as e:
logger.error(f"Error in price_cmd: {e}")
await update.effective_message.reply_text(f"❌ Error: {str(e)}")

添加到 bot/main.py。

async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle button clicks (pagination buttons)."""
query = update.callback_query
if not query:
return
# Acknowledge the button click
await query.answer()
# Handle "Next" button for prices
if query.data == "prices_next":
page = context.user_data.get("prices_page", 0) + 1
context.user_data["prices_page"] = page
try:
gql = SaiGQLClient()
prices = gql.fetch_prices(limit=200)
text, has_more = format_prices(prices, start_idx=page * 10, page_size=10)
reply_markup = None
if has_more:
keyboard = [[InlineKeyboardButton("Next →", callback_data="prices_next")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(text, reply_markup=reply_markup)
except Exception as e:
await query.edit_message_text(f"❌ Error: {str(e)}")

添加到 bot/main.py 末尾。

def main() -> None:
"""Main function - creates and starts the bot."""
# Load environment variables from .env file
load_dotenv()
# Get bot token
token = os.environ.get("TELEGRAM_BOT_TOKEN")
if not token:
raise RuntimeError("❌ TELEGRAM_BOT_TOKEN not found in .env!")
# Create the bot application
app = Application.builder().token(token).build()
# Register command handlers
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("help", help_cmd))
app.add_handler(CommandHandler("trades", trades_cmd))
app.add_handler(CommandHandler("prices", prices_cmd))
app.add_handler(CommandHandler("price", price_cmd))
# Register button click handler
app.add_handler(CallbackQueryHandler(callback_handler))
# Start the bot
logger.info("🚀 Bot is starting...")
app.run_polling()
if __name__ == "__main__":
main()

确认位于项目目录且虚拟环境已激活。

Terminal window
# You should see (venv) at the start of your terminal prompt
# If not, activate it:
# Mac/Linux:
source venv/bin/activate
# Windows:
venv\Scripts\activate
Terminal window
python -m bot.main

应看到以下输出。

INFO:__main__:🚀 Bot is starting...

机器人已运行,请保持终端打开。

  1. 打开 Telegram。
  2. 搜索创建的用户名。
  3. 发送 /start。
  4. 确认欢迎消息。

尝试以下命令。

命令作用
/start欢迎消息
/help帮助
/prices全部价格
/price BTCBTC 价格
/trades nibiru1abc123...交易,需真实钱包地址

测试后,在终端按 Ctrl+C 停止。


解决方法:

  1. 确认项目根目录存在 .env。
  2. 确认有 Token。
  3. 不留空格:TELEGRAM_BOT_TOKEN=123456789:ABC...。

解决方法:

  1. 确认仍在运行,终端应有 🚀 Bot is starting...。
  2. 先尝试 /start。
  3. 检查终端错误。

ModuleNotFoundError: No module named ‘bot’

Section titled “ModuleNotFoundError: No module named ‘bot’”

解决方法:

Terminal window
# Make sure you're in the project root directory
cd /path/to/sai-telegram-bot
# Make sure you activated the virtual environment
source venv/bin/activate # Mac/Linux
# or
venv\Scripts\activate # Windows
# Run with -m flag
python -m bot.main

解决方法:

  1. 检查网络。

  2. 验证 .env 的端点。

    SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query
  3. 在浏览器打开 URL 手动测试。


用户使用机器人时的流程如下。

1. User sends: /prices
↓
2. Telegram receives it and forwards to your bot
↓
3. prices_cmd() function is called
↓
4. SaiGQLClient queries the Sai GraphQL API
↓
5. API returns price data
↓
6. format_prices() formats the data beautifully
↓
7. Bot sends the message back to the user
↓
8. User sees prices with a "Next" button
↓
9. User clicks "Next"
↓
10. callback_handler() fetches the next page
↓
11. User sees next page of prices

运行后,可继续添加功能。

  • 价格提醒,如 BTC 达到 $100,000 时通知。
  • 钱包监控,如指定地址开仓时通知。
  • 增加市场和数据源。
  • 在 AWS、DigitalOcean 等服务器全天运行。
  • 用 systemd 或 Docker 保持运行。

第一个 Telegram 机器人已支持:

  • 连接 Sai。
  • 查询实时数据。
  • 显示价格和交易。
  • 分页。
  • 按资产筛选。

你已掌握机器人开发的基础。


问:可以分享给别人吗? 答:可以,在 Telegram 搜索用户名并点击 Start 即可。

问:关闭电脑后还会运行吗? 答:不会。全天运行需部署服务器,见生产部署部分。

问:如何更新功能? 答:编辑 Python 文件,Ctrl+C 停止,再运行 python -m bot.main。

问:可以分享 Token 吗? 答:不可以。视为密码,保存在 .env,不要提交 GitHub。


继续尝试开发吧。