Back to Blog

Developer / C03

MERX Python SDK: 零依赖,全功能

MERX Python SDK仅使用Python标准库提供完整的MERX TRON能量交易接口 - 无需requests、httpx或aiohttp。本文涵盖四个SDK模块(prices、orders、balance、webhooks)、每个方法的可运行示例、数据类类型系统、错误处理,以及与JavaScript SDK的对比,方便同时使用两种语言的团队参考。

为什么选择零依赖

大多数Python HTTP客户端会引入依赖树。仅 requests 库就会带入 urllib3certificharset-normalizeridna。对于一个只需要少量API调用的轻量级SDK来说,这种开销完全没有必要。

MERX Python SDK仅使用标准库中的 urllib.request。这意味着:

代价是缺少异步支持。SDK使用同步HTTP调用。如果您需要异步,REST API足够简单,可以按照本文展示的模式直接使用 aiohttphttpx 调用。

安装

pip install merx-sdk

仅此而已。不会安装任何附加依赖。

快速入门

from merx import MerxClient

client = MerxClient(api_key="sk_live_your_key_here")

# 查询当前价格
prices = client.prices.list()
for p in prices:
    if p.energy_prices:
        print(f"{p.provider}: {p.energy_prices[0].price_sun} SUN/energy")

# 购买能量
order = client.orders.create(
    resource_type="ENERGY",
    amount=65000,
    target_address="TYourTargetAddressHere",
    duration_sec=3600,
)
print(f"Order {order.id}: {order.status}")

客户端使用 merx.exchange 的MERX控制面板中的API密钥初始化。可选的 base_url 参数默认为 https://merx.exchange

client = MerxClient(
    api_key="sk_live_your_key_here",
    base_url="https://merx.exchange",  # 可选
)

类型系统

每个API响应都被解析为Python数据类。没有原始字典,没有无类型数据。您的IDE可以提供完整的自动补全,类型检查器可以在运行前捕获错误。

SDK导出16个数据类类型:

from merx import (
    Balance,
    DepositInfo,
    Fill,
    HistoryEntry,
    HistorySummary,
    Order,
    OrderPreview,
    OrderWithFills,
    PreviewMatch,
    PriceHistoryEntry,
    PricePoint,
    PriceStats,
    ProviderPrice,
    Webhook,
    Withdrawal,
    MerxError,
)

核心类型定义

@dataclass
class PricePoint:
    duration_sec: int
    price_sun: int

@dataclass
class ProviderPrice:
    provider: str
    is_market: bool
    energy_prices: list[PricePoint]
    bandwidth_prices: list[PricePoint]
    available_energy: int
    available_bandwidth: int
    fetched_at: int

@dataclass
class Order:
    id: str
    resource_type: str       # "ENERGY" 或 "BANDWIDTH"
    order_type: str          # "MARKET", "LIMIT", "PERIODIC", "BROADCAST"
    status: str              # "PENDING", "EXECUTING", "FILLED", "FAILED", "CANCELLED"
    amount: int
    target_address: str
    duration_sec: int
    total_cost_sun: Optional[int] = None
    total_fee_sun: Optional[int] = None
    created_at: str = ""
    filled_at: Optional[str] = None
    expires_at: Optional[str] = None

@dataclass
class Fill:
    provider: str
    amount: int
    price_sun: int
    cost_sun: int
    tx_id: Optional[str] = None
    status: str = ""
    delegation_tx: Optional[str] = None
    verified: bool = False
    tronscan_url: Optional[str] = None

所有数据类使用标准Python类型提示。带有默认值的字段是可选的API响应字段,可能不会出现在每个响应中。

模块一: Prices

Prices模块提供五个方法用于查询实时和历史价格数据。所有价格接口均为公开接口,无需认证,但SDK始终发送API密钥头以保持一致性。

prices.list()

返回所有活跃供应商的当前定价。

prices = client.prices.list()

for p in prices:
    print(f"\n{p.provider} (market={p.is_market}):")
    print(f"  Available energy: {p.available_energy:,}")
    for tier in p.energy_prices:
        hours = tier.duration_sec // 3600
        print(f"  {hours}h: {tier.price_sun} SUN/unit")

返回 list[ProviderPrice]。每个条目包含供应商名称、是否支持灵活时长的市价订单、能量和带宽的价格层级,以及当前可用容量。

prices.best(resource, amount=None)

返回指定资源类型的最低可用价格。

best = client.prices.best("ENERGY")
print(f"Cheapest: {best.price_sun} SUN from {best.provider}")

# 仅显示至少有200,000可用量的供应商
best_large = client.prices.best("ENERGY", amount=200000)

返回 PriceHistoryEntryamount 参数会过滤掉容量不足的供应商。

