create-routine
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCreate / Edit Routine
创建/编辑程序
You are working on a routine for Condor — a Python script auto-discovered from . Routines run via Telegram () or the web dashboard.
routines//routinesNot agent routines. Agent routines live inside trading agent strategies and are created via./trading-agent-builder
你正在为Condor开发一个程序——这是一个从自动发现的Python脚本。程序可通过Telegram(命令)或Web控制面板运行。
routines//routines注意:不是Agent程序。Agent程序位于交易Agent策略内部,需通过创建。/trading-agent-builder
Minimal Routine
最小化程序示例
python
from pydantic import BaseModel, Field
from telegram.ext import ContextTypes
from config_manager import get_client
CATEGORY = "Market Data" # Market Data | Analysis | Arbitrage | Monitoring | Bot Analysis
class Config(BaseModel):
"""One-line description shown in UI."""
trading_pair: str = Field(default="BTC-USDT", description="Trading pair")
connector_name: str = Field(default="binance_perpetual", description="Exchange connector")
async def run(config: Config, context: ContextTypes.DEFAULT_TYPE) -> str:
client = await get_client(context._chat_id, context=context)
if not client:
return "No server available"
# ... do work ...
return "result string"python
from pydantic import BaseModel, Field
from telegram.ext import ContextTypes
from config_manager import get_client
CATEGORY = "Market Data" # Market Data | Analysis | Arbitrage | Monitoring | Bot Analysis
class Config(BaseModel):
"""UI中显示的单行描述。"""
trading_pair: str = Field(default="BTC-USDT", description="交易对")
connector_name: str = Field(default="binance_perpetual", description="交易所连接器")
async def run(config: Config, context: ContextTypes.DEFAULT_TYPE) -> str:
client = await get_client(context._chat_id, context=context)
if not client:
return "无可用服务器"
# ... 执行任务 ...
return "结果字符串"Key Rules
核心规则
- File goes in as
routines/snake_case.py - Must export (Pydantic BaseModel) and
Configasync def run(config, context) -> str - = routine description in UI
Config.__doc__ - at module level groups it in the catalog
CATEGORY - Return a string, or for rich output
RoutineResult - is optional — routines can use external APIs directly (aiohttp, etc.)
get_client() - Use for parallel fetches
asyncio.gather - Handle missing data gracefully — return error strings, don't raise
- 文件需以命名并存放在
snake_case.py目录下routines/ - 必须导出(Pydantic BaseModel)和
Config函数async def run(config, context) -> str - 为程序在UI中显示的描述文本
Config.__doc__ - 模块级别的用于在目录中对程序进行分组
CATEGORY - 返回字符串,或使用实现富文本输出
RoutineResult - 为可选依赖——程序可直接调用外部API(如aiohttp等)
get_client() - 使用实现并行数据获取
asyncio.gather - 优雅处理缺失数据——返回错误字符串,而非抛出异常
Rich Output
富文本输出
python
from routines.base import RoutineResultpython
from routines.base import RoutineResultTables in web dashboard
Web控制面板中的表格
return RoutineResult(
text="Summary for Telegram",
table_data=[{"Pair": "BTC-USDT", "Price": 100000}],
table_columns=["Pair", "Price"],
)
return RoutineResult(
text="Telegram端摘要",
table_data=[{"Pair": "BTC-USDT", "Price": 100000}],
table_columns=["Pair", "Price"],
)
Chart image sent to Telegram
发送至Telegram的图表图片
return RoutineResult(text=summary, chart_image=png_bytes)
return RoutineResult(text=summary, chart_image=png_bytes)
KPI cards in web dashboard
Web控制面板中的KPI卡片
return RoutineResult(text=summary, sections=[
{"type": "kpi", "label": "Price", "value": "$100K", "delta": "+5%", "trend": "up"},
])
undefinedreturn RoutineResult(text=summary, sections=[
{"type": "kpi", "label": "Price", "value": "$100K", "delta": "+5%", "trend": "up"},
])
undefinedReportBuilder (HTML Reports)
ReportBuilder(HTML报告生成)
Always lazy-import inside try/except:
python
try:
from condor.reports import ReportBuilder
builder = ReportBuilder("Report Title")
builder.source("routine", "routine_name").tags(["tag1", "tag2"])
builder.kpi("Price", "$100K", delta="+5%", trend="up") # individual calls, NOT a list
builder.markdown("## Analysis\nSome text") # use markdown() for all text/headings
builder.table([{"Col": "val"}]) # columns auto-detected from first row
builder.plotly(fig) # Plotly figure object
builder.manual_order() # preserve insertion order (default: kpi→plotly→table→markdown)
builder.save()
except Exception as e:
logger.warning(f"Report generation failed: {e}")Only these methods exist: , , , , , , , . No , , , or .
sourcetagskpimarkdowntableplotlymanual_ordersaveheading()text()section()html()请始终在try/except块中延迟导入:
python
try:
from condor.reports import ReportBuilder
builder = ReportBuilder("Report Title")
builder.source("routine", "routine_name").tags(["tag1", "tag2"])
builder.kpi("Price", "$100K", delta="+5%", trend="up") # 单独调用,而非传入列表
builder.markdown("## Analysis\nSome text") # 使用markdown()处理所有文本/标题
builder.table([{"Col": "val"}]) # 自动从第一行检测列名
builder.plotly(fig) # Plotly图形对象
builder.manual_order() # 保留插入顺序(默认顺序:kpi→plotly→table→markdown)
builder.save()
except Exception as e:
logger.warning(f"Report generation failed: {e}")仅支持以下方法: , , , , , , , 。不支持, , , 或。
sourcetagskpimarkdowntableplotlymanual_ordersaveheading()text()section()html()Live Reports for Continuous Routines
持续运行程序的实时报告
Use for continuous routines that produce a living report updated each tick:
LiveReportpython
from condor.reports import LiveReport
report = LiveReport("Monitor Title", source_name="routine_name", tags=["live"])
history = []
try:
while True:
# ... fetch data ...
history.append({"Time": now, "Price": price})
report.clear() # reset builder for fresh render
report.builder.manual_order()
report.builder.kpi("Price", f"${price:,.2f}")
report.builder.table(history[-50:])
report.update() # creates on first call, updates thereafter
await asyncio.sleep(interval)
except asyncio.CancelledError:
return "Stopped"LiveReport API: , , (property), (property — the underlying )
clear()update()report_idbuilderReportBuilder对于需持续运行并生成实时更新报告的程序,请使用:
LiveReportpython
from condor.reports import LiveReport
report = LiveReport("Monitor Title", source_name="routine_name", tags=["live"])
history = []
try:
while True:
# ... 获取数据 ...
history.append({"Time": now, "Price": price})
report.clear() # 重置生成器以重新渲染
report.builder.manual_order()
report.builder.kpi("Price", f"${price:,.2f}")
report.builder.table(history[-50:])
report.update() # 首次调用创建报告,后续调用更新报告
await asyncio.sleep(interval)
except asyncio.CancelledError:
return "已停止"LiveReport API: , , (属性), (属性——底层的)
clear()update()report_idbuilderReportBuilderExecution Contexts
执行上下文
Routines run in 3 different contexts — your code must work in all of them:
| Context | | | Trigger |
|---|---|---|---|
| Telegram | Real bot (python-telegram-bot) | User's chat ID | |
| Web Dashboard | | User ID or 0 | Web API |
| MCP | | | |
Key point: is always available — never . In non-Telegram contexts, it's an that sends messages via the Telegram HTTP API using . You can always call safely.
context.botNone_HttpBotTELEGRAM_TOKENcontext.bot.send_message(...)程序会在3种不同上下文中运行——你的代码必须适配所有场景:
| 上下文 | | | 触发方式 |
|---|---|---|---|
| Telegram | 真实机器人(python-telegram-bot) | 用户聊天ID | |
| Web控制面板 | | 用户ID或0 | Web API |
| MCP | | | |
核心要点: 始终可用——永远不会为。在非Telegram上下文中,它是,通过Telegram HTTP API(使用)发送消息。你可以安全地调用。
context.botNone_HttpBotTELEGRAM_TOKENcontext.bot.send_message(...)What _HttpBot
supports
_HttpBot_HttpBot
支持的方法
_HttpBotsend_message(chat_id=..., text=..., parse_mode=...)send_photo(chat_id=..., photo=..., caption=...)send_document(chat_id=..., document=..., caption=...)edit_message_text(chat_id=..., message_id=..., text=...)
If is not set, calls are silently ignored (no crash).
TELEGRAM_TOKENsend_message(chat_id=..., text=..., parse_mode=...)send_photo(chat_id=..., photo=..., caption=...)send_document(chat_id=..., document=..., caption=...)edit_message_text(chat_id=..., message_id=..., text=...)
若未设置,调用会被静默忽略(不会崩溃)。
TELEGRAM_TOKENContinuous Routines
持续运行程序
Set for routines with internal loops. These run as asyncio tasks until cancelled.
CONTINUOUS = Truepython
import asyncio
from pydantic import BaseModel, Field
from telegram.ext import ContextTypes
from config_manager import get_client
CONTINUOUS = True
class Config(BaseModel):
"""Live price monitor with alerts."""
connector: str = Field(default="binance", description="Exchange connector")
trading_pair: str = Field(default="BTC-USDT", description="Trading pair")
threshold_pct: float = Field(default=1.0, description="Alert threshold %")
interval_sec: int = Field(default=10, description="Check interval in seconds")
async def run(config: Config, context: ContextTypes.DEFAULT_TYPE) -> str:
chat_id = context._chat_id
client = await get_client(chat_id, context=context)
if not client:
return "No server available"
# Send start notification (works in all contexts)
await context.bot.send_message(
chat_id=chat_id,
text=f"Started monitoring {config.trading_pair}",
)
last_price = None
try:
while True:
prices = await client.market_data.get_prices(
connector_name=config.connector,
trading_pairs=config.trading_pair,
)
current = prices["prices"].get(config.trading_pair)
if current and last_price:
change = abs((current - last_price) / last_price) * 100
if change >= config.threshold_pct:
await context.bot.send_message(
chat_id=chat_id,
text=f"Alert: {config.trading_pair} moved {change:.2f}%",
)
last_price = current or last_price
await asyncio.sleep(config.interval_sec)
except asyncio.CancelledError:
return "Stopped"对于包含内部循环的程序,设置。这类程序会以asyncio任务的形式运行,直到被取消。
CONTINUOUS = Truepython
import asyncio
from pydantic import BaseModel, Field
from telegram.ext import ContextTypes
from config_manager import get_client
CONTINUOUS = True
class Config(BaseModel):
"""带告警功能的实时价格监控器。"""
connector: str = Field(default="binance", description="交易所连接器")
trading_pair: str = Field(default="BTC-USDT", description="交易对")
threshold_pct: float = Field(default=1.0, description="告警阈值百分比")
interval_sec: int = Field(default=10, description="检查间隔(秒)")
async def run(config: Config, context: ContextTypes.DEFAULT_TYPE) -> str:
chat_id = context._chat_id
client = await get_client(chat_id, context=context)
if not client:
return "无可用服务器"
# 发送启动通知(适配所有上下文)
await context.bot.send_message(
chat_id=chat_id,
text=f"已开始监控{config.trading_pair}",
)
last_price = None
try:
while True:
prices = await client.market_data.get_prices(
connector_name=config.connector,
trading_pairs=config.trading_pair,
)
current = prices["prices"].get(config.trading_pair)
if current and last_price:
change = abs((current - last_price) / last_price) * 100
if change >= config.threshold_pct:
await context.bot.send_message(
chat_id=chat_id,
text=f"告警:{config.trading_pair}波动{change:.2f}%",
)
last_price = current or last_price
await asyncio.sleep(config.interval_sec)
except asyncio.CancelledError:
return "已停止"Continuous routine rules:
持续运行程序规则:
- Always catch at the outer loop — re-raise or return
asyncio.CancelledError - Use for real-time notifications (works in all contexts)
context.bot.send_message() - Inner loop exceptions should be caught and logged, NOT re-raised
- Return a summary string when cancelled
- 始终在外层循环捕获——可重新抛出或返回结果
asyncio.CancelledError - 使用发送实时通知(适配所有上下文)
context.bot.send_message() - 内层循环异常需捕获并记录日志,而非重新抛出
- 被取消时返回摘要字符串
Sending Charts to Telegram
向Telegram发送图表
python
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=150) # matplotlibpython
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=150) # matplotlibOR: fig.write_image(buf, format="png", scale=2) # plotly
或:fig.write_image(buf, format="png", scale=2) # plotly
buf.seek(0)
buf.seek(0)
Works in all contexts (Telegram, Web, MCP)
适配所有上下文(Telegram、Web、MCP)
await context.bot.send_photo(chat_id=context._chat_id, photo=buf, caption="Title")
await context.bot.send_photo(chat_id=context._chat_id, photo=buf, caption="标题")
Also return as RoutineResult for web dashboard
同时返回为RoutineResult供Web控制面板展示
return RoutineResult(text=summary, chart_image=buf.getvalue())
undefinedreturn RoutineResult(text=summary, chart_image=buf.getvalue())
undefinedHummingbot Client API
Hummingbot Client API
python
client = await get_client(context._chat_id, context=context)python
client = await get_client(context._chat_id, context=context)Market data
市场数据
await client.market_data.get_candles(connector, pair, interval="1m", max_records=100)
await client.market_data.get_order_book(connector, pair, depth=10)
await client.market_data.get_prices(connector, trading_pairs) # str or list
await client.market_data.get_funding_info(connector, pair)
await client.market_data.get_price_for_volume(connector, pair, volume, is_buy)
await client.market_data.get_historical_candles(connector, pair, interval, start_time, end_time)
await client.market_data.get_candles_last_days(connector, pair, days, interval="1h")
await client.market_data.get_candles(connector, pair, interval="1m", max_records=100)
await client.market_data.get_order_book(connector, pair, depth=10)
await client.market_data.get_prices(connector, trading_pairs) # 字符串或列表
await client.market_data.get_funding_info(connector, pair)
await client.market_data.get_price_for_volume(connector, pair, volume, is_buy)
await client.market_data.get_historical_candles(connector, pair, interval, start_time, end_time)
await client.market_data.get_candles_last_days(connector, pair, days, interval="1h")
Portfolio
投资组合
await client.portfolio.get_state(account_names=None, connector_names=None)
await client.portfolio.get_total_value() # returns float
await client.portfolio.get_distribution()
await client.portfolio.get_history(limit=100, interval=None)
await client.portfolio.get_state(account_names=None, connector_names=None)
await client.portfolio.get_total_value() # 返回float类型
await client.portfolio.get_distribution()
await client.portfolio.get_history(limit=100, interval=None)
Executors
执行器
await client.executors.search_executors(controller_ids=[], status="active", limit=50)
await client.executors.get_performance_report(controller_id=cid) # NOT executor_id
await client.executors.create_executor(executor_config_dict)
undefinedawait client.executors.search_executors(controller_ids=[], status="active", limit=50)
await client.executors.get_performance_report(controller_id=cid) # 注意:不是executor_id
await client.executors.create_executor(executor_config_dict)
undefinedParsing responses
响应解析
python
undefinedpython
undefinedCandles — handle both formats
K线数据——兼容两种格式
result = await client.market_data.get_candles(connector, pair, interval="1m", max_records=100)
records = result if isinstance(result, list) else result.get("data", result.get("candles", []))
result = await client.market_data.get_candles(connector, pair, interval="1m", max_records=100)
records = result if isinstance(result, list) else result.get("data", result.get("candles", []))
Order book
订单簿
ob = await client.market_data.get_order_book(connector, pair, depth=10)
bids, asks = ob.get("bids", []), ob.get("asks", []) # [[price, size], ...]
ob = await client.market_data.get_order_book(connector, pair, depth=10)
bids, asks = ob.get("bids", []), ob.get("asks", []) # [[价格, 数量], ...]
Bounded concurrency for bulk fetches
批量获取时的并发限制
sem = asyncio.Semaphore(10)
async def fetch(p):
async with sem:
return await client.market_data.get_candles(connector, p, interval="1m", max_records=100)
results = await asyncio.gather(*[fetch(p) for p in pairs], return_exceptions=True)
undefinedsem = asyncio.Semaphore(10)
async def fetch(p):
async with sem:
return await client.market_data.get_candles(connector, p, interval="1m", max_records=100)
results = await asyncio.gather(*[fetch(p) for p in pairs], return_exceptions=True)
undefinedPlotly Chart Rules
Plotly图表规则
- Legend always at the bottom: Every Plotly figure must set so the legend appears horizontally below the chart, never on top or to the side.
fig.update_layout(legend=dict(orientation="h", yanchor="top", y=-0.15, xanchor="center", x=0.5))
- 图例始终置于底部: 所有Plotly图形必须设置,确保图例水平显示在图表下方,而非顶部或侧边。
fig.update_layout(legend=dict(orientation="h", yanchor="top", y=-0.15, xanchor="center", x=0.5))
Common Mistakes
常见错误
- NOT
get_order_book()get_order_book_snapshot - NOT
get_candles(connector, pair, interval, max_records)get_candles(pair, interval, limit) - NOT
get_performance_report(controller_id=...)get_performance_report(executor_id=...) - — plain dict, NOT Pydantic model
create_executor(config_dict) - — individual args, NOT a list of dicts
builder.kpi(label, value) - All client methods are async — always
await - returns
get_total_value(), all others returnfloatdict
- 使用而非~~
get_order_book()~~get_order_book_snapshot - 使用而非~~
get_candles(connector, pair, interval, max_records)~~get_candles(pair, interval, limit) - 使用而非~~
get_performance_report(controller_id=...)~~get_performance_report(executor_id=...) - 需传入普通字典,而非Pydantic模型
create_executor(config_dict) - 需传入单独参数,而非字典列表
builder.kpi(label, value) - 所有客户端方法均为异步方法——必须使用
await - 返回
get_total_value()类型,其他方法均返回float类型dict