Skip to content

Commit 09870ea

Browse files
authored
Merge pull request #37 from TexasCoding/update_get_bars
v3.1.5: Add optional start_time and end_time parameters to get_bars
2 parents cdb5844 + e1f5b22 commit 09870ea

File tree

10 files changed

+330
-18
lines changed

10 files changed

+330
-18
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Migration guides will be provided for all breaking changes
1515
- Semantic versioning (MAJOR.MINOR.PATCH) is strictly followed
1616

17+
## [3.1.5] - 2025-08-11
18+
19+
### Added
20+
- **📊 Enhanced Bar Data Retrieval**: Added optional `start_time` and `end_time` parameters to `get_bars()` method
21+
- Allows precise time range specification for historical data queries
22+
- Parameters override the `days` argument when provided
23+
- Supports both timezone-aware and naive datetime objects
24+
- Automatically converts times to UTC for API consistency
25+
- Smart defaults: `end_time` defaults to now, `start_time` defaults based on `days` parameter
26+
- Full backward compatibility maintained - existing code using `days` parameter continues to work
27+
28+
### Tests
29+
- Added comprehensive test coverage for new time-based parameters
30+
- Tests for both `start_time` and `end_time` together
31+
- Tests for individual parameter usage
32+
- Tests for timezone-aware datetime handling
33+
- Tests confirming time parameters override `days` parameter
34+
1735
## [3.1.4] - 2025-08-10
1836

1937
### Fixed

CLAUDE.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

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

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

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

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

301301
## Recent Changes
302302

303+
### v3.1.5 - Enhanced Bar Data Retrieval
304+
- **Added**: Optional `start_time` and `end_time` parameters to `get_bars()` method
305+
- **Improved**: Precise time range specification for historical data queries
306+
- **Enhanced**: Full timezone support with automatic UTC conversion
307+
- **Maintained**: Complete backward compatibility with existing `days` parameter
308+
303309
### v3.1.4 - WebSocket Connection Fix
304310
- **Fixed**: Critical WebSocket error with missing `_use_batching` attribute
305311
- **Improved**: Proper mixin initialization in ProjectXRealtimeClient

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,14 @@ All 58+ indicators work with async data pipelines:
294294
import polars as pl
295295
from project_x_py.indicators import RSI, SMA, MACD, FVG, ORDERBLOCK, WAE
296296

297-
# Get data
298-
data = await client.get_bars("ES", days=30)
297+
# Get data - multiple ways
298+
data = await client.get_bars("ES", days=30) # Last 30 days
299+
300+
# Or use specific time range (v3.1.5+)
301+
from datetime import datetime
302+
start = datetime(2025, 1, 1, 9, 30)
303+
end = datetime(2025, 1, 10, 16, 0)
304+
data = await client.get_bars("ES", start_time=start, end_time=end)
299305

300306
# Apply traditional indicators
301307
data = data.pipe(SMA, period=20).pipe(RSI, period=14)

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
project = "project-x-py"
2424
copyright = "2025, Jeff West"
2525
author = "Jeff West"
26-
release = "3.1.4"
27-
version = "3.1.4"
26+
release = "3.1.5"
27+
version = "3.1.5"
2828

2929
# -- General configuration ---------------------------------------------------
3030

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "project-x-py"
3-
version = "3.1.4"
3+
version = "3.1.5"
44
description = "High-performance Python SDK for futures trading with real-time WebSocket data, technical indicators, order management, and market depth analysis"
55
readme = "README.md"
66
license = { text = "MIT" }

src/project_x_py/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595

9696
from project_x_py.client.base import ProjectXBase
9797

98-
__version__ = "3.1.4"
98+
__version__ = "3.1.5"
9999
__author__ = "TexasCoding"
100100

101101
# Core client classes - renamed from Async* to standard names