prices.history(provider=None, resource=None, period="24h")

返回用于图表和分析的历史价格快照。

history = client.prices.history(
    provider="sohu",
    resource="ENERGY",
    period="7d",
)

for entry in history:
    print(f"{entry.polled_at}: {entry.price_sun} SUN ({entry.available:,} available)")

返回 list[PriceHistoryEntry]。可用周期: "1h""6h""24h""7d""30d"

prices.stats()

返回市场汇总统计数据。

stats = client.prices.stats()
print(f"Best price: {stats.best_price_sun} SUN")
print(f"Average price: {stats.avg_price_sun} SUN")
print(f"Providers online: {stats.total_providers}")
print(f"Cheapest-provider changes (24h): {stats.cheapest_changes_24h}")

返回 PriceStats

prices.preview(resource, amount, duration, max_price_sun=None)

在下单前预览订单成本。

preview = client.prices.preview(
    resource="ENERGY",
    amount=100000,
    duration=86400,       # 24小时
    max_price_sun=35,     # 可选价格上限
)

if preview.best:
    print(f"Best: {preview.best.provider}")
    print(f"Cost: {preview.best.cost_trx} TRX")
    print(f"Price: {preview.best.price_sun} SUN/unit")

for fb in preview.fallbacks:
    print(f"Alternative: {fb.provider} at {fb.cost_trx} TRX")

if preview.no_providers:
    print("No providers available for these parameters")

返回 OrderPreview,包含 best 匹配(或 None)、fallbacks 列表和 no_providers 布尔值。

模块二: Orders

orders.create(...)

创建能量或带宽订单。

order = client.orders.create(
    resource_type="ENERGY",
    amount=65000,
    target_address="TYourTargetAddress",
    duration_sec=3600,
    order_type="MARKET",           # 默认值
    max_price_sun=None,            # LIMIT订单必填
    idempotency_key="unique-id",   # 防止重复订单
)

print(f"Order {order.id}: {order.status}")

返回 Order。请注意,与JavaScript SDK使用字典传递参数不同,Python SDK使用显式关键字参数以提高清晰度。

idempotency_key 参数对生产系统至关重要。如果网络错误导致重试,API将返回原始订单而非创建重复订单。

orders.list(limit=30, offset=0, status=None)

带分页的订单列表。

orders, total = client.orders.list(limit=10, offset=0, status="FILLED")
print(f"{total} filled orders total")

for o in orders:
    cost_trx = (o.total_cost_sun or 0) / 1_000_000
    print(f"  {o.id}: {o.amount:,} {o.resource_type}, {cost_trx:.3f} TRX")

返回 tuple[list[Order], int] - 订单列表和用于分页的总数。

orders.get(order_id)

返回单个订单及其成交明细。

order = client.orders.get("ord_abc123")

print(f"Status: {order.status}")
print(f"Total cost: {(order.total_cost_sun or 0) / 1_000_000:.3f} TRX")

for fill in order.fills:
    print(f"  {fill.provider}: {fill.amount:,} at {fill.price_sun} SUN")
    print(f"  Verified: {fill.verified}")
    if fill.tronscan_url:
        print(f"  TX: {fill.tronscan_url}")

返回 OrderWithFills,它扩展了 Order 并包含 Fill 对象的 fills 列表。

模块三: Balance

balance.get()

返回当前账户余额。

bal = client.balance.get()
print(f"TRX: {bal.trx}")
print(f"USDT: {bal.usdt}")
print(f"Locked: {bal.trx_locked}")

返回 Balancetrx_locked 字段显示为待处理订单预留的TRX。

balance.deposit_info()

返回为账户充值的充值地址和备注。

info = client.balance.deposit_info()
print(f"Send TRX to: {info.address}")
print(f"Memo: {info.memo}")
print(f"Minimum: {info.min_amount_trx} TRX / {info.min_amount_usdt} USDT")

返回 DepositInfo。请始终在充值交易中包含备注以实现自动到账。

balance.withdraw(address, amount, currency="TRX", idempotency_key=None)

将资金提现至外部TRON地址。

withdrawal = client.balance.withdraw(
    address="TExternalAddress",
    amount=100,
    currency="TRX",
    idempotency_key="withdraw-unique-id",
)
print(f"Withdrawal {withdrawal.id}: {withdrawal.status}")

返回 Withdrawal

balance.history(period="30D") 和 balance.summary()

history = client.balance.history("7D")
print(f"{len(history)} transactions in last 7 days")

summary = client.balance.summary()
print(f"Total orders: {summary.total_orders}")
print(f"Total energy: {summary.total_energy:,}")
print(f"Average price: {summary.avg_price_sun} SUN")
print(f"Total spent: {summary.total_spent_sun / 1_000_000:.3f} TRX")

