Bitfinex lending automation 指的是在 Bitfinex 的 P2P 借贷市场中,用程序自动完成资金借出、利率调整、到期续借等一系列操作。这类工具最常见的部署方式是放到云服务器上保持长期运行,但如果你管理的资金规模不大、策略也不复杂,完全可以把它运行在自己的 PC 上,而不是租一台服务器。这篇文章会从 Bitfinex 借贷市场的基本机制讲起,逐步完成环境准备、最小脚本实现、参数调整、运行验证和问题排查,最后给出本地稳定运行的一整套工程建议。
这套方案的核心特点是:脚本和 API Key 都留在本地设备上,不依赖远程机器;通过计划任务或开机自启保持进程可用;用完整的日志和状态文件保证重启后不误操作。适合已经掌握 Python 基础、想用自己电脑管理小额闲置资金的开发者,也适合想理解“本地化交易自动化”设计思路的读者。
1. 先想清楚:自动化在 Bitfinex 借贷市场里到底能做什么
做技术实现之前,需要先理解业务场景。Bitfinex 的借贷市场不只适用于机构,普通用户也可以把持有的资产放出去,获得资金使用费。自动化要做的不是“预测行情”,而是把重复操作从人工步骤变成程序步骤。
1.1 借贷市场的最小概念
在 Bitfinex 上,借贷市场是一个资金供求双方直接撮合的市场。需求方通常是想加杠杆的交易者,供给方则是你这样的资金提供者。你可以挂出一笔资金订单,指定金额、期限和期望利率;如果有交易者愿意接受,这笔订单就会成交,资金进入借贷关系,到期后按约定利率返还。
一个借贷挂单通常需要四个核心字段:
| 字段 | 作用 | 示例 |
|---|---|---|
| 币种 | 借出哪种资产 | USD、BTC、ETH 等 |
| 金额 | 借出多少数量 | 1000 |
| 期限 | 借出天数 | 30 天 |
| 利率 | 期望获得的资金费率 | 0.0001 |
这看起来和限价挂单很像。因此自动化程序的首要任务,就是替用户完成“看余额、定价、挂单”这一步。之后还要处理更麻烦的问题:订单没成交怎么办、到期后要不要重新挂单、程序重启后会不会重复下单。
1.2 自动化的核心能力与边界
一个完整的本地出借自动化程序,至少应该包含以下几个模块:
- 读取钱包余额,找出可借出的资产数量。
- 获取当前市场行情,或者根据配置的固定利率生成挂单价格。
- 提交借贷挂单。
- 记录本地订单状态,避免重复提交。
- 定期检查未成交订单和已成交订单。
- 出错时写日志,并在下一轮自动重试。
自动化能提升的是执行效率和纪律性。它可以做到每 60 秒检查一次余额,也可以做到在挂单成交后自动把到期资金重新投入市场。但它不能保证收益。市场利率可能下降,订单可能长时间不成交,交易者可能提前偿还,这些都属于正常市场波动。任何宣传“自动出借稳赚不赔”的方案都不可信。
注意:所有自动化策略本质上都是把人的决策规则翻译成代码,所以策略本身的漏洞也会被代码放大。首次运行务必使用小额资金。
1.3 PC 与服务器:成本、隔离、维护三个维度
标题强调运行在 PC 而不是服务器,这背后是三类工程取舍。
第一是成本。云服务器按年付费,即便是低配实例,也需要维护公网 IP、系统补丁和监控告警。如果账户资金规模很小,服务器租金会吃掉不少收益。PC 的最大优势是边际成本为零,毕竟电脑平时也在使用。
第二是安全隔离。API Key 放在服务器上,意味着 Key 的物理位置在第三方机房。服务器一旦被入侵,脚本配置和密钥可能一起泄露。运行在个人电脑上时,Key 只存在于自己的设备上,泄露面更小。代价是电脑本身也需要做好系统安全、锁屏和防病毒。
第三是稳定性。服务器最大的优势是稳定,机房电力、网络和散热都比家庭环境可靠。PC 可能被关机、睡眠、断网、断电,甚至因为系统更新而重启。这不是“本地方案比服务器方案更好”,而是“本地方案是否可用取决于你有没有能力补上稳定性短板”。
| 维度 | PC 本地运行 | 云服务器运行 |
|---|---|---|
| 成本 | 低,复用日常电脑 | 持续产生租用费用 |
| 密钥位置 | 本地设备 | 远端机器,暴露面更大 |
| 持续运行 | 受关机、睡眠、断电影响 | 通常可长期运行 |
| 网络稳定性 | 家庭宽带、Wi-Fi 波动 | 数据中心网络更稳定 |
| 维护方式 | 手动开机、手动更新 | 需要远程登录配置 |
| 适合场景 | 小规模、低频策略 | 大规模、高可用要求 |
2. 本地运行环境用什么组合最省事
实现本地自动化不需要复杂的分布式架构。一个 Python 脚本、一个配置文件、一个日志目录,足以跑起最小闭环。但环境准备阶段有三个地方容易踩坑:版本选择、API Key 权限、配置文件组织。
2.1 运行环境与版本选择
推荐使用 Python 3.9 以上版本,主要原因是requests、python-dotenv等依赖在较新版本中维护良好,类型注解和dataclass也能让脚本更清晰。操作系统可以是 Windows 10/11、macOS 或 Linux 桌面版,只要支持 Python 即可。
先创建项目目录和虚拟环境:
mkdir bitfinex-lending-local cd bitfinex-lending-local python -m venv .venvWindows 下激活虚拟环境:
.venv\Scripts\activatemacOS 或 Linux 下激活虚拟环境:
source .venv/bin/activate然后安装依赖:
pip install requests python-dotenvrequests负责 HTTP 请求,python-dotenv负责读取本地环境变量。由于脚本会长期运行,建议把依赖清单固定到requirements.txt,方便以后在一台新电脑上还原环境。
2.2 在 Bitfinex 后台申请 API Key
在 Bitfinex 官方网站登录账户后,进入 API Keys 页面创建新 Key。创建过程中需要选择权限,这里有一个非常重要的原则:只勾选自动化真正需要的权限,不要勾选提现权限。
推荐权限组合:
| 权限项 | 是否需要 | 原因 |
|---|---|---|
| 读取钱包余额 | 需要 | 获取可用资金 |
| 读取订单 | 需要 | 检查已有挂单 |
| 提交资金订单 | 需要 | 实现自动出借 |
| 取消资金订单 | 建议 | 处理撤单重挂场景 |
| 提现 | 不需要 | 防止脚本或恶意代码动资产 |
创建完成后,页面只会完整显示一次 API Secret。务必把它保存到本地密码管理器或.env文件中。如果 Secret 丢失,只能重新生成 Key。
2.3 项目目录与配置管理
为了让脚本容易维护,建议按下面的结构组织项目:
bitfinex-lending-local/ ├─ .env ├─ .gitignore ├─ requirements.txt ├─ config.json ├─ bitfinex_client.py ├─ lending_bot.py ├─ main.py └─ logs/.env保存敏感信息,内容如下:
BITFINEX_API_KEY=你的API_KEY BITFINEX_API_SECRET=你的API_SECRETconfig.json保存策略参数:
{ "currency": "USD", "rate": 0.0001, "period": 30, "amount_ratio": 0.9, "check_interval_seconds": 60, "dry_run": true }.gitignore至少要忽略.env、logs/和__pycache__/,避免密钥被提交到公开仓库:
.env logs/ __pycache__/把密钥和策略分离是最基础的安全习惯。.env只负责密钥,config.json只负责策略参数,代码不硬编码任何账户信息。
3. 用 Python 把最小出借流程跑起来
环境准备好之后,就可以写核心代码。这一节给出的代码是结构示意,重点在于展示“签名请求、读取余额、提交挂单、主循环”四个环节如何串联。具体 API 端点和字段的准确写法,落地前一定要对照 Bitfinex 官方 API 文档确认。
3.1 封装 Bitfinex 私有 API 签名请求
Bitfinex 的私有接口需要签名认证。客户端类的主要职责是构造请求头,并把业务接口的调用封装成简单方法。
import hashlib import hmac import json import time from urllib.parse import urljoin import requests API_BASE = "https://api.bitfinex.com" class BitfinexAuthClient: def __init__(self, api_key: str, api_secret: str): self.api_key = api_key self.api_secret = api_secret def _signed_headers(self, path: str, body: dict): nonce = str(int(time.time() * 1000)) body_json = json.dumps(body, separators=(",", ":")) # 注意:Bitfinex 的签名拼接方式在 v1/v2 中并不完全相同。 # 示例使用常见的 v2 私有接口签名结构,实际接入以官方文档为准。 signing_string = "/api" + path + nonce + body_json signature = hmac.new( self.api_secret.encode("utf-8"), signing_string.encode("utf-8"), hashlib.sha384, ).hexdigest() return { "Content-Type": "application/json", "bfx-nonce": nonce, "bfx-apikey": self.api_key, "bfx-signature": signature, } def post(self, path: str, body: dict): headers = self._signed_headers(path, body) url = urljoin(API_BASE, path) resp = requests.post(url, headers=headers, json=body, timeout=30) resp.raise_for_status() return resp.json()这里的签名核心是nonce。nonce必须保证单调递增,通常用毫秒时间戳生成。如果同一毫秒内有多个请求,或者本地系统时间回拨,接口可能拒绝请求。实际项目里可以考虑用自增序列号或加锁保证nonce不重复。
3.2 读取可用余额
读取余额的私有接口可能返回钱包数组,每个钱包包含账户类型、币种、总余额、可用余额等字段。下面代码按“数组字段顺序”方式读取,是因为部分版本接口返回结构更接近数组而非对象。
def get_funding_available(client: BitfinexAuthClient, currency: str) -> float: path = "/v2/auth/r/wallets" wallets = client.post(path, {}) for wallet in wallets: # 常见字段顺序:type, currency, balance, available wallet_type = wallet[0] wallet_currency = wallet[1] if wallet_type == "funding" and wallet_currency == currency.upper(): return float(wallet[3]) return 0.0这段代码只关心funding类型钱包,因为出借资金通常来自资金账户。如果你的币种在exchange钱包里,需要先手动转入funding钱包,或者在程序里增加转账逻辑。第一次实现时建议手动转账,减少程序复杂度。
3.3 提交借贷挂单
提交挂单时需要指定币种、数量、利率和期限。下面代码演示了一个最小提交函数。
def submit_funding_offer( client: BitfinexAuthClient, currency: str, amount: float, rate: float, period: int, ): path = "/v2/auth/w/funding/offer/submit" body = { "type": "FRAME", "symbol": f"f{currency.upper()}", "amount": str(amount), "rate": str(rate), "period": period, } return client.post(path, body)这里的rate单位需要以官方文档为准。有的接口要求十进制小数,有的要求基于百分比扩展后的整数;amount也可能要求字符串类型。如果接口返回参数错误,优先检查这两个字段的格式。
3.4 用主循环把流程串起来
主循环负责周期性执行“查余额、算利率、提交挂单”。为了防止程序在运行多个轮次后重复挂单,必须在提交前检查当前是否已经有未成交的借贷挂单。
import logging import time logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", ) logger = logging.getLogger("lending") class LendingBot: def __init__(self, client: BitfinexAuthClient, config: dict): self.client = client self.config = config def has_open_offer(self, currency: str) -> bool: # 读取当前未成交资金订单,这里省略具体接口调用和字段解析 # 建议实现:调用查询资金订单接口,返回列表中如果存在相同币种订单则返回 True return False def run_once(self): currency = self.config["currency"] available = get_funding_available(self.client, currency) if available <= 0: logger.info("no available balance for %s", currency) return if self.has_open_offer(currency): logger.info("open offer already exists, skip submit") return amount = round(available * self.config["amount_ratio"], 4) rate = self.config["rate"] if self.config.get("dry_run", False): logger.info("dry-run: would submit offer amount=%s rate=%s", amount, rate) return result = submit_funding_offer( self.client, currency=currency, amount=amount, rate=rate, period=self.config["period"], ) logger.info("offer submitted: %s", result) def loop(self): while True: try: self.run_once() except Exception: logger.exception("run_once failed") time.sleep(self.config["check_interval_seconds"])run_once设计成单轮执行,好处是方便测试。loop只负责循环调用和异常兜底。最关键的防御逻辑是has_open_offer,这一项不实现,脚本重启后很可能重复挂单。
4. 参数和状态管理:自动化脚本最容易错的地方
代码能跑通只是第一步。参数设计不合理、请求频率过高、状态记录缺失,才是在真实运行中最常见的失败原因。
4.1 利率参数要防止“挂不出去”和“收益被吃”
利率是自动化策略里风险最大的参数。如果利率设置过高,订单可能长期不成交;如果设置过低,虽然容易成交,但资金使用收益会被压缩。更危险的是,如果对rate的理解有误,可能提交一个远高于市场价格的订单,造成不必要的资金占用。
| 参数 | 含义 | 初学建议 | 设置过大的表现 | 设置过小的表现 |
|---|---|---|---|---|
| rate | 期望利率 | 先使用市场参考利率 | 订单很难成交 | 成交快但收益偏低 |
| period | 借贷期限 | 30 天 | 资金长期锁定 | 到期频繁,需要重新挂单 |
| amount_ratio | 可用资金使用比例 | 0.9 | 可能没有预留手续费 | 资金利用率下降 |
不要直接写死一个利率就上线。更稳妥的做法是:第一周用 dry-run 模式记录“如果当时挂单,会不会成交”,再把历史市场利率拉出来对照。
4.2 轮询间隔和接口频控
本地脚本和服务器脚本一样,也会触发交易所的频率限制。如果check_interval_seconds设置成 1 秒,脚本每秒钟都去查询钱包和未成交订单,很容易被接口限流。
推荐策略:
- 查询类请求间隔不低于 30 秒。
- 提交、撤单等写操作间隔不低于 10 秒。
- 如果接口返回
429或Too Many Requests,立即增加重试等待时间。 - 不要让多个实例同时运行。
频控问题一旦触发,日志里往往不会直接提示“你没有权限”,而是出现请求超时或状态码 429。排查时要优先看日志里的时间间隔,而不是只盯着业务结果。
4.3 状态记录:重启之后不重复挂单
脚本重启是常态。手动更新代码、电脑重启、进程崩溃后重新拉起,这些情况都会发生。如果状态只保存在内存里,重启后脚本就“失忆”了。
推荐用本地 JSON 文件记录已提交订单:
{ "currency": "USD", "last_offer_id": 123456789, "open_offers": [ { "offer_id": 123456789, "amount": "900.0", "rate": "0.0001", "period": 30, "submit_time": "2025-01-01T10:00:00Z" } ] }每次提交前读取这个文件。如果发现存在相同币种的open_offers,就不重复提交,而是先查询交易所那边的真实状态。只有确认未成交订单已经不存在或已经取消时,才提交新订单。
注意:本地记录只是辅助手段,永远以交易所接口返回的订单状态为准。两者不一致时,要优先信任交易所的数据,并修正本地记录。
5. 从小额到稳定运行:怎么验证和排查
很多人写好脚本后直接设置成大额资金运行,这是最危险的做法。正确顺序是先只读验证,再用小额走通订单,最后观察几天再放大金额。
5.1 先做只读检查
在config.json中把dry_run设置为true,脚本不会真正提交订单,只输出逻辑结果。运行命令:
python main.py --dry-run预期输出类似这样,具体字段会随脚本实现不同:
2025-06-01 10:00:00 INFO no available balance for USD或者:
2025-06-01 10:00:00 INFO dry-run: would submit offer amount=900.0 rate=0.0001如果在这里已经看到异常,比如 API Key 权限不足、余额读取为 0,就不要继续往下走,先把问题解决掉。
5.2 用最小金额走通真实订单
把dry_run改为false,并把钱包里的资金换成最小测试数量。假设你准备借出 USD,可以先转 10 USD 到funding钱包,然后运行脚本。
验证点包括:
- 日志中是否出现
offer submitted,并且返回了订单 ID。 - Bitfinex 页面或 API 查询中是否能看到这笔挂单。
- 挂单的状态是
ACTIVE还是立即成交。 - 脚本下一次轮询时,
has_open_offer是否能正确识别已有订单,避免重复提交。
这一阶段不要使用全部余额,也不要使用高利率。目标是验证 API 调用链路、字段格式和状态判断逻辑全部正确。
5.3 观察日志和恢复能力
小额真实订单跑通后,建议连续运行 3 到 7 天,重点观察以下场景:
- 电脑睡眠或锁屏后,脚本是否继续运行。
- 网络断线恢复后,脚本能否自动重试。
- 接口返回错误时,日志是否足够定位问题。
- 订单到期后,脚本是否会重新挂单。
如果三天内没有出现重复挂单、异常退出和频控问题,再考虑逐步增加金额。每次增加金额后,也要再观察至少一天。
6. 本地运行的排错地图
本地运行最大的问题不是代码逻辑复杂,而是环境变化太多:系统时间不对、家庭网络波动、电脑睡眠、API Key 权限配置错误。以下是一张可以直接对照的排错表。
| 问题现象 | 常见原因 | 检查方式 | 解决方案 |
|---|---|---|---|
| 请求返回认证失败 | 本地系统时间不同步、nonce 重复 | 查看系统时间;检查签名串生成逻辑 | 开启自动时间同步;同一毫秒内加锁或用递增序列号 |
| 返回 permission denied | API Key 缺少资金订单权限 | 登录 Bitfinex 后台查看 Key 权限 | 重新生成 Key,只勾选必要权限 |
| 返回 invalid symbol 或 invalid amount | 币种符号或字段格式错误 | 打印请求 body;对照官方文档字段示例 | 修正 symbol 前缀和 amount 的字符串格式 |
| 请求超时或 429 | 家庭网络不稳定或轮询太频繁 | 观察日志中的请求间隔 | 增加check_interval_seconds;加入指数退避重试 |
| 脚本运行几小时后退出 | 电脑睡眠或电源管理关闭了进程 | 查看系统事件日志和进程日志 | 关闭休眠;使用任务计划程序配置开机自启 |
| 重复挂单 | 状态记录没有持久化或逻辑失效 | 查看本地 JSON 和交易所订单列表 | 实现has_open_offer检查;以交易所状态为准 |
| 余额一直为 0 | 资金在 exchange 钱包而不是 funding 钱包 | 登录页面检查钱包类型 | 手动把资金转入 funding 钱包 |
更复杂的“崩溃后无法恢复”问题,建议遵循下面这个排查顺序:
- 确认输入是否正确。检查
config.json里的币种、利率、金额比例。 - 确认文件路径和命名是否正确。确保
.env位于脚本启动目录下。 - 确认依赖版本是否匹配。
requests版本过低或 Python 版本过高都可能出现兼容问题。 - 确认配置是否生效。
dry_run修改后是否重启了脚本。 - 确认权限、端口、网络、环境变量。家庭网络是否允许访问交易所 API。
- 查看日志中的明确异常。不要只看最后几行,要查看异常发生前的上下文。
- 确认是脚本问题还是交易所接口限制。对比官方 API 文档中的字段和状态码。
7. 本地部署的工程化建议:自启、日志、安全和回滚
当脚本从“临时测试”变成“长期运行”后,就需要像对待生产环境一样对待这台 PC。下面几条建议可以明显提高稳定性。
7.1 开机自启和禁止睡眠
笔记本如果合上盖子,脚本就会暂停。建议在运行期间把电源计划设置为“从不睡眠”,至少在使用自动化脚本期间要这样做。
Windows 下可以用“任务计划程序”创建开机自启动任务,触发条件选择“计算机启动时”,操作指向虚拟环境中的 Python 和main.py。macOS 可以使用launchd,Linux 桌面可以用桌面环境自带的“启动应用程序”功能。
自启不是重点,重点是自启之后要能在日志里看到启动时间。没有日志的自启,崩溃后很难定位。
7.2 日志轮转和异常通知
长期运行的脚本,日志文件会不断增长。建议使用logging.handlers.RotatingFileHandler,把日志写入logs/lending.log,并限制单文件大小。
import logging from logging.handlers import RotatingFileHandler file_handler = RotatingFileHandler( "logs/lending.log", maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8", ) logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", handlers=[file_handler], )异常通知可以在except分支中发送邮件或即时消息提醒。这里不需要引入复杂组件,只要在logger.exception后面加一个notify()函数,把异常标题和日志摘要发出去即可。通知实现可以后续再补,重点是先保证日志完整。
7.3 API Key 和配置安全
本地运行不等于内部安全。如果操作系统被植入恶意软件,.env文件里的密钥依然可能被读取。因此:
- API Key 只保留读取与资金订单相关权限,绝不开启提现权限。
- 定期轮换 API Key,比如每 90 天重新生成一次。
- 不要把
.env文件放在桌面或公共目录,建议放在项目根目录并设置访问权限。 - 不要在日志里打印完整签名、API Secret 或请求头。
- 不要在远程桌面、聊天工具中发送 Key 内容。
密钥泄露的第一道防线是权限设计,第二道防线是轮换机制。权限越小,泄露后的损失就越可控。
7.4 风控和回滚
自动化脚本必须有“急停开关”。最简单的实现是在config.json中增加enabled字段:
{ "enabled": false, "currency": "USD", "rate": 0.0001, "period": 30, "amount_ratio": 0.9, "check_interval_seconds": 60 }main.py启动时检查enabled,为false时直接退出:
if not config.get("enabled", False): logger.info("bot is disabled, exit") return这样遇到市场异常、API 异常或策略需要调整时,不需要删除 Key,也不需要改代码,只需把enabled改为false并重启脚本。
代码版本管理也很重要。不要直接在运行目录里改代码,而是把代码放到 Git 仓库,每次改动打一个 tag。如果新策略有问题,可以快速回退到上一个 tag 对应的版本。
本地运行自动化最大的优势不是性能,而是门槛低、密钥不离开设备。但这也意味着稳定性责任全部落在这台 PC 上:开机状态、网络质量、系统时间、日志保留和异常恢复,每一样都需要自己负责。
一个比较稳妥的上线路径是:先用 dry-run 跑一周,观察日志;再用最小金额跑通真实订单;确认状态记录和重复挂单防护有效后,再逐步增加资金。下一步可以继续扩展多币种支持、基于市场最高利率的动态定价、到期前撤单重挂,以及把状态存储从 JSON 升级为 SQLite。每一个方向都会让这个本地小工具越来越像一个严肃的个人交易系统。