src/project_x_py/client/market_data.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,8 @@ async def get_bars(
328328
unit: int = 2,
329329
limit: int | None = None,
330330
partial: bool = True,
331+
start_time: datetime.datetime | None = None,
332+
end_time: datetime.datetime | None = None,
331333
) -> pl.DataFrame:
332334
"""
333335
Retrieve historical OHLCV bar data for an instrument.
@@ -338,12 +340,14 @@ async def get_bars(
338340
339341
Args:
340342
symbol: Symbol of the instrument (e.g., "MGC", "MNQ", "ES")
341-
days: Number of days of historical data (default: 8)
343+
days: Number of days of historical data (default: 8, ignored if start_time/end_time provided)
342344
interval: Interval between bars in the specified unit (default: 5)
343345
unit: Time unit for the interval (default: 2 for minutes)
344346
1=Second, 2=Minute, 3=Hour, 4=Day, 5=Week, 6=Month
345347
limit: Maximum number of bars to retrieve (auto-calculated if None)
346348
partial: Include incomplete/partial bars (default: True)
349+
start_time: Optional start datetime (overrides days if provided)
350+
end_time: Optional end datetime (defaults to now if not provided)
347351
348352
Returns:
349353
pl.DataFrame: DataFrame with OHLCV data and timezone-aware timestamps
@@ -371,6 +375,11 @@ async def get_bars(
371375
>>> # V3: Different time units available
372376
>>> # unit=1 (seconds), 2 (minutes), 3 (hours), 4 (days)
373377
>>> hourly_data = await client.get_bars("ES", days=1, interval=1, unit=3)
378+
>>> # V3: Use specific time range
379+
>>> from datetime import datetime
380+
>>> start = datetime(2025, 1, 1, 9, 30)
381+
>>> end = datetime(2025, 1, 1, 16, 0)
382+
>>> data = await client.get_bars("ES", start_time=start, end_time=end)
374383
"""
375384
with LogContext(
376385
logger,
@@ -383,27 +392,55 @@ async def get_bars(
383392
):
384393
await self._ensure_authenticated()
385394

395+
# Calculate date range
396+
from datetime import timedelta
397+
398+
if start_time is not None or end_time is not None:
399+
# Use provided time range
400+
if start_time is not None:
401+
# Ensure timezone awareness
402+
if start_time.tzinfo is None:
403+
start_date = pytz.UTC.localize(start_time)
404+
else:
405+
start_date = start_time.astimezone(pytz.UTC)
406+
else:
407+
# Default to days parameter ago if only end_time provided
408+
start_date = datetime.datetime.now(pytz.UTC) - timedelta(days=days)
409+
410+
if end_time is not None:
411+
# Ensure timezone awareness
412+
if end_time.tzinfo is None:
413+
end_date = pytz.UTC.localize(end_time)
414+
else:
415+
end_date = end_time.astimezone(pytz.UTC)
416+
else:
417+
# Default to now if only start_time provided
418+
end_date = datetime.datetime.now(pytz.UTC)
419+
420+
# Calculate days for cache key (approximate)
421+
days_calc = int((end_date - start_date).total_seconds() / 86400)
422+
cache_key = f"{symbol}_{start_date.isoformat()}_{end_date.isoformat()}_{interval}_{unit}_{partial}"
423+
else:
424+
# Use days parameter
425+
start_date = datetime.datetime.now(pytz.UTC) - timedelta(days=days)
426+
end_date = datetime.datetime.now(pytz.UTC)
427+
days_calc = days
428+
cache_key = f"{symbol}_{days}_{interval}_{unit}_{partial}"
429+
386430
# Check market data cache
387-
cache_key = f"{symbol}_{days}_{interval}_{unit}_{partial}"
388431
cached_data = self.get_cached_market_data(cache_key)
389432
if cached_data is not None:
390433
logger.debug(LogMessages.CACHE_HIT, extra={"cache_key": cache_key})
391434
return cached_data
392435

393436
logger.debug(
394437
LogMessages.DATA_FETCH,
395-
extra={"symbol": symbol, "days": days, "interval": interval},
438+
extra={"symbol": symbol, "days": days_calc, "interval": interval},
396439
)
397440

398441
# Lookup instrument
399442
instrument = await self.get_instrument(symbol)
400443

401-
# Calculate date range
402-
from datetime import timedelta
403-
404-
start_date = datetime.datetime.now(pytz.UTC) - timedelta(days=days)
405-
end_date = datetime.datetime.now(pytz.UTC)
406-
407444
# Calculate limit based on unit type
408445
if limit is None:
409446
if unit == 1: # Seconds

src/project_x_py/indicators/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@
202202
)
203203

204204
# Version info
205-
__version__ = "3.1.4"
205+
__version__ = "3.1.5"
206206
__author__ = "TexasCoding"
207207

208208

0 commit comments

Comments
 (0)