almanak-strategy-builder
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAlmanak Strategy Builder
Almanak 策略构建器
You are helping a quant build DeFi strategies using the Almanak SDK.
Strategies are Python classes that return Intent objects. The framework handles
compilation to transactions, execution, and state management.
<!-- almanak-sdk-start: quick-start -->你正在帮助量化分析师使用Almanak SDK构建DeFi策略。策略是返回Intent对象的Python类,该框架会自动处理交易编译、执行和状态管理。
<!-- almanak-sdk-start: quick-start -->Quick Start
快速开始
bash
undefinedbash
undefinedInstall
安装
pip install almanak
pip install almanak
Scaffold a new strategy
生成新策略脚手架
almanak strat new --template mean_reversion --name my_rsi --chain arbitrum
almanak strat new --template mean_reversion --name my_rsi --chain arbitrum
Run on local Anvil fork (auto-starts gateway + Anvil)
在本地Anvil分叉链上运行(自动启动gateway + Anvil)
cd my_rsi
almanak strat run --network anvil --once
cd my_rsi
almanak strat run --network anvil --once
Run a single iteration on mainnet
在主网运行单次迭代
almanak strat run --once
almanak strat run --once
Browse and copy a working demo strategy
浏览并复制可运行的演示策略
almanak strat demo
**Minimal strategy (3 files):**
my_strategy/
strategy.py # IntentStrategy subclass with decide() method
config.json # Runtime parameters (chain, tokens, thresholds)
.env # Secrets (ALMANAK_PRIVATE_KEY, ALCHEMY_API_KEY)
For Anvil testing, add `anvil_funding` to `config.json` so your wallet is auto-funded on fork start
(see [Configuration](#configuration) below).
```pythonalmanak strat demo
**最小策略结构(3个文件):**
my_strategy/
strategy.py # 实现decide()方法的IntentStrategy子类
config.json # 运行时参数(链、代币、阈值等)
.env # 敏感信息(ALMANAK_PRIVATE_KEY、ALCHEMY_API_KEY)
如果要在Anvil上测试,可以在`config.json`中添加`anvil_funding`配置,这样分叉链启动时会自动为你的钱包充值(参考下文[配置](#configuration)部分)。
```pythonstrategy.py
strategy.py
from decimal import Decimal
from almanak.framework.strategies import IntentStrategy, MarketSnapshot, almanak_strategy
from almanak.framework.intents import Intent
@almanak_strategy(
name="my_strategy",
version="1.0.0",
supported_chains=["arbitrum"],
supported_protocols=["uniswap_v3"],
intent_types=["SWAP", "HOLD"],
)
class MyStrategy(IntentStrategy):
def init(self, *args, **kwargs):
super().init(*args, **kwargs)
self.trade_size = Decimal(str(self.config.get("trade_size_usd", "100")))
def decide(self, market: MarketSnapshot) -> Intent | None:
rsi = market.rsi("WETH", period=14)
if rsi.value < 30:
return Intent.swap(
from_token="USDC", to_token="WETH",
amount_usd=self.trade_size, max_slippage=Decimal("0.005"),
)
return Intent.hold(reason=f"RSI={rsi.value:.1f}, waiting")
> **Note:** `amount_usd=` requires a live price oracle from the gateway. If swaps revert with
> "Too little received", switch to `amount=` (token units) which bypasses USD-to-token conversion.
> Always verify pricing on first live run with `--dry-run --once`.
<!-- almanak-sdk-end: quick-start -->
<!-- almanak-sdk-start: core-concepts -->from decimal import Decimal
from almanak.framework.strategies import IntentStrategy, MarketSnapshot, almanak_strategy
from almanak.framework.intents import Intent
@almanak_strategy(
name="my_strategy",
version="1.0.0",
supported_chains=["arbitrum"],
supported_protocols=["uniswap_v3"],
intent_types=["SWAP", "HOLD"],
)
class MyStrategy(IntentStrategy):
def init(self, *args, **kwargs):
super().init(*args, **kwargs)
self.trade_size = Decimal(str(self.config.get("trade_size_usd", "100")))
def decide(self, market: MarketSnapshot) -> Intent | None:
rsi = market.rsi("WETH", period=14)
if rsi.value < 30:
return Intent.swap(
from_token="USDC", to_token="WETH",
amount_usd=self.trade_size, max_slippage=Decimal("0.005"),
)
return Intent.hold(reason=f"RSI={rsi.value:.1f}, waiting")
> **注意:** `amount_usd=` 需要gateway提供实时价格预言机。如果兑换交易因为「到账金额过少」回滚,可以改用`amount=`(代币单位),这样会跳过USD到代币的转换。首次上线运行时一定要先使用`--dry-run --once`参数验证定价逻辑。
<!-- almanak-sdk-end: quick-start -->
<!-- almanak-sdk-start: core-concepts -->Core Concepts
核心概念
IntentStrategy
IntentStrategy
All strategies inherit from and implement one method:
IntentStrategypython
def decide(self, market: MarketSnapshot) -> Intent | NoneThe framework calls on each iteration with a fresh .
Return an object (swap, LP, borrow, etc.) or .
decide()MarketSnapshotIntentIntent.hold()所有策略都继承自,并且需要实现一个方法:
IntentStrategypython
def decide(self, market: MarketSnapshot) -> Intent | None框架每次迭代都会传入最新的调用方法,你可以返回一个对象(兑换、LP做市、借贷等)或者。
MarketSnapshotdecide()IntentIntent.hold()Lifecycle
生命周期
- : Extract config parameters, set up state
__init__ - : Called each iteration - return an Intent
decide(market) - : Optional callback after execution
on_intent_executed(intent, success, result) - : Optional - return dict for monitoring dashboards
get_status() - /
supports_teardown(): Optional safe shutdowngenerate_teardown_intents()
- : 提取配置参数,初始化状态
__init__ - : 每次迭代调用,返回一个Intent
decide(market) - : 可选回调,在Intent执行后触发
on_intent_executed(intent, success, result) - : 可选,返回字典用于监控仪表盘展示
get_status() - /
supports_teardown(): 可选的安全关闭逻辑generate_teardown_intents()
@almanak_strategy Decorator
@almanak_strategy 装饰器
Attaches metadata used by the framework and CLI:
python
@almanak_strategy(
name="my_strategy", # Unique identifier
description="What it does", # Human-readable description
version="1.0.0", # Strategy version
author="Your Name", # Optional
tags=["trading", "rsi"], # Optional tags for discovery
supported_chains=["arbitrum"], # Which chains this runs on
supported_protocols=["uniswap_v3"], # Which protocols it uses
intent_types=["SWAP", "HOLD"], # Intent types it may return
)用于附加框架和CLI需要用到的元数据:
python
@almanak_strategy(
name="my_strategy", # 唯一标识符
description="What it does", # 人类可读的描述
version="1.0.0", # 策略版本
author="Your Name", # 可选
tags=["trading", "rsi"], # 可选标签,用于发现
supported_chains=["arbitrum"], # 策略支持的链
supported_protocols=["uniswap_v3"], # 策略用到的协议
intent_types=["SWAP", "HOLD"], # 可能返回的Intent类型
)Config Access
配置访问
In , read parameters from (dict loaded from config.json):
__init__self.configpython
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.trade_size = Decimal(str(self.config.get("trade_size_usd", "100")))
self.rsi_period = int(self.config.get("rsi_period", 14))
self.base_token = self.config.get("base_token", "WETH")Also available: (str), (str).
<!-- almanak-sdk-end: core-concepts -->
<!-- almanak-sdk-start: intent-vocabulary -->self.chainself.wallet_address在中可以从读取参数(从config.json加载的字典):
__init__self.configpython
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.trade_size = Decimal(str(self.config.get("trade_size_usd", "100")))
self.rsi_period = int(self.config.get("rsi_period", 14))
self.base_token = self.config.get("base_token", "WETH")还可以访问:(字符串,当前链)、(字符串,钱包地址)。
<!-- almanak-sdk-end: core-concepts -->
<!-- almanak-sdk-start: intent-vocabulary -->self.chainself.wallet_addressIntent Reference
Intent 参考
All intents are created via factory methods. Import:
Intentpython
from almanak.framework.intents import Intent所有Intent都通过工厂方法创建,导入方式:
Intentpython
from almanak.framework.intents import IntentTrading
交易类
Intent.swap - Exchange tokens on a DEX
python
Intent.swap(
from_token="USDC", # Token to sell
to_token="WETH", # Token to buy
amount_usd=Decimal("1000"), # Amount in USD (use amount_usd OR amount)
amount=Decimal("500"), # Amount in token units (alternative to amount_usd)
max_slippage=Decimal("0.005"), # Max slippage (0.5%)
protocol="uniswap_v3", # Optional: specific DEX
chain="arbitrum", # Optional: override chain
destination_chain="base", # Optional: cross-chain swap
)Use to swap the entire balance.
amount="all"amount=amount_usd=amount_usd=amount=amount=Intent.swap - 在DEX上兑换代币
python
Intent.swap(
from_token="USDC", # 要卖出的代币
to_token="WETH", # 要买入的代币
amount_usd=Decimal("1000"), # USD计价的金额(二选一使用amount_usd或amount)
amount=Decimal("500"), # 代币单位的金额(amount_usd的替代选项)
max_slippage=Decimal("0.005"), # 最大滑点(0.5%)
protocol="uniswap_v3", # 可选:指定DEX
chain="arbitrum", # 可选:覆盖默认链
destination_chain="base", # 可选:跨链兑换
)使用可以兑换全部余额。
amount="all"amount=amount_usd=amount_usdamountamountLiquidity Provision
流动性提供
Intent.lp_open - Open a concentrated LP position
python
Intent.lp_open(
pool="WETH/USDC", # Pool identifier
amount0=Decimal("1.0"), # Amount of token0
amount1=Decimal("2000"), # Amount of token1
range_lower=Decimal("1800"), # Lower price bound
range_upper=Decimal("2200"), # Upper price bound
protocol="uniswap_v3", # Default: uniswap_v3
chain=None, # Optional override
)Intent.lp_close - Close an LP position
python
Intent.lp_close(
position_id="12345", # NFT token ID from lp_open result
pool="WETH/USDC", # Optional pool identifier
collect_fees=True, # Collect accumulated fees
protocol="uniswap_v3",
)Intent.collect_fees - Harvest LP fees without closing
python
Intent.collect_fees(
pool="WETH/USDC",
protocol="traderjoe_v2",
)Intent.lp_open - 开一个集中流动性LP仓位
python
Intent.lp_open(
pool="WETH/USDC", # 池标识符
amount0=Decimal("1.0"), # token0的数量
amount1=Decimal("2000"), # token1的数量
range_lower=Decimal("1800"), # 价格区间下限
range_upper=Decimal("2200"), # 价格区间上限
protocol="uniswap_v3", # 默认:uniswap_v3
chain=None, # 可选覆盖链
)Intent.lp_close - 关闭LP仓位
python
Intent.lp_close(
position_id="12345", # lp_open返回的NFT代币ID
pool="WETH/USDC", # 可选池标识符
collect_fees=True, # 归集累积的手续费
protocol="uniswap_v3",
)Intent.collect_fees - 提取LP手续费但不关闭仓位
python
Intent.collect_fees(
pool="WETH/USDC",
protocol="traderjoe_v2",
)Lending / Borrowing
借贷类
Intent.supply - Deposit collateral into a lending protocol
python
Intent.supply(
protocol="aave_v3",
token="WETH",
amount=Decimal("10"),
use_as_collateral=True, # Enable as collateral (default: True)
market_id=None, # Required for Morpho Blue
)Intent.borrow - Borrow tokens against collateral
python
Intent.borrow(
protocol="aave_v3",
collateral_token="WETH",
collateral_amount=Decimal("10"),
borrow_token="USDC",
borrow_amount=Decimal("5000"),
interest_rate_mode="variable", # Aave: "variable" or "stable"
market_id=None, # Required for Morpho Blue
)Intent.repay - Repay borrowed tokens
python
Intent.repay(
protocol="aave_v3",
token="USDC",
amount=Decimal("5000"),
repay_full=False, # Set True to repay entire debt
market_id=None,
)Intent.withdraw - Withdraw from lending protocol
python
Intent.withdraw(
protocol="aave_v3",
token="WETH",
amount=Decimal("10"),
withdraw_all=False, # Set True to withdraw everything
market_id=None,
)Intent.supply - 向借贷协议存入抵押品
python
Intent.supply(
protocol="aave_v3",
token="WETH",
amount=Decimal("10"),
use_as_collateral=True, # 启用作为抵押品(默认:True)
market_id=None, # Morpho Blue需要填此项
)Intent.borrow - 用抵押品借入代币
python
Intent.borrow(
protocol="aave_v3",
collateral_token="WETH",
collateral_amount=Decimal("10"),
borrow_token="USDC",
borrow_amount=Decimal("5000"),
interest_rate_mode="variable", # Aave可选:"variable"浮动 / "stable"固定
market_id=None, # Morpho Blue需要填此项
)Intent.repay - 偿还借入的代币
python
Intent.repay(
protocol="aave_v3",
token="USDC",
amount=Decimal("5000"),
repay_full=False, # 设为True偿还全部债务
market_id=None,
)Intent.withdraw - 从借贷协议提取资金
python
Intent.withdraw(
protocol="aave_v3",
token="WETH",
amount=Decimal("10"),
withdraw_all=False, # 设为True提取全部资金
market_id=None,
)Perpetuals
永续合约
Intent.perp_open - Open a perpetual futures position
python
Intent.perp_open(
market="ETH/USD",
collateral_token="USDC",
collateral_amount=Decimal("1000"),
size_usd=Decimal("5000"),
is_long=True,
leverage=Decimal("5"),
max_slippage=Decimal("0.01"),
protocol="gmx_v2",
)Intent.perp_close - Close a perpetual futures position
python
Intent.perp_close(
market="ETH/USD",
collateral_token="USDC",
is_long=True,
size_usd=None, # None = close full position
max_slippage=Decimal("0.01"),
protocol="gmx_v2",
)Intent.perp_open - 开永续合约仓位
python
Intent.perp_open(
market="ETH/USD",
collateral_token="USDC",
collateral_amount=Decimal("1000"),
size_usd=Decimal("5000"),
is_long=True,
leverage=Decimal("5"),
max_slippage=Decimal("0.01"),
protocol="gmx_v2",
)Intent.perp_close - 平永续合约仓位
python
Intent.perp_close(
market="ETH/USD",
collateral_token="USDC",
is_long=True,
size_usd=None, # None = 平掉全部仓位
max_slippage=Decimal("0.01"),
protocol="gmx_v2",
)Bridging
跨链桥
Intent.bridge - Cross-chain token transfer
python
Intent.bridge(
token="USDC",
amount=Decimal("1000"),
from_chain="arbitrum",
to_chain="base",
max_slippage=Decimal("0.005"),
preferred_bridge=None, # Optional: specific bridge protocol
)Intent.bridge - 跨链转移代币
python
Intent.bridge(
token="USDC",
amount=Decimal("1000"),
from_chain="arbitrum",
to_chain="base",
max_slippage=Decimal("0.005"),
preferred_bridge=None, # 可选:指定跨链桥协议
)Staking
质押
Intent.stake - Liquid staking deposit
python
Intent.stake(
protocol="lido",
token_in="ETH",
amount=Decimal("10"),
receive_wrapped=True, # Receive wrapped token (e.g., wstETH)
)Intent.unstake - Withdraw from liquid staking
python
Intent.unstake(
protocol="lido",
token_in="wstETH",
amount=Decimal("10"),
)Intent.stake - 流动性质押存款
python
Intent.stake(
protocol="lido",
token_in="ETH",
amount=Decimal("10"),
receive_wrapped=True, # 接收包装后的代币(比如wstETH)
)Intent.unstake - 从流动性质押中提取
python
Intent.unstake(
protocol="lido",
token_in="wstETH",
amount=Decimal("10"),
)Flash Loans
闪电贷
Intent.flash_loan - Borrow and repay in a single transaction
python
Intent.flash_loan(
provider="aave", # "aave", "balancer", "morpho", or "auto"
token="USDC",
amount=Decimal("100000"),
callback_intents=[...], # Intents to execute with the borrowed funds
)Intent.flash_loan - 在同一笔交易中借入并偿还
python
Intent.flash_loan(
provider="aave", # "aave", "balancer", "morpho", 或 "auto"
token="USDC",
amount=Decimal("100000"),
callback_intents=[...], # 用借入资金执行的Intent列表
)Vaults (ERC-4626)
金库(ERC-4626)
Intent.vault_deposit - Deposit into an ERC-4626 vault
python
Intent.vault_deposit(
vault="0x...", # Vault contract address
asset_token="USDC",
amount=Decimal("1000"),
)Intent.vault_redeem - Redeem shares from an ERC-4626 vault
python
Intent.vault_redeem(
vault="0x...",
shares_amount=Decimal("1000"),
)Intent.vault_deposit - 存入ERC-4626金库
python
Intent.vault_deposit(
vault="0x...", # 金库合约地址
asset_token="USDC",
amount=Decimal("1000"),
)Intent.vault_redeem - 从ERC-4626金库赎回份额
python
Intent.vault_redeem(
vault="0x...",
shares_amount=Decimal("1000"),
)Prediction Markets
预测市场
python
Intent.prediction_buy(protocol="polymarket", market="...", amount_usd=Decimal("100"))
Intent.prediction_sell(protocol="polymarket", market="...", amount_shares=Decimal("50"))
Intent.prediction_redeem(protocol="polymarket", market="...")python
Intent.prediction_buy(protocol="polymarket", market="...", amount_usd=Decimal("100"))
Intent.prediction_sell(protocol="polymarket", market="...", amount_shares=Decimal("50"))
Intent.prediction_redeem(protocol="polymarket", market="...")Cross-Chain
跨链
Intent.ensure_balance - Meta-intent that resolves to a (if balance is insufficient) or (if already met). Call before returning from .
BridgeIntentHoldIntent.resolve(market)decide()python
intent = Intent.ensure_balance(
token="USDC",
min_amount=Decimal("1000"),
target_chain="arbitrum",
max_slippage=Decimal("0.005"),
preferred_bridge=None,
)Intent.ensure_balance - 元Intent,如果目标链余额不足会解析为,如果余额足够则返回。在返回前需要调用。
BridgeIntentHoldIntentdecide().resolve(market)python
intent = Intent.ensure_balance(
token="USDC",
min_amount=Decimal("1000"),
target_chain="arbitrum",
max_slippage=Decimal("0.005"),
preferred_bridge=None,
)Must resolve before returning - returns BridgeIntent or HoldIntent
返回前必须解析,返回BridgeIntent或HoldIntent
resolved = intent.resolve(market)
return resolved
undefinedresolved = intent.resolve(market)
return resolved
undefinedControl Flow
控制流
Intent.hold - Do nothing this iteration
python
Intent.hold(reason="RSI in neutral zone")Intent.sequence - Execute multiple intents in order
python
Intent.sequence(
intents=[
Intent.swap(from_token="USDC", to_token="WETH", amount_usd=Decimal("1000")),
Intent.supply(protocol="aave_v3", token="WETH", amount=Decimal("0.5")),
],
description="Buy WETH then supply to Aave",
)Intent.hold - 本次迭代不执行任何操作
python
Intent.hold(reason="RSI处于中性区间")Intent.sequence - 按顺序执行多个Intent
python
Intent.sequence(
intents=[
Intent.swap(from_token="USDC", to_token="WETH", amount_usd=Decimal("1000")),
Intent.supply(protocol="aave_v3", token="WETH", amount=Decimal("0.5")),
],
description="买入WETH然后存入Aave",
)Chained Amounts
链式金额传递
Use to reference the full output of a prior intent:
"all"python
Intent.sequence(intents=[
Intent.swap(from_token="USDC", to_token="WETH", amount_usd=Decimal("1000")),
Intent.supply(protocol="aave_v3", token="WETH", amount="all"), # Uses swap output
])使用可以引用上一个Intent的全部输出:
"all"python
Intent.sequence(intents=[
Intent.swap(from_token="USDC", to_token="WETH", amount_usd=Decimal("1000")),
Intent.supply(protocol="aave_v3", token="WETH", amount="all"), # 使用兑换的全部输出
])Market Data API
市场数据API
The passed to provides these methods:
MarketSnapshotdecide()传入的提供以下方法:
decide()MarketSnapshotPrices
价格
python
price = market.price("WETH") # Decimal, USD price
price = market.price("WETH", quote="USDC") # Price in USDC terms
pd = market.price_data("WETH") # PriceData object
pd.price # Decimal - current price
pd.price_24h_ago # Decimal
pd.change_24h_pct # Decimal
pd.high_24h # Decimal
pd.low_24h # Decimal
pd.timestamp # datetimepython
price = market.price("WETH") # Decimal类型,USD计价价格
price = market.price("WETH", quote="USDC") # USDC计价的价格
pd = market.price_data("WETH") # PriceData对象
pd.price # Decimal - 当前价格
pd.price_24h_ago # Decimal
pd.change_24h_pct # Decimal
pd.high_24h # Decimal
pd.low_24h # Decimal
pd.timestamp # datetimeBalances
余额
python
bal = market.balance("USDC")
bal.balance # Decimal - token amount
bal.balance_usd # Decimal - USD value
bal.symbol # str
bal.address # str - token contract addressTokenBalancebal > Decimal("100")python
bal = market.balance("USDC")
bal.balance # Decimal - 代币数量
bal.balance_usd # Decimal - USD价值
bal.symbol # str
bal.address # str - 代币合约地址TokenBalancebal > Decimal("100")Technical Indicators
技术指标
All indicators accept , (int), and (str, default ).
tokenperiodtimeframe"4h"python
rsi = market.rsi("WETH", period=14, timeframe="4h")
rsi.value # Decimal (0-100)
rsi.is_oversold # bool (value < 30)
rsi.is_overbought # bool (value > 70)
rsi.signal # "BUY" | "SELL" | "HOLD"
macd = market.macd("WETH", fast_period=12, slow_period=26, signal_period=9)
macd.macd_line # Decimal
macd.signal_line # Decimal
macd.histogram # Decimal
macd.is_bullish_crossover # bool
macd.is_bearish_crossover # bool
bb = market.bollinger_bands("WETH", period=20, std_dev=2.0)
bb.upper_band # Decimal
bb.middle_band # Decimal
bb.lower_band # Decimal
bb.bandwidth # Decimal
bb.percent_b # Decimal (0.0 = at lower band, 1.0 = at upper band)
bb.is_squeeze # bool
stoch = market.stochastic("WETH", k_period=14, d_period=3)
stoch.k_value # Decimal
stoch.d_value # Decimal
stoch.is_oversold # bool
stoch.is_overbought # bool
atr_val = market.atr("WETH", period=14)
atr_val.value # Decimal (absolute)
atr_val.value_percent # Decimal, percentage points (2.62 means 2.62%, not 0.0262)
atr_val.is_high_volatility # bool
sma = market.sma("WETH", period=20)
ema = market.ema("WETH", period=12)所有指标都接受、(整数)和(字符串,默认)参数。
tokenperiodtimeframe"4h"python
rsi = market.rsi("WETH", period=14, timeframe="4h")
rsi.value # Decimal (0-100)
rsi.is_oversold # bool (值 < 30)
rsi.is_overbought # bool (值 > 70)
rsi.signal # "BUY" | "SELL" | "HOLD"
macd = market.macd("WETH", fast_period=12, slow_period=26, signal_period=9)
macd.macd_line # Decimal
macd.signal_line # Decimal
macd.histogram # Decimal
macd.is_bullish_crossover # bool
macd.is_bearish_crossover # bool
bb = market.bollinger_bands("WETH", period=20, std_dev=2.0)
bb.upper_band # Decimal
bb.middle_band # Decimal
bb.lower_band # Decimal
bb.bandwidth # Decimal
bb.percent_b # Decimal (0.0 = 在下轨, 1.0 = 在上轨)
bb.is_squeeze # bool
stoch = market.stochastic("WETH", k_period=14, d_period=3)
stoch.k_value # Decimal
stoch.d_value # Decimal
stoch.is_oversold # bool
stoch.is_overbought # bool
atr_val = market.atr("WETH", period=14)
atr_val.value # Decimal (绝对值)
atr_val.value_percent # Decimal, 百分点(2.62代表2.62%,不是0.0262)
atr_val.is_high_volatility # bool
sma = market.sma("WETH", period=20)
ema = market.ema("WETH", period=12)Both return MAData with: .value, .is_price_above, .is_price_below, .signal
两者都返回MAData对象,包含:.value, .is_price_above, .is_price_below, .signal
adx = market.adx("WETH", period=14)
adx.adx # Decimal (0-100, trend strength)
adx.plus_di # Decimal (+DI)
adx.minus_di # Decimal (-DI)
adx.is_strong_trend # bool (adx >= 25)
adx.is_uptrend # bool (+DI > -DI)
adx.is_downtrend # bool (-DI > +DI)
obv = market.obv("WETH")
obv.obv # Decimal (OBV value)
obv.signal_line # Decimal (SMA of OBV)
obv.is_bullish # bool (OBV > signal)
obv.is_bearish # bool (OBV < signal)
cci = market.cci("WETH", period=20)
cci.value # Decimal
cci.is_oversold # bool (value <= -100)
cci.is_overbought # bool (value >= 100)
ich = market.ichimoku("WETH")
ich.tenkan_sen # Decimal (conversion line)
ich.kijun_sen # Decimal (base line)
ich.senkou_span_a # Decimal (leading span A)
ich.senkou_span_b # Decimal (leading span B)
ich.cloud_top # Decimal
ich.cloud_bottom # Decimal
ich.is_bullish_crossover # bool (tenkan > kijun)
ich.is_above_cloud # bool
ich.signal # "BUY" | "SELL" | "HOLD"
undefinedadx = market.adx("WETH", period=14)
adx.adx # Decimal (0-100, 趋势强度)
adx.plus_di # Decimal (+DI)
adx.minus_di # Decimal (-DI)
adx.is_strong_trend # bool (adx >= 25)
adx.is_uptrend # bool (+DI > -DI)
adx.is_downtrend # bool (-DI > +DI)
obv = market.obv("WETH")
obv.obv # Decimal (OBV值)
obv.signal_line # Decimal (OBV的SMA)
obv.is_bullish # bool (OBV > 信号线)
obv.is_bearish # bool (OBV < 信号线)
cci = market.cci("WETH", period=20)
cci.value # Decimal
cci.is_oversold # bool (值 <= -100)
cci.is_overbought # bool (值 >= 100)
ich = market.ichimoku("WETH")
ich.tenkan_sen # Decimal (转换线)
ich.kijun_sen # Decimal (基准线)
ich.senkou_span_a # Decimal (先行带A)
ich.senkou_span_b # Decimal (先行带B)
ich.cloud_top # Decimal
ich.cloud_bottom # Decimal
ich.is_bullish_crossover # bool (转换线上穿基准线)
ich.is_above_cloud # bool
ich.signal # "BUY" | "SELL" | "HOLD"
undefinedMulti-Token Queries
多代币查询
python
prices = market.prices(["WETH", "WBTC"]) # dict[str, Decimal]
balances = market.balances(["USDC", "WETH"]) # dict[str, Decimal]
usd_val = market.balance_usd("WETH") # Decimal - USD value of holdings
total = market.total_portfolio_usd() # Decimalpython
prices = market.prices(["WETH", "WBTC"]) # dict[str, Decimal]
balances = market.balances(["USDC", "WETH"]) # dict[str, Decimal]
usd_val = market.balance_usd("WETH") # Decimal - 持仓的USD价值
total = market.total_portfolio_usd() # DecimalOHLCV Data
OHLCV数据
python
df = market.ohlcv("WETH", timeframe="1h", limit=100) # pd.DataFramepython
df = market.ohlcv("WETH", timeframe="1h", limit=100) # pd.DataFrameColumns: open, high, low, close, volume
列:open, high, low, close, volume
undefinedundefinedPool and DEX Data
池和DEX数据
python
pool = market.pool_price("0x...") # DataEnvelope[PoolPrice]
pool = market.pool_price_by_pair("WETH", "USDC") # DataEnvelope[PoolPrice]
reserves = market.pool_reserves("0x...") # PoolReserves
history = market.pool_history("0x...", resolution="1h") # DataEnvelope[list[PoolSnapshot]]
analytics = market.pool_analytics("0x...") # DataEnvelope[PoolAnalytics]
best = market.best_pool("WETH", "USDC", metric="fee_apr") # DataEnvelope[PoolAnalyticsResult]python
pool = market.pool_price("0x...") # DataEnvelope[PoolPrice]
pool = market.pool_price_by_pair("WETH", "USDC") # DataEnvelope[PoolPrice]
reserves = market.pool_reserves("0x...") # PoolReserves
history = market.pool_history("0x...", resolution="1h") # DataEnvelope[list[PoolSnapshot]]
analytics = market.pool_analytics("0x...") # DataEnvelope[PoolAnalytics]
best = market.best_pool("WETH", "USDC", metric="fee_apr") # DataEnvelope[PoolAnalyticsResult]Price Aggregation and Slippage
价格聚合和滑点
python
twap = market.twap("WETH/USDC", window_seconds=300) # DataEnvelope[AggregatedPrice]
lwap = market.lwap("WETH/USDC") # DataEnvelope[AggregatedPrice]
depth = market.liquidity_depth("0x...") # DataEnvelope[LiquidityDepth]
slip = market.estimate_slippage("WETH", "USDC", Decimal("10000")) # DataEnvelope[SlippageEstimate]
best_dex = market.best_dex_price("WETH", "USDC", Decimal("1")) # BestDexResultpython
twap = market.twap("WETH/USDC", window_seconds=300) # DataEnvelope[AggregatedPrice]
lwap = market.lwap("WETH/USDC") # DataEnvelope[AggregatedPrice]
depth = market.liquidity_depth("0x...") # DataEnvelope[LiquidityDepth]
slip = market.estimate_slippage("WETH", "USDC", Decimal("10000")) # DataEnvelope[SlippageEstimate]
best_dex = market.best_dex_price("WETH", "USDC", Decimal("1")) # BestDexResultLending and Funding Rates
借贷和资金费率
python
rate = market.lending_rate("aave_v3", "USDC", side="supply") # LendingRate
best = market.best_lending_rate("USDC", side="supply") # BestRateResult
fr = market.funding_rate("binance", "ETH-PERP") # FundingRate
spread = market.funding_rate_spread("ETH-PERP", "binance", "hyperliquid") # FundingRateSpreadpython
rate = market.lending_rate("aave_v3", "USDC", side="supply") # LendingRate
best = market.best_lending_rate("USDC", side="supply") # BestRateResult
fr = market.funding_rate("binance", "ETH-PERP") # FundingRate
spread = market.funding_rate_spread("ETH-PERP", "binance", "hyperliquid") # FundingRateSpreadImpermanent Loss
无常损失
python
il = market.il_exposure("position_id", fees_earned=Decimal("50")) # ILExposure
proj = market.projected_il("WETH", "USDC", price_change_pct=Decimal("0.1")) # ProjectedILResultpython
il = market.il_exposure("position_id", fees_earned=Decimal("50")) # ILExposure
proj = market.projected_il("WETH", "USDC", price_change_pct=Decimal("0.1")) # ProjectedILResultPrediction Markets
预测市场
python
mkt = market.prediction("market_id") # PredictionMarket
price = market.prediction_price("market_id", "YES") # Decimal
positions = market.prediction_positions("market_id") # list[PredictionPosition]
orders = market.prediction_orders("market_id") # list[PredictionOrder]python
mkt = market.prediction("market_id") # PredictionMarket
price = market.prediction_price("market_id", "YES") # Decimal
positions = market.prediction_positions("market_id") # list[PredictionPosition]
orders = market.prediction_orders("market_id") # list[PredictionOrder]Yield and Analytics
收益和分析
python
yields = market.yield_opportunities("USDC", min_tvl=100_000, sort_by="apy") # DataEnvelope[list[YieldOpportunity]]
gas = market.gas_price() # GasPrice
health = market.health() # HealthReport
signals = market.wallet_activity(action_types=["SWAP", "LP_OPEN"]) # listpython
yields = market.yield_opportunities("USDC", min_tvl=100_000, sort_by="apy") # DataEnvelope[list[YieldOpportunity]]
gas = market.gas_price() # GasPrice
health = market.health() # HealthReport
signals = market.wallet_activity(action_types=["SWAP", "LP_OPEN"]) # listContext Properties
上下文属性
python
market.chain # str - current chain name
market.wallet_address # str - wallet address
market.timestamp # datetime - snapshot timestamppython
market.chain # str - 当前链名称
market.wallet_address # str - 钱包地址
market.timestamp # datetime - 快照时间戳State Management
状态管理
self.stateload_state()self.statedecide()on_intent_executed()python
def decide(self, market):
# Read state (survives restarts)
last_trade = self.state.get("last_trade_time")
position_id = self.state.get("lp_position_id")
# Write state (persisted automatically after each iteration)
self.state["last_trade_time"] = datetime.now().isoformat()
self.state["consecutive_holds"] = self.state.get("consecutive_holds", 0) + 1Use to clear saved state when starting over: .
--freshalmanak strat run --fresh --onceFor strategies that need custom serialization (e.g., Decimal fields, complex objects), override
and :
get_persistent_state()load_persistent_state()python
def get_persistent_state(self) -> dict:
"""Called by framework to serialize state for persistence."""
return {
"phase": self.state.get("phase", "idle"),
"position_id": self.state.get("position_id"),
"entry_price": str(self.state.get("entry_price", "0")),
}
def load_persistent_state(self, saved: dict) -> None:
"""Called by framework on startup to restore state."""
self.state["phase"] = saved.get("phase", "idle")
self.state["position_id"] = saved.get("position_id")
self.state["entry_price"] = Decimal(saved.get("entry_price", "0"))self.stateload_state()decide()on_intent_executed()self.statepython
def decide(self, market):
# 读取状态(重启后仍然存在)
last_trade = self.state.get("last_trade_time")
position_id = self.state.get("lp_position_id")
# 写入状态(每次迭代后自动持久化)
self.state["last_trade_time"] = datetime.now().isoformat()
self.state["consecutive_holds"] = self.state.get("consecutive_holds", 0) + 1重新开始时可以使用清除保存的状态:。
--freshalmanak strat run --fresh --once如果策略需要自定义序列化(比如Decimal字段、复杂对象),可以重写和:
get_persistent_state()load_persistent_state()python
def get_persistent_state(self) -> dict:
"""框架调用此方法序列化状态用于持久化。"""
return {
"phase": self.state.get("phase", "idle"),
"position_id": self.state.get("position_id"),
"entry_price": str(self.state.get("entry_price", "0")),
}
def load_persistent_state(self, saved: dict) -> None:
"""框架在启动时调用此方法恢复状态。"""
self.state["phase"] = saved.get("phase", "idle")
self.state["position_id"] = saved.get("position_id")
self.state["entry_price"] = Decimal(saved.get("entry_price", "0"))on_intent_executed Callback
on_intent_executed 回调
After execution, access results (position IDs, swap amounts) via the callback. The framework
automatically enriches with protocol-specific data - no manual receipt parsing needed.
resultpython
undefined执行完成后,可以通过回调访问结果(仓位ID、兑换金额等)。框架会自动为填充协议特定的数据,不需要手动解析交易收据。
resultpython
undefinedIn your strategy file, import logging at the top:
在策略文件顶部导入logging:
import logging
import logging
logger = logging.getLogger(name)
logger = logging.getLogger(name)
def on_intent_executed(self, intent, success: bool, result):
if not success:
logger.warning(f"Intent failed: {intent.intent_type}")
return
# Capture LP position ID (enriched automatically by ResultEnricher)
if result.position_id is not None:
self.state["lp_position_id"] = result.position_id
logger.info(f"Opened LP position {result.position_id}")
# Store range bounds for rebalancing strategies (keep as Decimal)
if (
hasattr(intent, "range_lower") and intent.range_lower is not None
and hasattr(intent, "range_upper") and intent.range_upper is not None
):
self.state["range_lower"] = intent.range_lower
self.state["range_upper"] = intent.range_upper
# Capture swap amounts
if result.swap_amounts:
self.state["last_swap"] = {
"amount_in": str(result.swap_amounts.amount_in),
"amount_out": str(result.swap_amounts.amount_out),
}
logger.info(
f"Swapped {result.swap_amounts.amount_in} -> {result.swap_amounts.amount_out}"
)
<!-- almanak-sdk-end: state-management -->
<!-- almanak-sdk-start: configuration -->def on_intent_executed(self, intent, success: bool, result):
if not success:
logger.warning(f"Intent执行失败:{intent.intent_type}")
return
# 捕获LP仓位ID(由ResultEnricher自动填充)
if result.position_id is not None:
self.state["lp_position_id"] = result.position_id
logger.info(f"已开LP仓位 {result.position_id}")
# 存储区间上下限用于再平衡策略(保留Decimal类型)
if (
hasattr(intent, "range_lower") and intent.range_lower is not None
and hasattr(intent, "range_upper") and intent.range_upper is not None
):
self.state["range_lower"] = intent.range_lower
self.state["range_upper"] = intent.range_upper
# 捕获兑换金额
if result.swap_amounts:
self.state["last_swap"] = {
"amount_in": str(result.swap_amounts.amount_in),
"amount_out": str(result.swap_amounts.amount_out),
}
logger.info(
f"兑换完成 {result.swap_amounts.amount_in} -> {result.swap_amounts.amount_out}"
)
<!-- almanak-sdk-end: state-management -->
<!-- almanak-sdk-start: configuration -->Configuration
配置
config.json
config.json
json
{
"strategy_id": "my_rsi_strategy",
"chain": "arbitrum",
"base_token": "WETH",
"quote_token": "USDC",
"rsi_period": 14,
"rsi_oversold": 30,
"rsi_overbought": 70,
"trade_size_usd": 1000,
"max_slippage_bps": 50,
"anvil_funding": {
"USDC": "10000",
"WETH": "5"
}
}Required fields: , .
All other fields are strategy-specific and accessed via .
strategy_idchainself.config.get(key, default)json
{
"strategy_id": "my_rsi_strategy",
"chain": "arbitrum",
"base_token": "WETH",
"quote_token": "USDC",
"rsi_period": 14,
"rsi_oversold": 30,
"rsi_overbought": 70,
"trade_size_usd": 1000,
"max_slippage_bps": 50,
"anvil_funding": {
"USDC": "10000",
"WETH": "5"
}
}必填字段:、。其他字段都是策略特定的,可以通过访问。
strategy_idchainself.config.get(key, default).env
.env
bash
undefinedbash
undefinedRequired
必填
ALMANAK_PRIVATE_KEY=0x...
ALMANAK_PRIVATE_KEY=0x...
RPC access (set at least one)
RPC访问(至少配置一个)
ALCHEMY_API_KEY=your_key
ALCHEMY_API_KEY=your_key
RPC_URL=https://...
RPC_URL=https://...
Optional
可选
ENSO_API_KEY=
ENSO_API_KEY=
COINGECKO_API_KEY=
COINGECKO_API_KEY=
ALMANAK_API_KEY=
ALMANAK_API_KEY=
undefinedundefinedanvil_funding
anvil_funding
When running on Anvil (), the framework auto-funds the wallet
with tokens specified in . Values are in token units (not USD).
<!-- almanak-sdk-end: configuration -->
<!-- almanak-sdk-start: token-resolution -->--network anvilanvil_funding在Anvil上运行时(),框架会自动为钱包充值中指定的代币,值是代币单位(不是USD)。
<!-- almanak-sdk-end: configuration -->
<!-- almanak-sdk-start: token-resolution -->--network anvilanvil_fundingToken Resolution
代币解析
Use for all token lookups. Never hardcode addresses.
get_token_resolver()python
from almanak.framework.data.tokens import get_token_resolver
resolver = get_token_resolver()所有代币查询都应该使用,永远不要硬编码地址。
get_token_resolver()python
from almanak.framework.data.tokens import get_token_resolver
resolver = get_token_resolver()Resolve by symbol
通过符号解析
token = resolver.resolve("USDC", "arbitrum")
token = resolver.resolve("USDC", "arbitrum")
-> ResolvedToken(symbol="USDC", address="0xaf88...", decimals=6, chain="arbitrum")
-> ResolvedToken(symbol="USDC", address="0xaf88...", decimals=6, chain="arbitrum")
Resolve by address
通过地址解析
token = resolver.resolve("0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "arbitrum")
token = resolver.resolve("0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "arbitrum")
Convenience
便捷方法
decimals = resolver.get_decimals("arbitrum", "USDC") # -> 6
address = resolver.get_address("arbitrum", "USDC") # -> "0xaf88..."
decimals = resolver.get_decimals("arbitrum", "USDC") # -> 6
address = resolver.get_address("arbitrum", "USDC") # -> "0xaf88..."
For DEX swaps (auto-wraps native tokens: ETH->WETH, MATIC->WMATIC)
用于DEX兑换(自动包装原生代币:ETH->WETH, MATIC->WMATIC)
token = resolver.resolve_for_swap("ETH", "arbitrum") # -> WETH
token = resolver.resolve_for_swap("ETH", "arbitrum") # -> WETH
Resolve trading pair
解析交易对
usdc, weth = resolver.resolve_pair("USDC", "WETH", "arbitrum")
Resolution order: memory cache -> disk cache -> static registry -> gateway on-chain lookup.
Never default to 18 decimals. If the token is unknown, `TokenNotFoundError` is raised.
<!-- almanak-sdk-end: token-resolution -->
<!-- almanak-sdk-start: backtesting -->usdc, weth = resolver.resolve_pair("USDC", "WETH", "arbitrum")
解析顺序:内存缓存 -> 磁盘缓存 -> 静态注册表 -> gateway链上查询。
永远不要默认用18位小数,如果代币不存在会抛出`TokenNotFoundError`。
<!-- almanak-sdk-end: token-resolution -->
<!-- almanak-sdk-start: backtesting -->Backtesting
回测
PnL Backtest (historical prices, no on-chain execution)
PnL回测(历史价格,无链上执行)
bash
almanak strat backtest pnl -s my_strategy \
--start 2024-01-01 --end 2024-06-01 \
--initial-capital 10000bash
almanak strat backtest pnl -s my_strategy \
--start 2024-01-01 --end 2024-06-01 \
--initial-capital 10000Paper Trading (Anvil fork with real execution, PnL tracking)
模拟交易(Anvil分叉链真实执行,PnL跟踪)
bash
almanak strat backtest paper -s my_strategy \
--duration 3600 --interval 60 \
--initial-capital 10000Paper trading runs the full strategy loop on an Anvil fork with real transaction
execution, equity curve tracking, and JSON result logs.
bash
almanak strat backtest paper -s my_strategy \
--duration 3600 --interval 60 \
--initial-capital 10000模拟交易在Anvil分叉链上运行完整的策略循环,包含真实交易执行、权益曲线跟踪和JSON结果日志。
Parameter Sweep
参数扫描
bash
almanak strat backtest sweep -s my_strategy \
--start 2024-01-01 --end 2024-06-01 \
--param "rsi_oversold:20,25,30" \
--param "rsi_overbought:70,75,80"Runs the PnL backtest across all parameter combinations and ranks by Sharpe ratio.
bash
almanak strat backtest sweep -s my_strategy \
--start 2024-01-01 --end 2024-06-01 \
--param "rsi_oversold:20,25,30" \
--param "rsi_overbought:70,75,80"会在所有参数组合上运行PnL回测,并按夏普比率排序。
Programmatic Backtesting
编程式回测
python
from almanak.framework.backtesting import BacktestEngine
engine = BacktestEngine(
strategy_class=MyStrategy,
config={...},
start_date="2024-01-01",
end_date="2024-06-01",
initial_capital=10000,
)
results = engine.run()
results.sharpe_ratio
results.max_drawdown
results.total_return
results.plot() # Matplotlib equity curvepython
from almanak.framework.backtesting import BacktestEngine
engine = BacktestEngine(
strategy_class=MyStrategy,
config={...},
start_date="2024-01-01",
end_date="2024-06-01",
initial_capital=10000,
)
results = engine.run()
results.sharpe_ratio
results.max_drawdown
results.total_return
results.plot() # Matplotlib权益曲线Backtesting Limitations
回测限制
- OHLCV data: The PnL backtester uses historical close prices from CoinGecko. Indicators that require OHLCV data (ATR, Stochastic, Ichimoku) need a paid CoinGecko tier or an external data source.
- RPC for paper trading: Paper trading requires an RPC endpoint. Alchemy free tier is recommended for performance; public RPCs work but are slow.
- No CWD auto-discovery: Backtest CLI commands (,
backtest pnl,backtest paper) require an explicitbacktest sweepflag. They do not auto-discover strategies from the current directory like-s strategy_namedoes.strat run - Percentage fields: and similar
total_return_pctresult fields are decimal fractions (0.33 = 33%), not percentages._pct
- OHLCV数据:PnL回测器使用CoinGecko的历史收盘价。需要OHLCV数据的指标(ATR、Stochastic、Ichimoku)需要付费版CoinGecko或者外部数据源。
- 模拟交易的RPC:模拟交易需要RPC端点,推荐使用Alchemy免费层以获得更好的性能,公共RPC也可以运行但速度较慢。
- 无CWD自动发现:回测CLI命令(、
backtest pnl、backtest paper)需要显式指定backtest sweep参数,不会像-s strategy_name那样从当前目录自动发现策略。strat run - 百分比字段:和类似的
total_return_pct结果字段是小数形式(0.33 = 33%),不是百分比数值。_pct
CLI Commands
CLI命令
Strategy Management
策略管理
bash
almanak strat new # Interactive strategy scaffolding
almanak strat new -t mean_reversion -n my_rsi -c arbitrum # Non-interactive
almanak strat demo # Browse and copy a working demo strategyTemplates: , , , , , ,
blankdynamic_lpmean_reversionbollingerbasis_tradelending_loopcopy_traderbash
almanak strat new # 交互式生成策略脚手架
almanak strat new -t mean_reversion -n my_rsi -c arbitrum # 非交互式生成
almanak strat demo # 浏览并复制可运行的演示策略模板: 、、、、、、
blankdynamic_lpmean_reversionbollingerbasis_tradelending_loopcopy_traderRunning Strategies
运行策略
bash
almanak strat run --once # Single iteration (from strategy dir)
almanak strat run -d path/to/strat --once # Explicit directory
almanak strat run --network anvil --once # Local Anvil fork
almanak strat run --interval 30 # Continuous (30s between iterations)
almanak strat run --dry-run --once # No transactions submitted
almanak strat run --fresh --once # Clear state before running
almanak strat run --id abc123 --once # Resume previous run
almanak strat run --dashboard # Launch live monitoring dashboardbash
almanak strat run --once # 单次迭代(在策略目录下运行)
almanak strat run -d path/to/strat --once # 指定策略目录
almanak strat run --network anvil --once # 本地Anvil分叉链运行
almanak strat run --interval 30 # 连续运行(迭代间隔30秒)
almanak strat run --dry-run --once # 不提交交易
almanak strat run --fresh --once # 运行前清除状态
almanak strat run --id abc123 --once # 恢复之前的运行Backtesting
回测
bash
almanak strat backtest pnl -s my_strategy # Historical PnL simulation
almanak strat backtest paper -s my_strategy # Paper trading on Anvil fork
almanak strat backtest sweep -s my_strategy # Parameter sweep optimizationbash
almanak strat backtest pnl -s my_strategy # 历史PnL模拟
almanak strat backtest paper -s my_strategy # Anvil分叉链模拟交易
almanak strat backtest sweep -s my_strategy # 参数扫描优化Teardown
关闭策略
bash
almanak strat teardown plan # Preview teardown intents
almanak strat teardown execute # Execute teardownbash
almanak strat teardown plan # 预览关闭Intent
almanak strat teardown execute # 执行关闭Gateway
Gateway
bash
almanak gateway # Start standalone gateway
almanak gateway --network anvil # Gateway for local Anvil testing
almanak gateway --port 50052 # Custom portbash
almanak gateway # 启动独立gateway
almanak gateway --network anvil # 用于本地Anvil测试的gateway
almanak gateway --port 50052 # 自定义端口Agent Skill Management
Agent技能管理
bash
almanak agent install # Auto-detect platforms and install
almanak agent install -p claude # Install for specific platform
almanak agent install -p all # Install for all 9 platforms
almanak agent update # Update installed skill files
almanak agent status # Check installation statusbash
almanak agent install # 自动检测平台并安装
almanak agent install -p claude # 为指定平台安装
almanak agent install -p all # 为所有9个平台安装
almanak agent update # 更新已安装的技能文件
almanak agent status # 检查安装状态Documentation
文档
bash
almanak docs path # Path to bundled LLM docs
almanak docs dump # Print full LLM docs
almanak docs agent-skill # Path to bundled agent skill
almanak docs agent-skill --dump # Print agent skill contentbash
almanak docs path # 内置LLM文档的路径
almanak docs dump # 打印完整LLM文档
almanak docs agent-skill # 内置Agent技能的路径
almanak docs agent-skill --dump # 打印Agent技能内容Supported Chains and Protocols
支持的链和协议
Chains
链
| Chain | Enum Value | Config Name |
|---|---|---|
| Ethereum | | |
| Arbitrum | | |
| Optimism | | |
| Base | | |
| Avalanche | | |
| Polygon | | |
| BSC | | |
| Sonic | | |
| Plasma | | |
| Blast | | |
| Mantle | | |
| Berachain | | |
| 区块链 | 枚举值 | 配置名称 |
|---|---|---|
| Ethereum | | |
| Arbitrum | | |
| Optimism | | |
| Base | | |
| Avalanche | | |
| Polygon | | |
| BSC | | |
| Sonic | | |
| Plasma | | |
| Blast | | |
| Mantle | | |
| Berachain | | |
Protocols
协议
| Protocol | Enum Value | Type | Config Name |
|---|---|---|---|
| Uniswap V3 | | DEX / LP | |
| PancakeSwap V3 | | DEX / LP | |
| SushiSwap V3 | | DEX / LP | |
| TraderJoe V2 | | DEX / LP | |
| Aerodrome | | DEX / LP | |
| Enso | | Aggregator | |
| Pendle | | Yield | |
| MetaMorpho | | Lending | |
| LiFi | | Bridge | |
| Vault | | ERC-4626 | |
| Curve | | DEX / LP | |
| Balancer | | DEX / LP | |
| Aave V3 | * | Lending | |
| Morpho Blue | * | Lending | |
| Compound V3 | * | Lending | |
| GMX V2 | * | Perps | |
| Hyperliquid | * | Perps | |
| Polymarket | * | Prediction | |
| Kraken | * | CEX | |
| Lido | * | Staking | |
| Lagoon | * | Vault | |
* These protocols do not have a enum value. Use the string config name (e.g., ) in intents. They are resolved by the intent compiler and transaction builder directly.
Protocolprotocol="aave_v3"| 协议 | 枚举值 | 类型 | 配置名称 |
|---|---|---|---|
| Uniswap V3 | | DEX / LP | |
| PancakeSwap V3 | | DEX / LP | |
| SushiSwap V3 | | DEX / LP | |
| TraderJoe V2 | | DEX / LP | |
| Aerodrome | | DEX / LP | |
| Enso | | 聚合器 | |
| Pendle | | 收益 | |
| MetaMorpho | | 借贷 | |
| LiFi | | 跨链桥 | |
| Vault | | ERC-4626 | |
| Curve | | DEX / LP | |
| Balancer | | DEX / LP | |
| Aave V3 | * | 借贷 | |
| Morpho Blue | * | 借贷 | |
| Compound V3 | * | 借贷 | |
| GMX V2 | * | 永续合约 | |
| Hyperliquid | * | 永续合约 | |
| Polymarket | * | 预测市场 | |
| Kraken | * | CEX | |
| Lido | * | 质押 | |
| Lagoon | * | 金库 | |
* 这些协议没有枚举值,在Intent中使用字符串配置名称即可(比如),它们会由Intent编译器和交易构建器直接解析。
Protocolprotocol="aave_v3"Networks
网络
| Network | Enum Value | Description |
|---|---|---|
| Mainnet | | Production chains |
| Anvil | | Local fork for testing |
| Sepolia | | Testnet |
| 网络 | 枚举值 | 描述 |
|---|---|---|
| 主网 | | 生产链 |
| Anvil | | 本地测试分叉链 |
| Sepolia | | 测试网 |
Protocol-Specific Notes
协议特定说明
GMX V2 (Perpetuals)
- Market format: Use slash separator: ,
"BTC/USD","ETH/USD"(not dash)."LINK/USD" - Two-step execution: GMX V2 uses a keeper-based execution model. When you call , the SDK submits an order creation transaction. A GMX keeper then executes the actual position change in a separate transaction.
Intent.perp_open()fires when the order creation TX confirms, not when the keeper executes the position. Strategies should poll position state before relying on it.on_intent_executed(success=True) - Minimum position size: GMX V2 enforces a minimum position size of approximately $11 net of fees. Orders below this threshold are silently rejected by the keeper with no on-chain error.
- Collateral approvals: Handled automatically by the intent compiler (same as LP opens).
- Position monitoring: may not return positions immediately after opening due to keeper delay. Allow a few seconds before querying.
get_all_positions() - Supported chains: Arbitrum, Avalanche.
- Collateral tokens: USDC, USDT (chain-dependent).
GMX V2(永续合约)
- 市场格式:使用斜杠分隔:、
"BTC/USD"、"ETH/USD"(不要用短横线)。"LINK/USD" - 两步执行:GMX V2使用keeper执行模型,当你调用时,SDK提交订单创建交易,然后GMX keeper会在单独的交易中执行实际的仓位变更。
Intent.perp_open()会在订单创建交易确认时触发,不是在keeper执行仓位变更时触发。策略在依赖仓位状态前应该轮询仓位状态。on_intent_executed(success=True) - 最小仓位规模:GMX V2要求最小仓位规模约为11美元(扣除手续费后),低于此阈值的订单会被keeper静默拒绝,链上不会有错误提示。
- 抵押品授权:由Intent编译器自动处理(和开LP仓位一样)。
- 仓位监控:开仓后可能不会立即返回仓位,因为keeper执行有延迟,查询前请等待几秒。
get_all_positions() - 支持的链:Arbitrum、Avalanche。
- 抵押代币:USDC、USDT(依链而定)。
Common Patterns
常用模式
RSI Mean Reversion (Trading)
RSI均值回归(交易)
python
def decide(self, market):
rsi = market.rsi(self.base_token, period=self.rsi_period)
quote_bal = market.balance(self.quote_token)
base_bal = market.balance(self.base_token)
if rsi.is_oversold and quote_bal.balance_usd >= self.trade_size:
return Intent.swap(
from_token=self.quote_token, to_token=self.base_token,
amount_usd=self.trade_size, max_slippage=Decimal("0.005"),
)
if rsi.is_overbought and base_bal.balance_usd >= self.trade_size:
return Intent.swap(
from_token=self.base_token, to_token=self.quote_token,
amount_usd=self.trade_size, max_slippage=Decimal("0.005"),
)
return Intent.hold(reason=f"RSI={rsi.value:.1f} in neutral zone")python
def decide(self, market):
rsi = market.rsi(self.base_token, period=self.rsi_period)
quote_bal = market.balance(self.quote_token)
base_bal = market.balance(self.base_token)
if rsi.is_oversold and quote_bal.balance_usd >= self.trade_size:
return Intent.swap(
from_token=self.quote_token, to_token=self.base_token,
amount_usd=self.trade_size, max_slippage=Decimal("0.005"),
)
if rsi.is_overbought and base_bal.balance_usd >= self.trade_size:
return Intent.swap(
from_token=self.base_token, to_token=self.quote_token,
amount_usd=self.trade_size, max_slippage=Decimal("0.005"),
)
return Intent.hold(reason=f"RSI={rsi.value:.1f} in neutral zone")LP Rebalancing
LP再平衡
python
def decide(self, market):
price = market.price(self.base_token)
position_id = self.state.get("lp_position_id")
if position_id:
# Check if price is out of range - close and reopen
if price < self.state.get("range_lower") or price > self.state.get("range_upper"):
return Intent.lp_close(position_id=position_id, protocol="uniswap_v3")
# Open new position centered on current price
atr = market.atr(self.base_token)
half_range = price * (atr.value_percent / Decimal("100")) * 2 # value_percent is percentage points
return Intent.lp_open(
pool="WETH/USDC",
amount0=Decimal("1"), amount1=Decimal("2000"),
range_lower=price - half_range,
range_upper=price + half_range,
)python
def decide(self, market):
price = market.price(self.base_token)
position_id = self.state.get("lp_position_id")
if position_id:
# 检查价格是否超出区间,如果是则关闭并重开
if price < self.state.get("range_lower") or price > self.state.get("range_upper"):
return Intent.lp_close(position_id=position_id, protocol="uniswap_v3")
# 开以当前价格为中心的新仓位
atr = market.atr(self.base_token)
half_range = price * (atr.value_percent / Decimal("100")) * 2 # value_percent是百分点
return Intent.lp_open(
pool="WETH/USDC",
amount0=Decimal("1"), amount1=Decimal("2000"),
range_lower=price - half_range,
range_upper=price + half_range,
)Multi-Step with IntentSequence
多步IntentSequence
python
def decide(self, market):
return Intent.sequence(
intents=[
Intent.swap(from_token="USDC", to_token="WETH", amount_usd=Decimal("5000")),
Intent.supply(protocol="aave_v3", token="WETH", amount="all"),
Intent.borrow(
protocol="aave_v3",
collateral_token="WETH", collateral_amount=Decimal("0"),
borrow_token="USDC", borrow_amount=Decimal("3000"),
),
],
description="Leverage loop: buy WETH, supply, borrow USDC",
)python
def decide(self, market):
return Intent.sequence(
intents=[
Intent.swap(from_token="USDC", to_token="WETH", amount_usd=Decimal("5000")),
Intent.supply(protocol="aave_v3", token="WETH", amount="all"),
Intent.borrow(
protocol="aave_v3",
collateral_token="WETH", collateral_amount=Decimal("0"),
borrow_token="USDC", borrow_amount=Decimal("3000"),
),
],
description="Leverage loop: buy WETH, supply, borrow USDC",
)Alerting
告警
python
from almanak.framework.alerting import AlertManager
class MyStrategy(IntentStrategy):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.alerts = AlertManager.from_config(self.config.get("alerting", {}))
def decide(self, market):
rsi = market.rsi("WETH")
if rsi.value < 20:
self.alerts.send("Extreme oversold: RSI={:.1f}".format(rsi.value), level="warning")
# ... trading logicpython
from almanak.framework.alerting import AlertManager
class MyStrategy(IntentStrategy):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.alerts = AlertManager.from_config(self.config.get("alerting", {}))
def decide(self, market):
rsi = market.rsi("WETH")
if rsi.value < 20:
self.alerts.send("Extreme oversold: RSI={:.1f}".format(rsi.value), level="warning")
# ... 交易逻辑Safe Teardown
安全关闭
Implement teardown so the strategy can cleanly exit positions:
python
def supports_teardown(self) -> bool:
return True
def generate_teardown_intents(self, mode, market=None) -> list[Intent]:
intents = []
position_id = self.state.get("lp_position_id")
if position_id:
intents.append(Intent.lp_close(position_id=position_id))
# Swap all base token back to quote
intents.append(Intent.swap(
from_token=self.base_token, to_token=self.quote_token,
amount="all", max_slippage=Decimal("0.03"),
))
return intents实现关闭逻辑,让策略可以干净地平仓退出:
python
def supports_teardown(self) -> bool:
return True
def generate_teardown_intents(self, mode, market=None) -> list[Intent]:
intents = []
position_id = self.state.get("lp_position_id")
if position_id:
intents.append(Intent.lp_close(position_id=position_id))
# 将所有基础代币换回计价代币
intents.append(Intent.swap(
from_token=self.base_token, to_token=self.quote_token,
amount="all", max_slippage=Decimal("0.03"),
))
return intentsError Handling
错误处理
Always wrap in try/except and return on error:
decide()Intent.hold()python
def decide(self, market):
try:
# ... strategy logic
except Exception as e:
logger.exception(f"Error in decide(): {e}")
return Intent.hold(reason=f"Error: {e}")总是用try/except包裹,出错时返回:
decide()Intent.hold()python
def decide(self, market):
try:
# ... 策略逻辑
except Exception as e:
logger.exception(f"Error in decide(): {e}")
return Intent.hold(reason=f"Error: {e}")Execution Failure Tracking (Circuit Breaker)
执行失败跟踪(断路器)
The framework retries each failed intent up to (default: 3) with
exponential backoff. However, after all retries are exhausted the strategy
continues running and will attempt the same trade on the next iteration.
Without a circuit breaker, this creates an infinite loop of reverted transactions
that burn gas without any hope of success.
max_retriesAlways track consecutive execution failures in persistent state and stop
trading (or enter an extended cooldown) after a threshold is reached:
python
MAX_CONSECUTIVE_FAILURES = 3 # Stop after 3 rounds of failed intents
FAILURE_COOLDOWN_SECONDS = 1800 # 30-min cooldown before retrying
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.consecutive_failures = 0
self.failure_cooldown_until = 0.0
def decide(self, market):
try:
now = time.time()
# Circuit breaker: skip trading while in cooldown
if now < self.failure_cooldown_until:
remaining = int(self.failure_cooldown_until - now)
return Intent.hold(
reason=f"Circuit breaker active, cooldown {remaining}s remaining"
)
# Circuit breaker: enter cooldown after too many failures
if self.consecutive_failures >= MAX_CONSECUTIVE_FAILURES:
self.failure_cooldown_until = now + FAILURE_COOLDOWN_SECONDS
self.consecutive_failures = 0
logger.warning(
f"Circuit breaker tripped after {MAX_CONSECUTIVE_FAILURES} "
f"consecutive failures, cooling down {FAILURE_COOLDOWN_SECONDS}s"
)
return Intent.hold(reason="Circuit breaker tripped")
# ... normal strategy logic ...
except Exception as e:
logger.exception(f"Error in decide(): {e}")
return Intent.hold(reason=f"Error: {e}")
def on_intent_executed(self, intent, success: bool, result):
if success:
self.consecutive_failures = 0 # Reset on success
else:
self.consecutive_failures += 1
logger.warning(
f"Intent failed ({self.consecutive_failures}/{MAX_CONSECUTIVE_FAILURES})"
)
def get_persistent_state(self) -> dict:
return {
"consecutive_failures": self.consecutive_failures,
"failure_cooldown_until": self.failure_cooldown_until,
}
def load_persistent_state(self, state: dict) -> None:
self.consecutive_failures = int(state.get("consecutive_failures", 0))
self.failure_cooldown_until = float(state.get("failure_cooldown_until", 0))Important: Only update trade-timing state (e.g. ) inside
when , not when the intent is created. Setting
it at creation time means a failed trade still resets the interval timer, causing
the strategy to wait before retrying — or worse, to keep retrying on a fixed
schedule with no failure awareness.
last_trade_tson_intent_executedsuccess=True框架会对每个失败的Intent最多重试次(默认3次),采用指数退避。但是所有重试耗尽后,策略会继续运行,并在下一次迭代中尝试执行同样的交易。如果没有断路器,会产生无限循环的回滚交易,白白消耗gas却不可能成功。
max_retries一定要在持久化状态中跟踪连续执行失败次数,达到阈值后停止交易(或者进入较长的冷却期):
python
MAX_CONSECUTIVE_FAILURES = 3 # 连续3次Intent失败后停止
FAILURE_COOLDOWN_SECONDS = 1800 # 冷却30分钟后再重试
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.consecutive_failures = 0
self.failure_cooldown_until = 0.0
def decide(self, market):
try:
now = time.time()
# 断路器:冷却期内跳过交易
if now < self.failure_cooldown_until:
remaining = int(self.failure_cooldown_until - now)
return Intent.hold(
reason=f"Circuit breaker active, cooldown {remaining}s remaining"
)
# 断路器:失败次数过多进入冷却期
if self.consecutive_failures >= MAX_CONSECUTIVE_FAILURES:
self.failure_cooldown_until = now + FAILURE_COOLDOWN_SECONDS
self.consecutive_failures = 0
logger.warning(
f"Circuit breaker tripped after {MAX_CONSECUTIVE_FAILURES} "
f"consecutive failures, cooling down {FAILURE_COOLDOWN_SECONDS}s"
)
return Intent.hold(reason="Circuit breaker tripped")
# ... 正常策略逻辑 ...
except Exception as e:
logger.exception(f"Error in decide(): {e}")
return Intent.hold(reason=f"Error: {e}")
def on_intent_executed(self, intent, success: bool, result):
if success:
self.consecutive_failures = 0 # 成功时重置计数
else:
self.consecutive_failures += 1
logger.warning(
f"Intent failed ({self.consecutive_failures}/{MAX_CONSECUTIVE_FAILURES})"
)
def get_persistent_state(self) -> dict:
return {
"consecutive_failures": self.consecutive_failures,
"failure_cooldown_until": self.failure_cooldown_until,
}
def load_persistent_state(self, state: dict) -> None:
self.consecutive_failures = int(state.get("consecutive_failures", 0))
self.failure_cooldown_until = float(state.get("failure_cooldown_until", 0))重要: 仅在中时更新交易时间状态(比如),不要在创建Intent时更新。如果创建时就更新,失败的交易仍然会重置间隔计时器,导致策略等待后才重试,更糟的是会按固定 schedule 不断重试,完全感知不到失败。
on_intent_executedsuccess=Truelast_trade_tsHandling Gas and Slippage Errors (Sadflow Hook)
处理Gas和滑点错误(Sadflow钩子)
Override to react to specific error types during intent
retries. This hook is called before each retry attempt and lets you modify the
transaction (e.g. increase gas or slippage) or abort early:
on_sadflow_enterpython
from almanak.framework.intents.state_machine import SadflowAction
class MyStrategy(IntentStrategy):
def on_sadflow_enter(self, error_type, attempt, context):
# Abort immediately on insufficient funds — retrying won't help
if error_type == "INSUFFICIENT_FUNDS":
return SadflowAction.abort("Insufficient funds, stopping retries")
# Increase gas limit for gas-related errors
if error_type == "GAS_ERROR" and context.action_bundle:
modified = self._increase_gas(context.action_bundle)
return SadflowAction.modify(modified, reason="Increased gas limit")
# For slippage errors ("Too little received"), abort after 1 attempt
# since retrying with the same parameters will produce the same result
if error_type == "SLIPPAGE" and attempt >= 1:
return SadflowAction.abort("Slippage error persists, aborting")
# Default: let the framework retry with backoff
return NoneError types passed to (from in ):
on_sadflow_enter_categorize_errorstate_machine.py- — gas estimation failed or gas limit exceeded
GAS_ERROR - — wallet balance too low
INSUFFICIENT_FUNDS - — "Too little received" or similar DEX revert
SLIPPAGE - — transaction confirmation timed out
TIMEOUT - — nonce mismatch or conflict
NONCE_ERROR - — generic transaction revert
REVERT - — RPC or API rate limit hit
RATE_LIMIT - — connection or network failure
NETWORK_ERROR - — unsupported protocol/chain (non-retriable)
COMPILATION_PERMANENT - — unclassified error
None
重写可以在Intent重试时响应特定的错误类型,这个钩子会在每次重试前调用,让你可以修改交易(比如增加gas或滑点)或者提前中止:
on_sadflow_enterpython
from almanak.framework.intents.state_machine import SadflowAction
class MyStrategy(IntentStrategy):
def on_sadflow_enter(self, error_type, attempt, context):
# 余额不足直接中止,重试没用
if error_type == "INSUFFICIENT_FUNDS":
return SadflowAction.abort("Insufficient funds, stopping retries")
# gas相关错误增加gas limit
if error_type == "GAS_ERROR" and context.action_bundle:
modified = self._increase_gas(context.action_bundle)
return SadflowAction.modify(modified, reason="Increased gas limit")
# 滑点错误(「到账金额过少」)尝试1次后中止,因为用同样参数重试结果一样
if error_type == "SLIPPAGE" and attempt >= 1:
return SadflowAction.abort("Slippage error persists, aborting")
# 默认:让框架按退避策略重试
return None传入的错误类型(来自中的):
on_sadflow_enterstate_machine.py_categorize_error- — gas预估失败或gas limit超出
GAS_ERROR - — 钱包余额不足
INSUFFICIENT_FUNDS - — 「到账金额过少」或类似的DEX回滚
SLIPPAGE - — 交易确认超时
TIMEOUT - — nonce不匹配或冲突
NONCE_ERROR - — 通用交易回滚
REVERT - — 触发RPC或API限流
RATE_LIMIT - — 连接或网络故障
NETWORK_ERROR - — 不支持的协议/链(不可重试)
COMPILATION_PERMANENT - — 未分类错误
None
Going Live Checklist
上线检查清单
Before deploying to mainnet:
- Test on Anvil with until
--network anvil --onceworks correctlydecide() - Run on mainnet to verify compilation without submitting transactions
--dry-run --once - Use (token units) for swaps if
amount=causes reverts (see swap reference above)amount_usd= - Override /
get_persistent_state()if your strategy tracks positions or phase stateload_persistent_state() - Verify token approvals for all protocols used (auto-handled for most, but verify on first run)
- Fund wallet on the correct chain with sufficient tokens plus gas (ETH/AVAX/MATIC)
- Note your instance ID after first successful iteration (needed for resume)
--id - Start with small amounts and monitor the first few iterations
部署到主网前:
- 先用在Anvil上测试,直到
--network anvil --once逻辑完全正确decide() - 在主网运行,验证编译逻辑正常,不会提交交易
--dry-run --once - 如果导致回滚,兑换交易改用
amount_usd=(代币单位)(参考上文兑换参考)amount= - 如果策略跟踪仓位或阶段状态,重写/
get_persistent_state()load_persistent_state() - 验证所有用到的协议的代币授权(大部分会自动处理,但首次运行需要验证)
- 为对应链的钱包充值足够的代币和gas(ETH/AVAX/MATIC)
- 首次成功迭代后记录实例ID(后续用恢复运行需要)
--id - 先用小额资金运行,监控前几次迭代
Troubleshooting
问题排查
| Error | Cause | Fix |
|---|---|---|
| Token symbol not in registry | Use exact symbol (e.g., "WETH" not "ETH" for swaps). Check |
| Gateway not running | Use |
| Missing .env | Add |
| Foundry not installed | Install: |
| Insufficient price history | The gateway needs time to accumulate data. Try a longer timeframe or wait. |
| Wallet doesn't have enough tokens | For Anvil: add |
| Trade too large or pool illiquid | Increase |
| Placeholder prices used for slippage calculation, or stale price data | Ensure real price feeds are active (not placeholder). Implement |
| Transactions keep reverting after max retries | Strategy re-emits the same failing intent on subsequent iterations | Track |
| Gas wasted on reverted transactions | No circuit breaker; framework retries 3x per intent, then strategy retries next iteration indefinitely | Implement |
| Intent compilation fails | Wrong parameter types | Ensure amounts are |
| 错误 | 原因 | 修复方案 |
|---|---|---|
| 代币符号不在注册表中 | 使用准确的符号(比如兑换用"WETH"不是"ETH"),检查 |
| Gateway未运行 | 使用 |
| 缺少.env配置 | 在 |
| 未安装Foundry | 安装命令: |
| 价格历史不足 | Gateway需要时间积累数据,尝试更长的时间周期或者等待。 |
| 钱包代币不足 | Anvil环境:在config.json中添加 |
| 交易金额太大或者池流动性不足 | 提高 |
| 滑点计算用了占位价格,或者价格数据过时 | 确保真实价格源正常运行(不是占位数据)。实现 |
| 达到最大重试次数后交易仍然反复回滚 | 策略在后续迭代中重复发出相同的失败Intent | 在持久化状态中跟踪 |
| 回滚交易浪费gas | 没有断路器;框架每个Intent重试3次,之后策略下次迭代会无限重试 | 实现 |
| Intent编译失败 | 参数类型错误 | 确保金额是 |
Debugging Tips
调试技巧
- Use flag for detailed logging:
--verbosealmanak strat run --once --verbose - Use to test decide() without submitting transactions
--dry-run - Use for machine-readable JSON logs
--log-file out.json - Check strategy state: persists between iterations
self.state - Paper trade first: runs real execution on Anvil
almanak strat backtest paper -s my_strategy
- 使用参数获取详细日志:
--verbosealmanak strat run --once --verbose - 使用测试decide()逻辑,不会提交交易
--dry-run - 使用生成机器可读的JSON日志
--log-file out.json - 检查策略状态:会在迭代之间持久化
self.state - 先做模拟交易:在Anvil上运行真实执行逻辑
almanak strat backtest paper -s my_strategy