feat: validate endpoint integration, Expr quantity sizing, apply_func input field fix
- Add /api/v1/strategies/validate client to SwymClient; wire into agent loop before submission so all DSL errors are surfaced in one round-trip - Update dsl-schema.json to upstream: quantity is now oneOf[DecimalString, Expr], ExprApplyFunc uses "input" field (renamed from "expr") - Update prompts: document expression-based quantity sizing (fixed-fraction and ATR-based examples), fix apply_func to use "input" not "expr" throughout - Remove unused ValidationError import Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -74,21 +74,36 @@ bars_since_entry — complete bars elapsed since position was opened
|
||||
balance — free balance of a named asset (e.g. "usdt", "usdc")
|
||||
|
||||
### Quantity
|
||||
Action quantity MUST be a fixed decimal string that parses as a floating-point number.
|
||||
NEVER use an expression object for quantity — only plain decimal strings are accepted.
|
||||
NEVER use placeholder strings like `"ATR_SIZED"`, `"FULL_BALANCE"`, `"percent_of_balance"`,
|
||||
`"dynamic"`, `"all"`, or any non-numeric string — these will be rejected immediately.
|
||||
To exit a position, use the SAME decimal string as the entry rule — the backtester matches
|
||||
open quantity automatically. There is no "close all" concept; just repeat the entry 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.
|
||||
|
||||
Size your quantity to represent roughly 5–10% of the initial $10,000 USDC balance at
|
||||
approximate current prices. Reference values (adjust if market prices differ significantly):
|
||||
- BTC: `"0.01"` ≈ $800 (8% of $10k)
|
||||
- ETH: `"3.0"` ≈ $600 (6% of $10k)
|
||||
- SOL: `"50.0"` ≈ $700 (7% of $10k)
|
||||
Since strategies run across all instruments with the same quantity string, choose a value
|
||||
appropriate for the primary instrument you are designing for. Avoid `"0.001"` — it produces
|
||||
negligible exposure and makes results statistically meaningless.
|
||||
**Preferred: expression-based sizing** — instrument-agnostic, scales automatically:
|
||||
|
||||
Fixed fraction of quote balance (5% of USDC balance, converted to base units 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"}}}}
|
||||
```
|
||||
|
||||
ATR-based risk sizing (risk $200 per trade, sized by volatility):
|
||||
```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:
|
||||
```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.
|
||||
|
||||
### Multi-timeframe
|
||||
Any expression can reference a different timeframe via "timeframe" field.
|
||||
@@ -166,7 +181,7 @@ Common mistakes to NEVER make:
|
||||
with `ApplyFuncName` values: `highest`, `lowest`, `sma`, `ema`, `wma`, `std_dev`, `sum`,
|
||||
`bollinger_upper`, `bollinger_lower`.
|
||||
- `volume` is a candle FIELD, not a func name. Access it as `{{"kind":"field","field":"volume"}}`.
|
||||
To compute EMA of volume: `{{"kind":"apply_func","name":"ema","period":20,"expr":{{"kind":"field","field":"volume"}}}}`.
|
||||
To compute EMA of volume: `{{"kind":"apply_func","name":"ema","period":20,"input":{{"kind":"field","field":"volume"}}}}`.
|
||||
- `bollinger_upper` and `bollinger_lower` are FUNC NAMES, not Expr kinds. To compare close to the upper band:
|
||||
`{{"kind":"compare","left":{{"kind":"field","field":"close"}},"op":">","right":{{"kind":"func","name":"bollinger_upper","period":20}}}}`
|
||||
NEVER write `{{"kind":"bollinger_upper",...}}` — `bollinger_upper` is not an Expr kind.
|
||||
@@ -374,7 +389,7 @@ The MACD line is `EMA(12) - EMA(26)`; the signal line is `EMA(9)` of the MACD li
|
||||
}},
|
||||
"right": {{
|
||||
"kind": "apply_func", "name": "ema", "period": 9,
|
||||
"expr": {{
|
||||
"input": {{
|
||||
"kind": "bin_op", "op": "sub",
|
||||
"left": {{"kind": "func", "name": "ema", "period": 12}},
|
||||
"right": {{"kind": "func", "name": "ema", "period": 26}}
|
||||
@@ -403,7 +418,7 @@ The MACD line is `EMA(12) - EMA(26)`; the signal line is `EMA(9)` of the MACD li
|
||||
}},
|
||||
"right": {{
|
||||
"kind": "apply_func", "name": "ema", "period": 9,
|
||||
"expr": {{
|
||||
"input": {{
|
||||
"kind": "bin_op", "op": "sub",
|
||||
"left": {{"kind": "func", "name": "ema", "period": 12}},
|
||||
"right": {{"kind": "func", "name": "ema", "period": 26}}
|
||||
@@ -434,9 +449,10 @@ The MACD line is `EMA(12) - EMA(26)`; the signal line is `EMA(9)` of the MACD li
|
||||
}}
|
||||
```
|
||||
|
||||
Key pattern: `apply_func` wraps any `Expr` tree, enabling EMA-of-expression (signal line),
|
||||
WMA-of-expression (Hull MA), or std_dev-of-returns. There is NO native `macd` func name —
|
||||
always compose it as `bin_op(sub, func(ema,12), func(ema,26))` as shown above.
|
||||
Key pattern: `apply_func` wraps any `Expr` tree using the `"input"` field (NOT `"expr"`).
|
||||
This enables EMA-of-expression (signal line), WMA-of-expression (Hull MA), or std_dev-of-returns.
|
||||
There is NO native `macd` func name — always compose it as `bin_op(sub, func(ema,12), func(ema,26))` as shown above.
|
||||
CRITICAL: `apply_func` uses `"input"`, not `"expr"`. Writing `"expr":` will be rejected by the API.
|
||||
|
||||
## Anti-patterns to avoid
|
||||
|
||||
|
||||
Reference in New Issue
Block a user