Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend_api_python/app/services/ai_generation_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
- Read run-supplied values only inside executable handlers or scheduled callbacks with `context.params.get("name", same_default)`. The discovery context used by `initialize(context)` has no `params`; never read `context.params` in `initialize`.
- Parameters may control signal periods, thresholds, target weights, stops, take profit, trailing protection, cooldowns, and bounded layer counts.
- Do not disguise universe, symbol, market type, frequency, leverage permission, initial capital, date range, commission, or slippage as ordinary strategy parameters.
- Use `context.set_metadata(...)` in `initialize` for stable descriptive metadata such as direction mode and strategy family. Metadata is not a substitute for executable risk logic.
- Use `context.set_metadata(...)` in `initialize` for stable descriptive metadata such as direction mode and strategy family, passing keyword arguments such as `context.set_metadata(direction_mode="long_only", strategy_family="trend")`. Metadata is not a substitute for executable risk logic.

## Data and factors
- Historical-bar signatures are exact: `get_history(count, frequency=None, field=None, security_list=None)` and `data.history(symbols, count, fields=None)`.
Expand Down
12 changes: 10 additions & 2 deletions backend_api_python/app/services/strategy_v2/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import hashlib
from dataclasses import dataclass
from types import SimpleNamespace
from typing import Any, Callable, Iterable
from typing import Any, Callable, Iterable, Mapping

from app.utils.safe_exec import build_safe_builtins, safe_exec_with_validation
from app.services.factors import FactorError, get_factor
Expand Down Expand Up @@ -150,7 +150,15 @@ def allow_leverage(self, max_leverage: object = 1) -> None:
self.leverage_allowed = value > 1.0
self.max_leverage = value

def set_metadata(self, **values: Any) -> None:
def set_metadata(self, *args: Any, **values: Any) -> None:
if len(args) == 1 and isinstance(args[0], Mapping):
self.metadata.update(args[0])
elif len(args) == 2:
self.metadata[str(args[0])] = args[1]
elif args:
raise TypeError(
"set_metadata expects keyword arguments, a single key/value pair, or a mapping"
)
self.metadata.update(values)


Expand Down
17 changes: 17 additions & 0 deletions backend_api_python/tests/test_strategy_v2_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,23 @@ def handle_data(context, data):
assert manifest.metadata()["directionMode"] == "both"


def test_set_metadata_accepts_positional_key_value_pair():
code = """
def initialize(context):
context.set_universe(["Crypto:BTC/USDT@okx:swap"])
context.subscribe(frequency="1h")
context.set_metadata("direction_mode", "long_only")
context.set_metadata({"strategy_family": "trend"})

def handle_data(context, data):
pass
"""
manifest = compile_strategy_v2(code).manifest

assert manifest.direction_mode == "long_only"
assert manifest.metadata()["directionMode"] == "long_only"


@pytest.mark.parametrize(
"strategy_body,expected",
[
Expand Down