site logo

Marico's space

我的首个加密交易 Bot 开发之旅——一个 Neo 的 Matrix 探索记

Others 2026-09-07 14:49:55 6

最近折腾了一个加密货币交易 Bot,踩了几个坑,这篇把问题说清楚。

说实话,我受够了凌晨两点盯着 K 线图,咖啡都凉透了,眼巴巴盼着有个小机器人能在我睡着的时候自动抄底。之前在 Binance 上手动交易过几次,有过赚钱的快感,也有过因为调试一个完全无关的脚本而错过突然拉升的懊恼。熟悉这种感觉吗?

那一刻——当我发现自己在刷新 Reddit 帖子的时候眼睁睁看着行情走了 5%——感觉就像 Neo 吞下了红色药丸。我想要逃离那个无休止手动点击的虚拟世界,进入一个代码能 24 小时盯盘、执行简单策略、让我腾出手来做点实际东西的新境界。

于是我踏上了征途:做一个能在便宜 VPS(虚拟专用服务器)上跑的加密交易 Bot,接入主流交易所 API(应用程序接口),实现一个基础的均值回归策略。如果这个能跑通,后面就有底气去试验更复杂的想法了。

顿悟时刻(核心认知)

最大的"原来如此"不是来自什么 exotic 算法;而是意识到难点根本不在数学——而是在管道工程。一旦你有了可靠的市场数据、干净的下单方式、扎实的错误处理,策略本身其实就是几行逻辑的事。

我最终定了三个核心模块:

  1. 数据获取 – 用 ccxt 库从 Binance 拉取 OHLCV 蜡烛图数据。
  2. 信号生成 – 一个简单的均值回归规则:如果价格跌破 20 周期 SMA(简单移动平均线) 2% 以上,做多;如果价格涨破 SMA 2% 以上,平仓。
  3. 订单执行 – 市价单加固定 USD(美元)金额,加上基本的安全检查(最小下单量、余额)。

关键在于关注点分离:获取 → 决策 → 执行。当每个模块独立工作正常后,把它们串起来的感觉就像施了个真能生效的魔法。

掌控力量(代码与示例)

挣扎期(改进前)

我的第一版是个单体脚本:获取数据、计算指标、下单、sleep 一分钟——全塞在一个 while True 循环里,零错误处理。大概长这样:

import time, ccxt exchange = ccxt.binance({'enableRateLimit': True})
symbol = 'BTC/USDT'
amount_usd = 20 while True: ohlcv = exchange.fetch_ohlcv(symbol, timeframe='1m', limit=21) closes = [c[4] for c in ohlcv] sma = sum(closes[-20:]) / 20 price = closes[-1] if price < sma * 0.98: # 2% below SMA → buy
 exchange.create_market_buy_order(symbol, amount_usd / price) elif price > sma * 1.02: # 2% above SMA → sell (close)
 # Oops! No position tracking → we might sell nothing or short!
 exchange.create_market_sell_order(symbol, amount_usd / price) time.sleep(60)

踩过的坑:

  • 没有仓位感知 – Bot 每次跌了就买,即使手里已经有 BTC 了,很快就会超出我预设的敞口。
  • 没有错误处理 – 网络抖动或者触发限速就会循环崩溃,Bot 直接哑火,直到我发现。
  • 金额写死 – 用固定 USD 金额下单,但没考虑 Binance 的最小下单量会随价格变化;有时候 Bot 想发 0.00001 BTC 的单,直接被拒绝。

胜利期(改进后)

我把脚本重构为三个明确的函数,加了一个简单的仓位追踪器,把交易所调用包进重试逻辑。改进版如下:

import time, ccxt, logging
from decimal import Decimal, ROUND_DOWN logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') exchange = ccxt.binance({ 'enableRateLimit': True, 'options': {'defaultType': 'future'} # adjust if you trade spot or futures
}) symbol = 'BTC/USDT'
timeframe = '1m'
lookback = 20 # SMA period

threshold = Decimal('0.02') # 2%

