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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- TypeScript reaches cross-binding parity with the other SDKs on three surfaces: the offline Greeks calculator (`allGreeks(...)` returning a typed object with the 23 Greek fields, and `impliedVolatility(...)` returning the `(iv, iv_error)` pair); historical-result streaming via `<endpoint>Stream(...)` that delivers typed row chunks through a thread-safe callback so peak memory tracks one chunk rather than the full result; and the `deriveOhlcvc` config toggle. The C ABI and C++ also gain historical-result streaming through a tick-chunk callback (`thetadatadx_<endpoint>_stream` / the C++ `<endpoint>_stream(..., handler)` over a contiguous span), with `option_list_contracts` remaining buffered-only on the C ABI.
- An Arrow-IPC terminal on the TypeScript and C++ history results — per-collection `<tick>ToArrowIpc(rows)` (TypeScript) and `thetadatadx::<collection>_to_arrow_ipc(rows)` (C++) emit the same Arrow IPC stream bytes as the existing flat-file terminal; an empty result is a valid zero-row stream carrying the schema.
- A `from_file` client-construction convenience across the bindings: the Python unified `Client.from_file(path, config=None)`, the C-ABI `thetadatadx_*_connect_from_file(path, config)` trio, and the C++ `from_file(path, config = Config::production())` statics, all defaulting to the production configuration so a credentials file is the only required input.
- Python `AsyncClient` gains awaitable constructors `await AsyncClient.connect(creds, config)` and `await AsyncClient.connect_from_file(path, config=None)` so async callers can establish a connection from inside a coroutine without the authentication handshake stalling the running event loop. The synchronous `AsyncClient(creds, config)` and `AsyncClient.from_file(...)` constructors stay available for construction outside a running loop.
- TypeScript precomputed epoch-instant fields on every tick that carries a `date` plus a milliseconds-of-day column (`createdTimestampMs`, `lastTradeTimestampMs`, `timestampMs`, `underlyingTimestampMs`, `quoteTimestampMs`), one-for-one with the Python `*_timestamp_ms` properties and resolved through the same DST-aware core conversion.
- TypeScript `Subscription` exposes the `contract` and `secType` getters Python already had, and `toString()` rendering is available on the TypeScript `ContractRef`, `Subscription`, and `SecType` values; C++ gains `operator<<` and a `thetadatadx::str(...)` rendering for the same fluent value types.
- Trade flag-word accessors are generated into every binding (previously Rust-only on `TradeTick`): `is_cancelled`, `regular_trading_hours`, `is_seller`, `trade_condition_no_last`, `price_condition_set_last`, and `is_incremental_volume` (Python computed properties, TypeScript precomputed boolean fields, C++ free functions). The C ABI also gains `thetadatadx_contract_strike_dollars`, the dollar-valued counterpart of the existing C++ `thetadatadx::strike(...)` accessor.
Expand Down
1 change: 1 addition & 0 deletions docs-site/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- TypeScript reaches cross-binding parity with the other SDKs on three surfaces: the offline Greeks calculator (`allGreeks(...)` returning a typed object with the 23 Greek fields, and `impliedVolatility(...)` returning the `(iv, iv_error)` pair); historical-result streaming via `<endpoint>Stream(...)` that delivers typed row chunks through a thread-safe callback so peak memory tracks one chunk rather than the full result; and the `deriveOhlcvc` config toggle. The C ABI and C++ also gain historical-result streaming through a tick-chunk callback (`thetadatadx_<endpoint>_stream` / the C++ `<endpoint>_stream(..., handler)` over a contiguous span), with `option_list_contracts` remaining buffered-only on the C ABI.
- An Arrow-IPC terminal on the TypeScript and C++ history results — per-collection `<tick>ToArrowIpc(rows)` (TypeScript) and `thetadatadx::<collection>_to_arrow_ipc(rows)` (C++) emit the same Arrow IPC stream bytes as the existing flat-file terminal; an empty result is a valid zero-row stream carrying the schema.
- A `from_file` client-construction convenience across the bindings: the Python unified `Client.from_file(path, config=None)`, the C-ABI `thetadatadx_*_connect_from_file(path, config)` trio, and the C++ `from_file(path, config = Config::production())` statics, all defaulting to the production configuration so a credentials file is the only required input.
- Python `AsyncClient` gains awaitable constructors `await AsyncClient.connect(creds, config)` and `await AsyncClient.connect_from_file(path, config=None)` so async callers can establish a connection from inside a coroutine without the authentication handshake stalling the running event loop. The synchronous `AsyncClient(creds, config)` and `AsyncClient.from_file(...)` constructors stay available for construction outside a running loop.
- TypeScript precomputed epoch-instant fields on every tick that carries a `date` plus a milliseconds-of-day column (`createdTimestampMs`, `lastTradeTimestampMs`, `timestampMs`, `underlyingTimestampMs`, `quoteTimestampMs`), one-for-one with the Python `*_timestamp_ms` properties and resolved through the same DST-aware core conversion.
- TypeScript `Subscription` exposes the `contract` and `secType` getters Python already had, and `toString()` rendering is available on the TypeScript `ContractRef`, `Subscription`, and `SecType` values; C++ gains `operator<<` and a `thetadatadx::str(...)` rendering for the same fluent value types.
- Trade flag-word accessors are generated into every binding (previously Rust-only on `TradeTick`): `is_cancelled`, `regular_trading_hours`, `is_seller`, `trade_condition_no_last`, `price_condition_set_last`, and `is_incremental_volume` (Python computed properties, TypeScript precomputed boolean fields, C++ free functions). The C ABI also gains `thetadatadx_contract_strike_dollars`, the dollar-valued counterpart of the existing C++ `thetadatadx::strike(...)` accessor.
Expand Down
59 changes: 59 additions & 0 deletions sdks/python/python/thetadatadx/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ from __future__ import annotations

