| spec_name = 'manual_generic'
|
| fields = {'account': 'cuixia', 'action': 'trim', 'dip_n': 1, 'dip_variant': '', ...}
|
| spec = CoidSpec(name='manual_generic', strategy_label='manual', prefix='manual-', regex=re.compile('^manual-', re.IGNORECASE)...ID (any `manual-*` prefix). Protected from auto-trim by buying_power_trim._manual_protected_symbols.', is_legacy=False)
|
| norm_fields = {'account': 'cuixia', 'action': 'trim', 'dip_n': 1, 'dip_variant': '', ...}
|
| k = 'dip_variant', v = ''
|
| e = ValueError("format_coid: spec='manual_generic' template requires field 'tag' (template='manual-{tag}-{symbol}-{account...b_id', 'entry_count', 'mmdd', 'hold_days', 'action', 'side', 'ts', 'uniq', 'kind', 'source', 'dip_n', 'dip_variant'])")
|
|
|
| def format_coid(spec_name: str, **fields: Any) -> str:
|
| """Format and return a COID per the named spec.
|
|
|
| The template's required fields are derived from the f-string's
|
| `{name}` placeholders. Missing fields raise ValueError (loud
|
| failure) — never silently substitute an empty string, because that
|
| produces malformed coids that parse to wrong specs.
|
|
|
| Strings are lowercased before substitution. `uniq` is auto-filled
|
| with a 6-char uuid hex when not provided (matches the ovf-* pattern
|
| and avoids collisions on repeated calls).
|
|
|
| The result is truncated to the spec's max_len (default 48 = Alpaca's
|
| client_order_id cap). Truncation preserves the prefix — if the
|
| template would exceed max_len the trailing fields are clipped.
|
|
|
| Raises:
|
| ValueError if `spec_name` is not in the registry, the spec is
|
| flagged is_legacy (read-only), or a required field is missing.
|
| """
|
| spec = _BY_NAME.get(spec_name)
|
| if spec is None:
|
| raise ValueError(
|
| f"format_coid: unknown spec_name={spec_name!r}. "
|
| f"Add a CoidSpec to rtrader/utils/coid_registry.REGISTRY."
|
| )
|
| if spec.is_legacy or not spec.format_template:
|
| raise ValueError(
|
| f"format_coid: spec={spec_name!r} is legacy/read-only. "
|
| f"New emit sites must use a non-legacy spec."
|
| )
|
|
|
| # Lowercase string fields; pass through ints/floats unchanged.
|
| norm_fields: Dict[str, Any] = {}
|
| for k, v in fields.items():
|
| if isinstance(v, str):
|
| norm_fields[k] = v.lower()
|
| else:
|
| norm_fields[k] = v
|
|
|
| # Auto-fill uniq when the template needs it but caller didn't supply.
|
| if "{uniq}" in spec.format_template and "uniq" not in norm_fields:
|
| norm_fields["uniq"] = uuid.uuid4().hex[:6]
|
|
|
| try:
|
| > coid = spec.format_template.format(**norm_fields)
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| E KeyError: 'tag'
|
|
|
| rtrader/utils/coid_registry.py:1253: KeyError
|
|
|
| The above exception was the direct cause of the following exception:
|
|
|
| spec = CoidSpec(name='manual_generic', strategy_label='manual', prefix='manual-', regex=re.compile('^manual-', re.IGNORECASE)...ID (any `manual-*` prefix). Protected from auto-trim by buying_power_trim._manual_protected_symbols.', is_legacy=False)
|
|
|
| @pytest.mark.parametrize(
|
| "spec",
|
| [s for s in REGISTRY if not s.is_legacy],
|
| ids=lambda s: s.name,
|
| )
|
| def test_each_spec_round_trips(spec):
|
| """format_coid(name, ...) → parse_coid(...) → same spec."""
|
| > coid = format_coid(spec.name, **_SAMPLE_FIELDS)
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
| tests/utils/test_coid_registry.py:66:
|
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
| spec_name = 'manual_generic'
|
| fields = {'account': 'cuixia', 'action': 'trim', 'dip_n': 1, 'dip_variant': '', ...}
|
| spec = CoidSpec(name='manual_generic', strategy_label='manual', prefix='manual-', regex=re.compile('^manual-', re.IGNORECASE)...ID (any `manual-*` prefix). Protected from auto-trim by buying_power_trim._manual_protected_symbols.', is_legacy=False)
|
| norm_fields = {'account': 'cuixia', 'action': 'trim', 'dip_n': 1, 'dip_variant': '', ...}
|
| k = 'dip_variant', v = ''
|
| e = ValueError("format_coid: spec='manual_generic' template requires field 'tag' (template='manual-{tag}-{symbol}-{account...b_id', 'entry_count', 'mmdd', 'hold_days', 'action', 'side', 'ts', 'uniq', 'kind', 'source', 'dip_n', 'dip_variant'])")
|
|
|
| def format_coid(spec_name: str, **fields: Any) -> str:
|
| """Format and return a COID per the named spec.
|
|
|
| The template's required fields are derived from the f-string's
|
| `{name}` placeholders. Missing fields raise ValueError (loud
|
| failure) — never silently substitute an empty string, because that
|
| produces malformed coids that parse to wrong specs.
|
|
|
| Strings are lowercased before substitution. `uniq` is auto-filled
|
| with a 6-char uuid hex when not provided (matches the ovf-* pattern
|
| and avoids collisions on repeated calls).
|
|
|
| The result is truncated to the spec's max_len (default 48 = Alpaca's
|
| client_order_id cap). Truncation preserves the prefix — if the
|
| template would exceed max_len the trailing fields are clipped.
|
|
|
| Raises:
|
| ValueError if `spec_name` is not in the registry, the spec is
|
| flagged is_legacy (read-only), or a required field is missing.
|
| """
|
| spec = _BY_NAME.get(spec_name)
|
| if spec is None:
|
| raise ValueError(
|
| f"format_coid: unknown spec_name={spec_name!r}. "
|
| f"Add a CoidSpec to rtrader/utils/coid_registry.REGISTRY."
|
| )
|
| if spec.is_legacy or not spec.format_template:
|
| raise ValueError(
|
| f"format_coid: spec={spec_name!r} is legacy/read-only. "
|
| f"New emit sites must use a non-legacy spec."
|
| )
|
|
|
| # Lowercase string fields; pass through ints/floats unchanged.
|
| norm_fields: Dict[str, Any] = {}
|
| for k, v in fields.items():
|
| if isinstance(v, str):
|
| norm_fields[k] = v.lower()
|
| else:
|
| norm_fields[k] = v
|
|
|
| # Auto-fill uniq when the template needs it but caller didn't supply.
|
| if "{uniq}" in spec.format_template and "uniq" not in norm_fields:
|
| norm_fields["uniq"] = uuid.uuid4().hex[:6]
|
|
|
| try:
|
| coid = spec.format_template.format(**norm_fields)
|
| except KeyError as exc:
|
| # Missing required template field — surface to Sentry then raise.
|
| e = ValueError(
|
| f"format_coid: spec={spec_name!r} template requires field "
|
| f"{exc!s} (template={spec.format_template!r}, "
|
| f"provided={list(fields.keys())})"
|
| )
|
| sentry_sdk.capture_exception(e)
|
| > raise e from exc
|
| E ValueError: format_coid: spec='manual_generic' template requires field 'tag' (template='manual-{tag}-{symbol}-{account}-{uniq}', provided=['symbol', 'account', 'plan_id', 'job_id', 'entry_count', 'mmdd', 'hold_days', 'action', 'side', 'ts', 'uniq', 'kind', 'source', 'dip_n', 'dip_variant'])
|
|
|
| rtrader/utils/coid_registry.py:1262: ValueError
|