模块四: Webhooks

webhooks.create(url, events)

创建Webhook订阅。

webhook = client.webhooks.create(
    url="https://your-server.com/merx-webhook",
    events=["order.filled", "order.failed", "deposit.received"],
)
print(f"Webhook ID: {webhook.id}")
print(f"Secret: {webhook.secret}")  # 仅显示一次

返回 Webhooksecret 字段仅在创建响应中包含,用于HMAC-SHA256签名验证。

可用事件类型: order.filledorder.faileddeposit.receivedwithdrawal.completed

webhooks.list() 和 webhooks.delete(webhook_id)

webhooks = client.webhooks.list()
active = [w for w in webhooks if w.is_active]
print(f"{len(active)} active webhooks")

deleted = client.webhooks.delete("wh_abc123")
print(f"Deleted: {deleted}")  # True 或 False

错误处理

所有API错误都抛出 MerxError,包含 code 属性和人类可读的消息。

from merx import MerxClient, MerxError

client = MerxClient(api_key="sk_live_your_key_here")

try:
    order = client.orders.create(
        resource_type="ENERGY",
        amount=65000,
        target_address="TInvalidAddress",
        duration_sec=3600,
    )
except MerxError as e:
    print(f"Error [{e.code}]: {e}")
    # 输出: Error [INVALID_ADDRESS]: Target address is not a valid TRON address

MerxError 类继承自 Exception,因此可以自然地融入Python异常处理机制。code 属性提供机器可读的分类:

代码含义
UNAUTHORIZED无效或缺少API密钥
INSUFFICIENT_FUNDS账户余额不足
INVALID_ADDRESS不是有效的TRON地址
ORDER_NOT_FOUND订单ID不存在
DUPLICATE_REQUEST幂等性密钥已被使用
RATE_LIMITED请求过多,请稍后重试
PROVIDER_UNAVAILABLE没有供应商可以完成该请求
VALIDATION_ERROR请求体或参数验证失败

与JavaScript SDK的对比

两个SDK涵盖相同的四个模块和相同的方法。主要差异在于语言惯用法:

方面Python SDKJavaScript SDK
HTTP客户端urllib.request(标准库)原生 fetch
依赖
异步支持否(仅同步)是(所有方法返回Promise)
类型数据类TypeScript接口
创建订单关键字参数对象参数
订单列表返回值tuple[list, int]{ orders: [], total: number }
删除Webhook返回 bool返回 { deleted: boolean }
最低运行时Python 3.10+Node.js 18+
模块系统标准导入仅ESM

两个SDK的设计理念是在各自的语言中提供自然的使用体验,同时保持功能一致。同时使用Python和JavaScript的团队可以期望每个SDK具有相同的行为。

生产模式: 价格感知的订单下达

以下是一个完整的生产流程,包含查询当前价格、验证成本,以及使用幂等性密钥创建订单:

import uuid
from merx import MerxClient, MerxError

client = MerxClient(api_key="sk_live_your_key_here")

def buy_energy(target: str, amount: int = 65000, max_cost_trx: float = 5.0):
    """购买能量,带成本验证和幂等性保护。"""

    # 先预览成本
    preview = client.prices.preview(
        resource="ENERGY",
        amount=amount,
        duration=3600,
    )

    if preview.no_providers:
        raise RuntimeError("No energy providers available")

    cost = float(preview.best.cost_trx)
    if cost > max_cost_trx:
        raise RuntimeError(
            f"Cost {cost:.3f} TRX exceeds limit {max_cost_trx:.3f} TRX"
        )

    # 下单
    order = client.orders.create(
        resource_type="ENERGY",
        amount=amount,
        target_address=target,
        duration_sec=3600,
        idempotency_key=str(uuid.uuid4()),
    )

    print(f"Order {order.id} created at {preview.best.provider}")
    print(f"Expected cost: {cost:.3f} TRX")
    return order


try:
    order = buy_energy("TYourTargetAddress", amount=65000, max_cost_trx=3.0)
except MerxError as e:
    print(f"API error: [{e.code}] {e}")
except RuntimeError as e:
    print(f"Business logic error: {e}")

安装验证

安装后,验证SDK是否正常工作:

from merx import MerxClient, __version__

print(f"merx-sdk version: {__version__}")

# 公开接口,无需认证
client = MerxClient(api_key="test")
try:
    prices = client.prices.list()
    print(f"Providers online: {len(prices)}")
    for p in prices:
        if p.energy_prices:
            print(f"  {p.provider}: {p.energy_prices[0].price_sun} SUN")
except Exception as e:
    print(f"Connection error: {e}")

prices.list() 接口为公开接口,因此即使使用占位API密钥也可以测试连通性。

资源


All Articles