From 3f8d4de7fba5abfcc56adc8b9ef42c168723c71b Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Tue, 10 Mar 2026 09:33:43 +0200 Subject: [PATCH] feat: add declarative SizingMethod types from upstream schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream added three new quantity sizing objects alongside DecimalString and Expr: - fixed_sum: buy N quote-currency worth at current price - percent_of_balance: buy N% of named asset's free balance - fixed_units: buy exactly N base units (semantic alias for decimal string) Update dsl-schema.json to include the three definitions and expand Action.quantity.oneOf to reference all five valid forms. Update prompts.rs Quantity section to present the declarative methods as the preferred approach — they're cleaner, more readable, and instrument-agnostic compared to raw Expr composition. Co-Authored-By: Claude Sonnet 4.6 --- src/dsl-schema.json | 36 +++++++++++++++++++++++++++++++++++- src/prompts.rs | 34 ++++++++++++++++++++-------------- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/dsl-schema.json b/src/dsl-schema.json index 1c63e15..e18d80a 100644 --- a/src/dsl-schema.json +++ b/src/dsl-schema.json @@ -66,14 +66,48 @@ "properties": { "side": { "type": "string", "enum": ["buy", "sell"] }, "quantity": { - "description": "Per-order size in base asset units. Either a fixed decimal string (e.g. \"0.001\") or a dynamic Expr evaluated at candle close. When an Expr returns None the order is skipped; negative values are clamped to zero.", + "description": "Per-order size in base asset units. Fixed decimal string (e.g. \"0.001\"), a declarative SizingMethod object, or a dynamic Expr object. When a method or Expr returns None the order is skipped; negative values are clamped to zero.", "oneOf": [ { "$ref": "#/definitions/DecimalString" }, + { "$ref": "#/definitions/SizingFixedSum" }, + { "$ref": "#/definitions/SizingPercentOfBalance" }, + { "$ref": "#/definitions/SizingFixedUnits" }, { "$ref": "#/definitions/Expr" } ] } } }, + "SizingFixedSum": { + "description": "Buy `amount` worth of quote currency at the current price. qty = amount / current_price.", + "type": "object", + "required": ["method", "amount"], + "additionalProperties": false, + "properties": { + "method": { "const": "fixed_sum" }, + "amount": { "$ref": "#/definitions/DecimalString", "description": "Quote-currency amount, e.g. \"500\" means buy $500 worth." } + } + }, + "SizingPercentOfBalance": { + "description": "Buy percent% of the named asset's free balance worth of base asset. qty = balance(asset) * percent/100 / current_price.", + "type": "object", + "required": ["method", "percent", "asset"], + "additionalProperties": false, + "properties": { + "method": { "const": "percent_of_balance" }, + "percent": { "$ref": "#/definitions/DecimalString", "description": "Percentage, e.g. \"2\" means 2% of the free balance." }, + "asset": { "type": "string", "description": "Asset name to look up, e.g. \"usdc\". Matched case-insensitively." } + } + }, + "SizingFixedUnits": { + "description": "Buy exactly `units` of base asset. Semantic alias for a fixed decimal quantity.", + "type": "object", + "required": ["method", "units"], + "additionalProperties": false, + "properties": { + "method": { "const": "fixed_units" }, + "units": { "$ref": "#/definitions/DecimalString", "description": "Base asset quantity, e.g. \"0.01\" means 0.01 BTC." } + } + }, "Rule": { "type": "object", "required": ["when", "then"], diff --git a/src/prompts.rs b/src/prompts.rs index 530e1fc..667b059 100644 --- a/src/prompts.rs +++ b/src/prompts.rs @@ -74,36 +74,42 @@ bars_since_entry — complete bars elapsed since position was opened balance — free balance of a named asset (e.g. "usdt", "usdc") ### Quantity -Action quantity is either a fixed decimal string **or** an Expr that evaluates to a number -at candle close. If the Expr returns None the order is skipped; negative values are clamped -to zero. +Action quantity accepts four forms — pick the simplest one for your intent: -**Preferred: expression-based sizing** — instrument-agnostic, scales automatically: +**1. Declarative sizing methods (preferred — instrument-agnostic, readable):** -Fixed fraction of quote balance (5% of USDC balance, converted to base units at current price): +Spend a fixed quote amount (e.g. $500 worth of base at current price): ```json -{{"kind":"bin_op","op":"div", - "left":{{"kind":"bin_op","op":"mul", - "left":{{"kind":"balance","asset":"usdc"}}, - "right":{{"kind":"literal","value":"0.05"}}}}, - "right":{{"kind":"field","field":"close"}}}} +{{"method":"fixed_sum","amount":"500"}} ``` -ATR-based risk sizing (risk $200 per trade, sized by volatility): +Spend a percentage of free quote balance (e.g. 5% of USDC): +```json +{{"method":"percent_of_balance","percent":"5","asset":"usdc"}} +``` + +Buy a fixed number of base units (semantic alias for a decimal string): +```json +{{"method":"fixed_units","units":"0.01"}} +``` + +**2. Plain decimal string** — use only when you have a specific reason: +`"0.01"` (0.01 BTC, 3.0 ETH, 50.0 SOL — instrument-specific, not portable) + +**3. Expr** — for dynamic sizing not covered by the methods above, e.g. ATR-based: ```json {{"kind":"bin_op","op":"div", "left":{{"kind":"literal","value":"200"}}, "right":{{"kind":"func","name":"atr","period":14}}}} ``` -For exit rules, use `position_quantity` to close the exact open position: +**4. Exit rules** — use `position_quantity` to close the exact open size: ```json {{"kind":"position_quantity"}} ``` -**Fixed strings are also valid** when you want a specific size, e.g. `"0.01"`. NEVER use placeholder strings like `"ATR_SIZED"`, `"FULL_BALANCE"`, `"all"`, `"dynamic"` — -these are rejected immediately. Use an Expr or a plain decimal string. +these are rejected immediately. ### Multi-timeframe Any expression can reference a different timeframe via "timeframe" field.