Skip to content
Draft
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
42 changes: 42 additions & 0 deletions bt_api_py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,18 @@
evaluate_ctp_budget,
evaluate_ctp_budget_numbers,
)
from bt_api_py._ctp_credential_binding import (
CtpCredentialBindingScope,
CtpCredentialBindingVerifier,
)
from bt_api_py._ctp_execution_authorization import (
ENTRY_APPROVAL_SCHEMA_VERSION,
RECOVERY_APPROVAL_PURPOSE,
RECOVERY_APPROVAL_SCHEMA_VERSION,
RECOVERY_APPROVAL_SCOPE_VERSION,
SIMNOW_APPROVAL_SCHEMA_VERSION,
SIMNOW_ENTRY_APPROVAL_SCHEMA_VERSION,
SIMNOW_RECOVERY_APPROVAL_SCHEMA_VERSION,
CtpExecutionApproval,
CtpExecutionApprovalCapability,
CtpExecutionApprovalContext,
Expand Down Expand Up @@ -156,6 +164,21 @@
round_trip_cost,
signed_funding_cashflow,
)
from bt_api_py.ctp_simnow_execution import (
OFFICIAL_SET1_PROFILES,
CtpSimNowCancelResult,
CtpSimNowExecutionAdapter,
CtpSimNowExecutionError,
CtpSimNowOrderIdentity,
CtpSimNowOrderRequest,
CtpSimNowOrderResult,
CtpSimNowQueryResult,
CtpSimNowReadObservation,
CtpSimNowSessionIdentity,
build_ctp_simnow_cancel_request,
map_ctp_simnow_cancel_result,
map_ctp_simnow_order_result,
)
from bt_api_py.forwarding import (
MAX_MESSAGE_BYTES,
BtApiForwardingAdapter,
Expand Down Expand Up @@ -207,9 +230,28 @@ def __getattr__(name: str):
"CtpExecutionApproval",
"CtpExecutionApprovalCapability",
"CtpExecutionApprovalContext",
"CtpCredentialBindingScope",
"CtpCredentialBindingVerifier",
"CtpSimNowExecutionAdapter",
"CtpSimNowExecutionError",
"CtpSimNowSessionIdentity",
"CtpSimNowOrderRequest",
"CtpSimNowOrderIdentity",
"CtpSimNowOrderResult",
"CtpSimNowCancelResult",
"CtpSimNowQueryResult",
"CtpSimNowReadObservation",
"OFFICIAL_SET1_PROFILES",
"build_ctp_simnow_cancel_request",
"map_ctp_simnow_cancel_result",
"map_ctp_simnow_order_result",
"ENTRY_APPROVAL_SCHEMA_VERSION",
"RECOVERY_APPROVAL_PURPOSE",
"RECOVERY_APPROVAL_SCHEMA_VERSION",
"RECOVERY_APPROVAL_SCOPE_VERSION",
"SIMNOW_APPROVAL_SCHEMA_VERSION",
"SIMNOW_ENTRY_APPROVAL_SCHEMA_VERSION",
"SIMNOW_RECOVERY_APPROVAL_SCHEMA_VERSION",
"recovery_action_digest",
"recovery_plan_digest",
"verify_ctp_execution_recovery_approval",
Expand Down
64 changes: 62 additions & 2 deletions bt_api_py/_contracts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,9 @@ class OrderRequest:
execution_cycle_id: str | None = None
execution_role: str | None = None
strategy_identity_sha256: str | None = None
managed_intent_id: str | None = None
hedge_flag: str | None = None
runtime_order_id: str | None = None

def __post_init__(self) -> None:
self._validate_core_fields()
Expand Down Expand Up @@ -460,6 +463,11 @@ def _validate_intent_fields(self) -> None:
raise ValueError("offset must be open, close, close_today or close_yesterday")
if self.position_mode not in {None, "net", "dual_side"}:
raise ValueError("position_mode must be net or dual_side")
if self.hedge_flag is not None and (
not isinstance(self.hedge_flag, str)
or self.hedge_flag not in {"1", "2", "3", "5", "6", "7"}
):
raise ValueError("hedge_flag must be a supported CTP hedge code")
if not self.account_id:
raise ValueError("account_id must be a non-empty string")
if not self.client_order_id:
Expand All @@ -484,6 +492,25 @@ def _validate_intent_fields(self) -> None:
raise ValueError(
"strategy_identity_sha256 must be a lowercase SHA-256 hex digest or None"
)
if self.runtime_order_id is not None and (
not isinstance(self.runtime_order_id, str)
or not self.runtime_order_id
or self.runtime_order_id != self.runtime_order_id.strip()
or len(self.runtime_order_id.encode("utf-8")) > 256
):
raise ValueError("runtime_order_id must be a bounded non-empty string or None")
if self.managed_intent_id is not None and (
not isinstance(self.managed_intent_id, str)
or not self.managed_intent_id
or self.managed_intent_id != self.managed_intent_id.strip()
or len(self.managed_intent_id.encode("utf-8")) > 256
):
raise ValueError("managed_intent_id must be a bounded non-empty string or None")
if self.managed_intent_id is not None:
if self.runtime_order_id is None:
raise ValueError("runtime_order_id is required with managed_intent_id")
if self.hedge_flag not in {"1", "2", "3"}:
raise ValueError("managed CTP requests require a supported exact hedge_flag")
if self.order_type is OrderType.LIMIT and self.price is None:
raise ValueError("limit order requires a price")
if self.order_type is OrderType.MARKET and self.price is not None:
Expand All @@ -501,14 +528,47 @@ class CancelOrderRequest:
front_id: int | None = None
session_id: int | None = None
order_ref: str | None = None
runtime_action_id: str | None = None
runtime_order_id: str | None = None
managed_cancel_intent_id: str | None = None

def __post_init__(self) -> None:
if not self.symbol:
raise ValueError("symbol must be a non-empty string")
if not self.account_id:
raise ValueError("account_id must be a non-empty string")
if not self.order_id and not self.client_order_id and not self.order_ref:
raise ValueError("order_id, client_order_id or order_ref must be provided")
if (
not self.order_id
and not self.client_order_id
and not self.order_ref
and not self.runtime_order_id
):
raise ValueError(
"order_id, client_order_id, order_ref or runtime_order_id must be provided"
)
if self.runtime_action_id is not None and (
not isinstance(self.runtime_action_id, str)
or not self.runtime_action_id
or self.runtime_action_id != self.runtime_action_id.strip()
or len(self.runtime_action_id.encode("utf-8")) > 256
):
raise ValueError("runtime_action_id must be a bounded non-empty string or None")
if self.runtime_order_id is not None and (
not isinstance(self.runtime_order_id, str)
or not self.runtime_order_id
or self.runtime_order_id != self.runtime_order_id.strip()
or len(self.runtime_order_id.encode("utf-8")) > 256
):
raise ValueError("runtime_order_id must be a bounded non-empty string or None")
if self.managed_cancel_intent_id is not None and (
not isinstance(self.managed_cancel_intent_id, str)
or not self.managed_cancel_intent_id
or self.managed_cancel_intent_id != self.managed_cancel_intent_id.strip()
or len(self.managed_cancel_intent_id.encode("utf-8")) > 256
):
raise ValueError("managed_cancel_intent_id must be a bounded non-empty string or None")
if self.managed_cancel_intent_id is not None and self.runtime_order_id is None:
raise ValueError("managed_cancel_intent_id requires runtime_order_id")


@dataclass(frozen=True)
Expand Down
Loading
Loading