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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Migration guides will be provided for all breaking changes
- Semantic versioning (MAJOR.MINOR.PATCH) is strictly followed

## [3.1.5] - 2025-08-11

### Added
- **📊 Enhanced Bar Data Retrieval**: Added optional `start_time` and `end_time` parameters to `get_bars()` method
- Allows precise time range specification for historical data queries
- Parameters override the `days` argument when provided
- Supports both timezone-aware and naive datetime objects
- Automatically converts times to UTC for API consistency
- Smart defaults: `end_time` defaults to now, `start_time` defaults based on `days` parameter
- Full backward compatibility maintained - existing code using `days` parameter continues to work

### Tests
- Added comprehensive test coverage for new time-based parameters
- Tests for both `start_time` and `end_time` together
- Tests for individual parameter usage
- Tests for timezone-aware datetime handling
- Tests confirming time parameters override `days` parameter

## [3.1.4] - 2025-08-10

### Fixed
Expand Down
8 changes: 7 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Status: v3.1.4 - Stable Production Release
## Project Status: v3.1.5 - Stable Production Release

**IMPORTANT**: This project uses a fully asynchronous architecture. All APIs are async-only, optimized for high-performance futures trading.

Expand Down Expand Up @@ -300,6 +300,12 @@ async with ProjectX.from_env() as client:

## Recent Changes

### v3.1.5 - Enhanced Bar Data Retrieval
- **Added**: Optional `start_time` and `end_time` parameters to `get_bars()` method
- **Improved**: Precise time range specification for historical data queries
- **Enhanced**: Full timezone support with automatic UTC conversion
- **Maintained**: Complete backward compatibility with existing `days` parameter

### v3.1.4 - WebSocket Connection Fix
- **Fixed**: Critical WebSocket error with missing `_use_batching` attribute
- **Improved**: Proper mixin initialization in ProjectXRealtimeClient
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,14 @@ All 58+ indicators work with async data pipelines:
import polars as pl
from project_x_py.indicators import RSI, SMA, MACD, FVG, ORDERBLOCK, WAE

# Get data
data = await client.get_bars("ES", days=30)
# Get data - multiple ways
data = await client.get_bars("ES", days=30) # Last 30 days

# Or use specific time range (v3.1.5+)
from datetime import datetime
start = datetime(2025, 1, 1, 9, 30)
end = datetime(2025, 1, 10, 16, 0)
data = await client.get_bars("ES", start_time=start, end_time=end)

# Apply traditional indicators
data = data.pipe(SMA, period=20).pipe(RSI, period=14)
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
project = "project-x-py"
copyright = "2025, Jeff West"
author = "Jeff West"
release = "3.1.4"
version = "3.1.4"
release = "3.1.5"
version = "3.1.5"

# -- General configuration ---------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "project-x-py"
version = "3.1.4"
version = "3.1.5"
description = "High-performance Python SDK for futures trading with real-time WebSocket data, technical indicators, order management, and market depth analysis"
readme = "README.md"
license = { text = "MIT" }
Expand Down
2 changes: 1 addition & 1 deletion src/project_x_py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@

from project_x_py.client.base import ProjectXBase

__version__ = "3.1.4"
__version__ = "3.1.5"
__author__ = "TexasCoding"

# Core client classes - renamed from Async* to standard names
Expand Down
55 changes: 46 additions & 9 deletions src/project_x_py/client/market_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ async def get_bars(
unit: int = 2,
limit: int | None = None,
partial: bool = True,
start_time: datetime.datetime | None = None,
end_time: datetime.datetime | None = None,
) -> pl.DataFrame:
"""
Retrieve historical OHLCV bar data for an instrument.
Expand All @@ -338,12 +340,14 @@ async def get_bars(

Args:
symbol: Symbol of the instrument (e.g., "MGC", "MNQ", "ES")
days: Number of days of historical data (default: 8)
days: Number of days of historical data (default: 8, ignored if start_time/end_time provided)
interval: Interval between bars in the specified unit (default: 5)
unit: Time unit for the interval (default: 2 for minutes)
1=Second, 2=Minute, 3=Hour, 4=Day, 5=Week, 6=Month
limit: Maximum number of bars to retrieve (auto-calculated if None)
partial: Include incomplete/partial bars (default: True)
start_time: Optional start datetime (overrides days if provided)
end_time: Optional end datetime (defaults to now if not provided)

Returns:
pl.DataFrame: DataFrame with OHLCV data and timezone-aware timestamps
Expand Down Expand Up @@ -371,6 +375,11 @@ async def get_bars(
>>> # V3: Different time units available
>>> # unit=1 (seconds), 2 (minutes), 3 (hours), 4 (days)
>>> hourly_data = await client.get_bars("ES", days=1, interval=1, unit=3)
>>> # V3: Use specific time range
>>> from datetime import datetime
>>> start = datetime(2025, 1, 1, 9, 30)
>>> end = datetime(2025, 1, 1, 16, 0)
>>> data = await client.get_bars("ES", start_time=start, end_time=end)
"""
with LogContext(
logger,
Expand All @@ -383,27 +392,55 @@ async def get_bars(
):
await self._ensure_authenticated()

# Calculate date range
from datetime import timedelta

if start_time is not None or end_time is not None:
# Use provided time range
if start_time is not None:
# Ensure timezone awareness
if start_time.tzinfo is None:
start_date = pytz.UTC.localize(start_time)
else:
start_date = start_time.astimezone(pytz.UTC)
else:
# Default to days parameter ago if only end_time provided
start_date = datetime.datetime.now(pytz.UTC) - timedelta(days=days)

if end_time is not None:
# Ensure timezone awareness
if end_time.tzinfo is None:
end_date = pytz.UTC.localize(end_time)
else:
end_date = end_time.astimezone(pytz.UTC)
else:
# Default to now if only start_time provided
end_date = datetime.datetime.now(pytz.UTC)

# Calculate days for cache key (approximate)
days_calc = int((end_date - start_date).total_seconds() / 86400)
cache_key = f"{symbol}_{start_date.isoformat()}_{end_date.isoformat()}_{interval}_{unit}_{partial}"
else:
# Use days parameter
start_date = datetime.datetime.now(pytz.UTC) - timedelta(days=days)
end_date = datetime.datetime.now(pytz.UTC)
days_calc = days
cache_key = f"{symbol}_{days}_{interval}_{unit}_{partial}"

# Check market data cache
cache_key = f"{symbol}_{days}_{interval}_{unit}_{partial}"
cached_data = self.get_cached_market_data(cache_key)
if cached_data is not None:
logger.debug(LogMessages.CACHE_HIT, extra={"cache_key": cache_key})
return cached_data

logger.debug(
LogMessages.DATA_FETCH,
extra={"symbol": symbol, "days": days, "interval": interval},
extra={"symbol": symbol, "days": days_calc, "interval": interval},
)

# Lookup instrument
instrument = await self.get_instrument(symbol)

# Calculate date range
from datetime import timedelta

start_date = datetime.datetime.now(pytz.UTC) - timedelta(days=days)
end_date = datetime.datetime.now(pytz.UTC)

# Calculate limit based on unit type
if limit is None:
if unit == 1: # Seconds
Expand Down
2 changes: 1 addition & 1 deletion src/project_x_py/indicators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
)

# Version info
__version__ = "3.1.4"
__version__ = "3.1.5"
__author__ = "TexasCoding"


Expand Down
Loading
Loading