usd_per_trade = Decimal('20')
position = 0 # positive = long, negative = short, 0 = flat
 def fetch_sma(): ohlcv = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=lookback + 1) closes = [Decimal(str(c[4])) for c in ohlcv] sma = sum(closes[:-1]) / lookback # exclude the forming candle
 return sma, closes[-1] def amount_to_trade(price): # Ensure we respect the exchange's step size and min notional
 market = exchange.market(symbol) step = Decimal(str(market['precision']['amount'])) min_notional = Decimal(str(market['limits']['cost']['min'])) if market['limits']['cost']['min'] else Decimal('0') raw = (usd_per_trade / price).quantize(step, rounding=ROUND_DOWN) if raw * price < min_notional: raise ValueError(f'Order too small: {raw} {symbol.split("/")[0]} < min_notional') return raw def place_order(side, qty): try: if side == 'buy': order = exchange.create_market_buy_order(symbol, float(qty)) else: order = exchange.create_market_sell_order(symbol, float(qty)) logging.info(f'{side.upper()} order placed: {qty} {symbol.split("/")[0]} @ market') return order except Exception as e: logging.error(f'Order failed: {e}') return None def main(): global position while True: try: sma, price = fetch_sma() signal = Decimal('0') if price < sma * (Decimal('1') - threshold): signal = Decimal('1') # go long
 elif price > sma * (Decimal('1') + threshold): signal = Decimal('-1') # go flat (exit long)
 if signal == 1 and position <= 0: # enter long
 qty = amount_to_trade(price) if place_order('buy', qty): position = float(qty) elif signal == -1 and position > 0: # exit long
 qty = amount_to_trade(price) if place_order('sell', qty): position = 0 else: logging.debug(f'No action. SMA={sma:.2f}, price={price:.2f}, position={position}') except ccxt.NetworkError as e: logging.warning(f'Network issue: {e}') except ccxt.ExchangeError as e: logging.error(f'Exchange error: {e}') except Exception as e: logging.exception(f'Unexpected error: {e}') time.sleep(60) # respect rate limits; adjust as needed
 if __name__ == '__main__': main()

改动点:

  • 仓位追踪 – 只有在空仓时才开多,只有在持有多头时才平仓。不会再意外金字塔加仓了。
  • 稳健的下单量计算amount_to_trade 严格遵守交易所的步长和最小名义价值,避免那些烦人的"订单太小"拒绝。
  • 错误处理 – 网络抖动会被记录并继续运行;意外异常会被捕获并打印完整堆栈,我可以调试而不至于把 Bot 直接搞挂。
  • 清晰的模块化 – 获取数据、决策、执行是独立代码块,后面想换指标或风险模型,改起来不费劲。

跑在一个月花 35 块钱的 VPS 上,现在有了一个对睡眠友好的半自动化交易员,每次操作都有日志。已经抓到好几波我刷剧时会错过的快速回归了。

这份新能力为何重要

现在骨架搭好了,真正的乐趣才开始。我可以接入均线交叉策略、加个简单的止损,甚至试验机器学习信号——核心循环完全不用动。Bot 把交易从一个紧张刺激肾上腺素飙升的爱好,变成了一场系统性实验:我控制变量,从数据中学习。

更重要的是,它提醒我:自动化不是为了取代直觉,而是为了释放心智带宽,让我能把精力放在策略的"为什么"上,而不是纠结于怎么点按钮。如果你也曾陷在手动盯盘的循环里,不妨试试这个。也许你也会觉得自己躲过了一颗子弹——换我的情况来说,就是做早饭的时候没再错过 3% 的拉升。

轮到你了

拿上面的骨架,把 SMA 换成 RSI 阈值试试,或者加个追踪止损。先在测试网跑,观察日志,看看行情震荡时 Bot 的表现如何。你第一个想改的地方是什么?留个评论或者发条动态——咱们一起把这趟探险继续下去!

祝编码愉快,愿你的交易永远顺风顺水。 🚀