Conditions
How a condition works, the values it can read, and what only JSON can express.
A condition is a rule that is true or false. The bot checks it at a fixed place in the strategy, and goes on only when it is true. A strategy has conditions in these places:
| Block | JSON | When the bot checks it |
|---|---|---|
| Buy only if | entry.if | At once, when the trigger starts the check. No condition: every time. |
| Then buy if or After the wait, buy if | entry.confirm | After the wait, or on the answers of the AI steps. |
| Run this step only if | entry.ai.steps[n].if | Before an AI step after the first. If it is false, the chain stops there. |
| Sell when or Ask the AI when | exits[n].if | On every price change and every second. |
| Then sell if | exits[n].confirm | On the answers of an AI sell check. |
Rows that must all hold
In the workflow, a condition is a list of rows. All rows must hold. Each row has:
- A value, for example
token.buyers. - Its window, if the value has one, for example 60 seconds.
- A comparison.
- The number or word to compare with.
Select Add a condition to add the first row, and And also to add more. Select the "x" on a row to remove it. A condition with no rows is "Always": the check goes on every time.
The comparisons are:
| In the workflow | In JSON | For |
|---|---|---|
| more than | > | Numbers |
| at least | >= | Numbers |
| less than | < | Numbers |
| at most | <= | Numbers |
| is | == | Numbers and words |
| is not | != | Numbers and words |
| is yes | the value alone | Yes or no values |
| is no | not | Yes or no values |
The yes or no values are token.graduated, token.creator_sold and event.first_buy, and AI answers or webhook fields of the yes or no type. All other rule values are numbers.
In JSON, a value is {"f": "<name>"}, with "args" for its window. A comparison has the value first and the number second. Rows that must all hold are an and:
{
"and": [
{ ">=": [{ "f": "token.buyers", "args": [60] }, 30] },
{ "not": { "f": "token.creator_sold" } }
]
}Windows
Some values count what happened in a recent window, for example token.buyers(window_s): the distinct wallets that bought in the last window_s seconds. The window is in seconds, and it is at most 3600 (one hour). The engine keeps one hour of trades and prices for each token.
token.price_change(window_s) is the price now divided by the price window_s seconds ago, so 1 is flat and 1.5 is up 50%. Until the engine has watched a pool for that long, it has no value.
Missing values
A value can be unknown, for example token.age_s for a token that launched before the engine started to watch it. A comparison with an unknown value is false. So a rule over missing data never buys and never sells. You do not need to check for missing values.
Prices
token.price is a price in wei per the token's smallest unit. For a token with 18 decimals (every Pons token), that is ETH per whole token.
The workflow shows and takes prices in ETH per whole token when it knows the token's decimals. The JSON keeps the stored number. For a token with fewer decimals, the stored number is larger: ETH per whole token times 10 to the power of (18 minus its decimals). Read prices per whole token.
Where each value can be used
Values come in four groups. Not every group can be used everywhere:
| Group | Entry conditions | Sell rules |
|---|---|---|
token.*: the token being considered | Yes | Yes |
event.*: the trade or launch that started the check | Yes, but not with a timer or a webhook trigger | No |
position.*: the open position | No | Yes |
bot.*: the bot | Yes | Yes |
The editor offers only the values that fit where you are. The engine refuses a value that is used where it cannot be known.
The token
| The token | Meaning |
|---|---|
token.age_s | Seconds since the token launched (null if the launch was before the engine started). |
token.price | Price in wei per the token's smallest unit: ETH per whole token for an 18-decimal token (every Pons token). |
token.price_change(window_s) | Price now divided by the price window_s seconds ago (1 = flat; at most 3600). Until the engine has watched a pool that long, null. |
token.buyers(window_s) | Distinct buying wallets in the last window_s seconds. |
token.buys(window_s) | Number of buys in the last window_s seconds. |
token.sells(window_s) | Number of sells in the last window_s seconds. |
token.buy_eth(window_s) | ETH spent on buys in the last window_s seconds. |
token.net_eth(window_s) | ETH bought minus ETH sold in the last window_s seconds. |
token.graduated | Pons: true once the token has left its bonding curve for its pool. Torch: true once its price has reached 10 times its launch price (Torch's "Launch Complete"; nothing changes on chain). For a limit of your own, use token.launch_multiple or token.market_cap. |
token.launch_multiple | Price now divided by the price at launch (a Torch pool's starting price, a Pons curve's first price). Null if the engine did not see the launch. |
token.market_cap | Market cap in ETH: price times the 1,000,000,000 supply (Torch tokens; null for others). |
token.liquidity | ETH in the token's market. Real ETH for a Pons curve (not its virtual start), a Torch pool (its preset's ranges), a graduated Pons pool (one full-range position) and a UP or Uniswap v2-style pool. For other UP and Uniswap concentrated pools: the ETH depth near its price (its active liquidity there), which a narrow position can make look deeper than the ETH in it. Null until known. |
token.buy_tax | The current buy tax, as a fraction: a Pons curve's, or a Torch token's (with anti-snipe, 0.8 at launch, falling to its own rate over its first 15 to 60 seconds). |
token.creator_sold | True if the creator has sold any of the token (since the engine started watching). |
The trade or launch that started the check
| The trade or launch that started the check | Meaning |
|---|---|
event.eth | ETH size of the triggering trade. |
event.trader | Wallet that made the triggering trade. |
event.first_buy | True if the triggering trade is the trader's first buy of the token. |
The workflow does not offer event.trader. You can use it in JSON.
The position (sell rules only)
| The position (exits only) | Meaning |
|---|---|
position.multiple | Price now divided by the price at entry (before costs). |
position.peak_multiple | Highest price since entry divided by the price at entry. |
position.drawdown_pct | Percent below the highest price since entry (for trailing stops). |
position.pnl_pct | Estimated return if sold now, after the venue fee and tax, ignoring price impact. |
position.age_s | Seconds since the position opened. |
The bot
| The bot | Meaning |
|---|---|
bot.open_positions | Open positions of this bot. |
Values from AI answers
Each AI step declares the fields of its answer. A condition after the AI steps can read them as ai.<step>.<field>, for example ai.decide.action. You can use them in Then buy if, in Then sell if, and in Run this step only if of a later step.
A rule can read fields that are a number, yes or no, or one of a list of words. It cannot read text, images or token addresses. A word must be one of the field's own words: the engine refuses "buyy" for a field whose words are buy and skip.
{
"and": [
{ "==": [{ "f": "ai.decide.action" }, "buy"] },
{ ">=": [{ "f": "ai.decide.confidence" }, 0.7] },
{ "!=": [{ "f": "ai.decide.risk" }, "high"] }
]
}No answer, an answer in the wrong shape, or an answer that comes too late never buys or sells. Read AI steps.
Values from webhook fields
With a webhook trigger, entry conditions can read the fields your service sends as hook.<field>. As with AI answers, a rule can read numbers, yes or no, and words, but not text or token addresses. Read webhooks.
What only JSON can express
The workflow rows cover most conditions. JSON can also express these. The workflow keeps such a condition exactly as it is, and you edit it in the JSON tab.
Or
or holds when at least one of its parts holds. This condition holds when 50 wallets bought in the last minute, or when more than 1 ETH net went in:
{
"or": [
{ ">=": [{ "f": "token.buyers", "args": [60] }, 50] },
{ ">": [{ "f": "token.net_eth", "args": [60] }, 1] }
]
}Not around more than one value
not around a single yes or no value is a row (is no). not around a larger condition needs JSON. This condition holds unless the token both has graduated and is more than an hour old:
{
"not": {
"and": [
{ "f": "token.graduated" },
{ ">": [{ "f": "token.age_s" }, 3600] }
]
}
}Arithmetic
+, -, * and / combine two values. This condition holds when the token had more than twice as many buys as sells in the last 5 minutes:
{ ">": [{ "f": "token.buys", "args": [300] }, { "*": [{ "f": "token.sells", "args": [300] }, 2] }] }A division by zero gives an unknown value, so the comparison is false.
A value on the right
The workflow rows put the value first. A comparison with a value on the right, or with a value on both sides, needs JSON.
Limits of a condition
- An
andor anorhas 1 to 20 parts. - A value takes at most 4 arguments.
- A word or text is at most 200 characters.
- There is no
nullin rules. A missing value already makes a comparison false.