from typing import (
Any,
Awaitable,
Callable,
List,
Literal,
Expand Down Expand Up @@ -1297,10 +1298,41 @@ class AsyncClient:
def __init__(self, creds: Credentials, config: Config) -> None:
"""Connect to ThetaData with ``creds`` and ``config``.

Runs the authentication and connection handshake to completion
before returning. Use this when constructing outside a running
event loop. Inside a coroutine, prefer
:meth:`connect` so the handshake does not stall the event loop.

Args:
creds: Account credentials.
config: Connection configuration.

Raises:
ThetaDataError: If authentication or the connection fails.
"""
...

@staticmethod
def connect(
creds: Credentials,
config: Config,
) -> Awaitable[AsyncClient]:
"""Connect without blocking the running event loop.

The authentication and connection handshake resolves off the
event loop, so other coroutines keep running while the connection
is established. This is the preferred way to build an
:class:`AsyncClient` from inside a coroutine::

client = await AsyncClient.connect(creds, config)

Args:
creds: Account credentials.
config: Connection configuration.

Returns:
An awaitable resolving to a connected :class:`AsyncClient`.

Raises:
ThetaDataError: If authentication or the connection fails.
"""
Expand All @@ -1327,6 +1359,33 @@ class AsyncClient:
"""
...

@staticmethod
def connect_from_file(
path: str,
config: Optional[Config] = None,
) -> Awaitable[AsyncClient]:
"""Connect from a credentials file without blocking the event loop.

Loads credentials from a two-line file and connects off the event
loop, defaulting to ``Config.production()`` when no ``config`` is
supplied::

client = await AsyncClient.connect_from_file("creds.txt")

Args:
path: Path to a two-line credentials file.
config: Connection configuration; defaults to
``Config.production()`` when omitted.

Returns:
An awaitable resolving to a connected :class:`AsyncClient`.

Raises:
ThetaDataError: If the file cannot be read or the connection
fails.
"""
...

def __repr__(self) -> str:
"""Return a representation including historical and streaming state."""
...
Expand Down
87 changes: 86 additions & 1 deletion sdks/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1788,13 +1788,19 @@ impl StreamView {
///
/// async def main():
/// creds = Credentials.from_file("creds.txt")
/// client = AsyncClient(creds, Config.production())
/// client = await AsyncClient.connect(creds, Config.production())
/// ticks = await client.stock_history_eod_async("AAPL", "20240101", "20240301")
/// print(ticks.to_pandas().head())
///
/// asyncio.run(main())
/// ```
///
/// Construct with `await AsyncClient.connect(...)` (or
/// `await AsyncClient.connect_from_file("creds.txt")`) from inside a
/// coroutine so the auth + connect handshake resolves off the event loop
/// instead of stalling it. The synchronous `AsyncClient(creds, config)`
/// constructor stays available for building outside a running loop.
///
/// Attribute access is restricted to async-suffixed methods plus a
/// safelisted set of synchronous lifecycle methods that have no
/// async counterpart on the wrapped surface
Expand Down Expand Up @@ -1972,12 +1978,64 @@ struct AsyncClient {

#[pymethods]
impl AsyncClient {
/// Synchronous constructor that runs the auth + connect handshake to
/// completion before returning.
///
/// Suitable for construction OUTSIDE a running event loop (module
/// import, a worker thread, a `__main__` body before `asyncio.run`).
/// Inside a coroutine, prefer ``await AsyncClient.connect(...)`` so
/// the handshake does not stall the event loop.
#[new]
fn new(py: Python<'_>, creds: &Credentials, config: &Config) -> PyResult<Self> {
let client = Py::new(py, Client::new(py, creds, config)?)?;
Ok(Self { inner: client })
}

/// Awaitable constructor that yields a connected ``AsyncClient``
/// without stalling the running event loop.
///
/// The auth round-trip and gRPC channel setup resolve off the event
/// loop, so other coroutines keep running while the connection is
/// established. This is the preferred way to build an ``AsyncClient``
/// from inside a coroutine::
///
/// client = await AsyncClient.connect(creds, config)
///
/// The synchronous ``AsyncClient(creds, config)`` constructor remains
/// available for construction outside a running loop.
#[staticmethod]
fn connect<'py>(
py: Python<'py>,
creds: &Credentials,
config: &Config,
) -> PyResult<Bound<'py, PyAny>> {
// Snapshot the config + credentials under the GIL before handing
// the connect future to the runtime. `connect()` takes ownership
// of the `DirectConfig`, and the outer `Config` handle may still
// be mutated Python-side after this call returns its awaitable.
let direct_config = {
let guard = config.inner.lock().unwrap_or_else(|e| e.into_inner());
guard.clone()
};
// Seed the process-global runtime from this client's runtime
// config before the awaitable resolves, so `worker_threads` takes
// effect when the first client in the process connects.
runtime_from_config(&direct_config.runtime);
let inner_creds = creds.inner.clone();
spawn_awaitable(
py,
async move { thetadatadx::Client::connect(&inner_creds, direct_config).await },
|py, client| {
let wrapped = Client {
client: std::sync::Arc::new(client),
callback: Arc::new(Mutex::new(None)),
};
let inner = Py::new(py, wrapped)?;
Ok(Py::new(py, Self { inner })?.into_any())
},
)
}

/// Convenience constructor: `AsyncClient.from_file("creds.txt")`.
/// Accepts an optional `config` kwarg defaulting to
/// `Config.production()` for non-production environment tests.
Expand All @@ -1997,6 +2055,33 @@ impl AsyncClient {
Ok(Self { inner: client })
}

/// Awaitable file constructor that yields a connected ``AsyncClient``
/// without stalling the running event loop.
///
/// Loads credentials from a two-line file (line 1 = email, line 2 =
/// password) and connects off the event loop, defaulting to the
/// production endpoint when no ``config`` is supplied::
///
/// client = await AsyncClient.connect_from_file("creds.txt")
#[staticmethod]
#[pyo3(signature = (path, config=None))]
fn connect_from_file<'py>(
py: Python<'py>,
path: &str,
config: Option<&Config>,
) -> PyResult<Bound<'py, PyAny>> {
let creds = Credentials::from_file(path)?;
let owned_default;
let cfg = match config {
Some(c) => c,
None => {
owned_default = Config::production();
&owned_default
}
};
Self::connect(py, &creds, cfg)
}

/// Forward attribute access to the wrapped `Client`.
/// Async-suffixed methods plus the safelisted lifecycle / streaming
/// methods are reachable; everything else raises `AttributeError`
Expand Down
Loading