diff --git a/examples/debug_fvg_indicator.py b/examples/debug_fvg_indicator.py deleted file mode 100644 index 8afef4d..0000000 --- a/examples/debug_fvg_indicator.py +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env python -""" -Debug script for Fair Value Gap indicator. - -This script creates synthetic data with known FVG patterns and tests the indicator. -It also analyzes real data to understand why FVGs aren't being detected. - -Author: ProjectX SDK -Date: 2025-01-12 -""" - -import asyncio -import os -import sys - -import polars as pl - -# Add parent directory to path for imports -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -from project_x_py import ProjectX -from project_x_py.indicators import FVG - - -def create_synthetic_fvg_data() -> pl.DataFrame: - """Create synthetic OHLCV data with known Fair Value Gaps.""" - from datetime import datetime, timedelta - - base_time = datetime.now() - - # Create data with intentional FVG patterns - data = { - "timestamp": [base_time + timedelta(minutes=i * 15) for i in range(10)], - "open": [100.0, 102.0, 105.0, 108.0, 104.0, 106.0, 103.0, 105.0, 107.0, 106.0], - "high": [101.0, 103.0, 106.0, 110.0, 105.0, 107.0, 104.0, 106.0, 108.0, 107.0], - "low": [99.0, 101.0, 104.0, 107.0, 103.0, 105.0, 102.0, 104.0, 106.0, 105.0], - "close": [100.5, 102.5, 105.5, 108.5, 104.5, 106.5, 103.5, 105.5, 107.5, 106.5], - "volume": [1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900], - } - - # Add a clear bullish FVG at index 5-7 - # Bar 5: high = 102 - # Bar 6: low = 105, high = 108 (low > prev high) - # Bar 7: low = 110 (low > bar 6 high AND bar 6 low > bar 5 high) - data["high"][5] = 102.0 # Bar 5 high - data["low"][5] = 100.0 # Bar 5 low - - data["high"][6] = 108.0 # Bar 6 high - data["low"][6] = 105.0 # Bar 6 low (> bar 5 high) - - data["high"][7] = 115.0 # Bar 7 high - data["low"][7] = 110.0 # Bar 7 low (> bar 6 high) - - # This should create a bullish FVG at bar 7 - # Because: bar7.low (110) > bar6.high (108) AND bar6.low (105) > bar5.high (102) - - return pl.DataFrame(data) - - -def analyze_real_data_for_gaps(df: pl.DataFrame) -> None: - """Analyze real data to understand why FVGs aren't being detected.""" - print("\n" + "=" * 80) - print("ANALYZING DATA FOR POTENTIAL GAPS") - print("=" * 80) - - # Add shifted columns for analysis - analysis_df = df.with_columns( - [ - pl.col("high").shift(1).alias("prev_high"), - pl.col("low").shift(1).alias("prev_low"), - pl.col("high").shift(2).alias("prev2_high"), - pl.col("low").shift(2).alias("prev2_low"), - ] - ) - - # Check bullish FVG conditions - analysis_df = analysis_df.with_columns( - [ - # First condition: current low > prev high - (pl.col("low") > pl.col("prev_high")).alias("bullish_cond1"), - # Second condition: prev low > prev2 high - (pl.col("prev_low") > pl.col("prev2_high")).alias("bullish_cond2"), - # Both conditions - ( - (pl.col("low") > pl.col("prev_high")) - & (pl.col("prev_low") > pl.col("prev2_high")) - ).alias("bullish_fvg"), - ] - ) - - # Check bearish FVG conditions - analysis_df = analysis_df.with_columns( - [ - # First condition: current high < prev low - (pl.col("high") < pl.col("prev_low")).alias("bearish_cond1"), - # Second condition: prev high < prev2 low - (pl.col("prev_high") < pl.col("prev2_low")).alias("bearish_cond2"), - # Both conditions - ( - (pl.col("high") < pl.col("prev_low")) - & (pl.col("prev_high") < pl.col("prev2_low")) - ).alias("bearish_fvg"), - ] - ) - - # Count how often each condition is met - bullish_cond1_count = analysis_df.filter(pl.col("bullish_cond1")).height - bullish_cond2_count = analysis_df.filter(pl.col("bullish_cond2")).height - bullish_both_count = analysis_df.filter(pl.col("bullish_fvg")).height - - bearish_cond1_count = analysis_df.filter(pl.col("bearish_cond1")).height - bearish_cond2_count = analysis_df.filter(pl.col("bearish_cond2")).height - bearish_both_count = analysis_df.filter(pl.col("bearish_fvg")).height - - print(f"\nšŸ“Š BULLISH FVG CONDITIONS:") - print( - f" Condition 1 (low > prev_high): {bullish_cond1_count}/{df.height} bars ({bullish_cond1_count / df.height * 100:.1f}%)" - ) - print( - f" Condition 2 (prev_low > prev2_high): {bullish_cond2_count}/{df.height} bars ({bullish_cond2_count / df.height * 100:.1f}%)" - ) - print(f" Both conditions met: {bullish_both_count} bars") - - print(f"\nšŸ“Š BEARISH FVG CONDITIONS:") - print( - f" Condition 1 (high < prev_low): {bearish_cond1_count}/{df.height} bars ({bearish_cond1_count / df.height * 100:.1f}%)" - ) - print( - f" Condition 2 (prev_high < prev2_low): {bearish_cond2_count}/{df.height} bars ({bearish_cond2_count / df.height * 100:.1f}%)" - ) - print(f" Both conditions met: {bearish_both_count} bars") - - # Show examples where condition 1 is met but not condition 2 - if bullish_cond1_count > 0 and bullish_both_count == 0: - print("\nāš ļø Examples where bullish condition 1 is met but not condition 2:") - examples = analysis_df.filter(pl.col("bullish_cond1")).head(3) - for row in examples.iter_rows(named=True): - print(f" Time: {row['timestamp']}") - print(f" Current: L={row['low']:.2f}, H={row['high']:.2f}") - print(f" Prev: L={row['prev_low']:.2f}, H={row['prev_high']:.2f}") - print(f" Prev2: L={row['prev2_low']:.2f}, H={row['prev2_high']:.2f}") - print( - f" Cond1: {row['low']:.2f} > {row['prev_high']:.2f} = {row['bullish_cond1']}" - ) - print( - f" Cond2: {row['prev_low']:.2f} > {row['prev2_high']:.2f} = {row['bullish_cond2']}" - ) - - # Look for simpler gap patterns (just current low > prev high or current high < prev low) - simple_bullish_gaps = analysis_df.filter(pl.col("low") > pl.col("prev_high")).height - simple_bearish_gaps = analysis_df.filter(pl.col("high") < pl.col("prev_low")).height - - print(f"\nšŸ’” SIMPLER GAP PATTERNS:") - print(f" Simple bullish gaps (low > prev_high): {simple_bullish_gaps} bars") - print(f" Simple bearish gaps (high < prev_low): {simple_bearish_gaps} bars") - - if simple_bullish_gaps > 0: - print("\n Examples of simple bullish gaps:") - examples = analysis_df.filter(pl.col("low") > pl.col("prev_high")).head(5) - for row in examples.iter_rows(named=True): - gap_size = row["low"] - row["prev_high"] - print( - f" {row['timestamp']}: Gap of ${gap_size:.2f} (Low ${row['low']:.2f} > Prev High ${row['prev_high']:.2f})" - ) - - -async def main(): - """Main function to debug FVG indicator.""" - print("šŸ” Fair Value Gap Indicator Debug") - print("=" * 80) - - # Test with synthetic data first - print("\n1ļøāƒ£ TESTING WITH SYNTHETIC DATA") - print("-" * 40) - - synthetic_df = create_synthetic_fvg_data() - print(f"Created synthetic data with {synthetic_df.height} bars") - print("\nSynthetic data:") - print(synthetic_df.select(["timestamp", "open", "high", "low", "close"])) - - # Apply FVG indicator to synthetic data - synthetic_with_fvg = synthetic_df.pipe(FVG, min_gap_size=0.0) - - # Check for detected FVGs - bullish_fvgs = synthetic_with_fvg.filter(pl.col("fvg_bullish")) - bearish_fvgs = synthetic_with_fvg.filter(pl.col("fvg_bearish")) - - print(f"\nāœ… Detected {bullish_fvgs.height} bullish FVGs in synthetic data") - print(f"āœ… Detected {bearish_fvgs.height} bearish FVGs in synthetic data") - - if bullish_fvgs.height > 0: - print("\nBullish FVGs found:") - for row in bullish_fvgs.iter_rows(named=True): - print(f" Time: {row['timestamp']}") - print(f" Gap Top: ${row.get('fvg_gap_top', 'N/A')}") - print(f" Gap Bottom: ${row.get('fvg_gap_bottom', 'N/A')}") - print(f" Gap Size: ${row.get('fvg_gap_size', 'N/A')}") - - # Now test with real data - print("\n2ļøāƒ£ TESTING WITH REAL MARKET DATA") - print("-" * 40) - - try: - async with ProjectX.from_env() as client: - await client.authenticate() - print(f"āœ… Connected to account: {client.account_info.name}") - - # Get historical data - try 1-minute bars for more samples - print("\nšŸ“„ Loading 1-minute bar data...") - bars_df = await client.get_bars( - "MNQ", - days=10, - interval=1, # 1-minute bars - unit=2, # 2 = minutes - ) - - if bars_df is None or bars_df.is_empty(): - print("āŒ No data retrieved!") - return - - print(f"āœ… Loaded {bars_df.height} bars") - - # Analyze why FVGs aren't being detected - analyze_real_data_for_gaps(bars_df) - - # Apply FVG indicator with very low threshold - print("\n3ļøāƒ£ APPLYING FVG INDICATOR TO REAL DATA") - print("-" * 40) - - fvg_df = bars_df.pipe(FVG, min_gap_size=0.0) - - # Check results - bullish_count = fvg_df.filter(pl.col("fvg_bullish")).height - bearish_count = fvg_df.filter(pl.col("fvg_bearish")).height - - print(f"\nšŸ“ˆ Bullish FVGs detected: {bullish_count}") - print(f"šŸ“‰ Bearish FVGs detected: {bearish_count}") - - if bullish_count == 0 and bearish_count == 0: - print("\nāš ļø No FVGs detected! The conditions may be too strict.") - print("The current FVG definition requires:") - print(" - Three consecutive bars with very specific gap patterns") - print( - " - These patterns are extremely rare in normal market conditions" - ) - print("\nšŸ’” Suggestion: Consider using simpler gap detection logic") - - except Exception as e: - print(f"\nāŒ Error: {e}") - import traceback - - traceback.print_exc() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/debug_orderblock_indicator.py b/examples/debug_orderblock_indicator.py deleted file mode 100644 index ad50468..0000000 --- a/examples/debug_orderblock_indicator.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python -""" -Debug script for Order Block indicator to check output values. - -Author: ProjectX SDK -Date: 2025-01-12 -""" - -import asyncio -import os -import sys - -import polars as pl - -# Add parent directory to path for imports -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -from project_x_py import ProjectX -from project_x_py.indicators import ORDERBLOCK - - -async def main(): - """Main function to debug Order Block indicator.""" - print("šŸ” Order Block Indicator Debug") - print("=" * 80) - - try: - async with ProjectX.from_env() as client: - await client.authenticate() - print(f"āœ… Connected to account: {client.account_info.name}") - - # Get a small sample of data - print("\nšŸ“„ Loading sample data...") - bars_df = await client.get_bars( - "MNQ", - days=1, - interval=5, # 5-minute bars for cleaner data - unit=2, # 2 = minutes - ) - - if bars_df is None or bars_df.is_empty(): - print("āŒ No data retrieved!") - return - - print(f"āœ… Loaded {bars_df.height} bars") - - # Apply Order Block indicator - print("\n" + "=" * 80) - print("APPLYING ORDER BLOCK INDICATOR") - print("=" * 80) - - ob_df = bars_df.pipe(ORDERBLOCK, lookback_periods=10) - - # Check the column types - print("\nšŸ“Š Column Information:") - for col in ob_df.columns: - if col.startswith("ob_"): - dtype = ob_df[col].dtype - print(f" {col}: {dtype}") - - # Show some actual values - print("\nšŸ“Š Sample Data with Order Block columns:") - display_cols = ["timestamp", "high", "low", "close", "volume"] - ob_cols = [col for col in ob_df.columns if col.startswith("ob_")] - display_cols.extend(ob_cols) - - # Show first few rows with order blocks - ob_detected = ob_df.filter(pl.col("ob_bullish") | pl.col("ob_bearish")) - if ob_detected.height > 0: - print(f"\nFound {ob_detected.height} bars with order blocks") - print("\nFirst 5 bars with order blocks:") - sample = ob_detected.select(display_cols).head(5) - print(sample) - - # Show detailed view of order block values - print("\nšŸ“Š Detailed Order Block Values:") - for idx, row in enumerate(ob_detected.head(10).iter_rows(named=True)): - print(f"\nBar {idx + 1}:") - print(f" Time: {row['timestamp']}") - print( - f" Price: H=${row['high']:.2f}, L=${row['low']:.2f}, C=${row['close']:.2f}" - ) - print( - f" ob_bullish: {row['ob_bullish']} (type: {type(row['ob_bullish'])})" - ) - print( - f" ob_bearish: {row['ob_bearish']} (type: {type(row['ob_bearish'])})" - ) - print(f" ob_top: {row.get('ob_top', 'N/A')}") - print(f" ob_bottom: {row.get('ob_bottom', 'N/A')}") - print(f" ob_volume: {row.get('ob_volume', 'N/A')}") - print(f" ob_strength: {row.get('ob_strength', 'N/A')}") - else: - print("\nNo order blocks detected in the data") - - # Check unique values - print("\nšŸ“Š Unique values in ob_bullish column:") - unique_bullish = ob_df["ob_bullish"].unique() - print(f" Values: {unique_bullish.to_list()}") - - print("\nšŸ“Š Unique values in ob_bearish column:") - unique_bearish = ob_df["ob_bearish"].unique() - print(f" Values: {unique_bearish.to_list()}") - - except Exception as e: - print(f"\nāŒ Error: {e}") - import traceback - - traceback.print_exc() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/src/project_x_py/indicators/candlestick.py b/src/project_x_py/indicators/candlestick.py index ef17fb0..a77b936 100644 --- a/src/project_x_py/indicators/candlestick.py +++ b/src/project_x_py/indicators/candlestick.py @@ -313,10 +313,6 @@ def calculate( ) -# Add similar classes for BearishEngulfing, MorningStar, etc. - -# For brevity, stopping here. You can add more similarly. - # Convenience functions @@ -334,6 +330,3 @@ def calculate_shootingstar(data: pl.DataFrame, **kwargs: Any) -> pl.DataFrame: def calculate_bullishengulfing(data: pl.DataFrame, **kwargs: Any) -> pl.DataFrame: return BullishEngulfing().calculate(data, **kwargs) - - -# Add more convenience functions as classes are added diff --git a/src/project_x_py/orderbook/base.py b/src/project_x_py/orderbook/base.py index b6d2bc2..432aae4 100644 --- a/src/project_x_py/orderbook/base.py +++ b/src/project_x_py/orderbook/base.py @@ -686,23 +686,23 @@ async def add_callback(self, event_type: str, callback: CallbackType) -> None: the event data specific to that event type. Example: - >>> # V3: DEPRECATED - Use EventBus instead - >>> # Old callback style (deprecated): - >>> # await orderbook.add_callback("trade", on_trade) - >>> # V3: Modern EventBus approach - >>> from project_x_py.events import EventBus, EventType - >>> event_bus = EventBus() - >>> @event_bus.on(EventType.TRADE_TICK) - >>> async def on_trade(data): - ... print( - ... f"Trade: {data['size']} @ {data['price']} ({data['side']})" - ... ) # V3: actual field names - >>> @event_bus.on(EventType.MARKET_DEPTH_UPDATE) - >>> async def on_depth_change(data): + >>> # Use TradingSuite with EventBus for callbacks + >>> from project_x_py import TradingSuite, EventType + >>> + >>> suite = await TradingSuite.create("MNQ", features=["orderbook"]) + >>> + >>> @suite.events.on(EventType.TRADE_TICK) + >>> async def on_trade(event): + ... data = event.data + ... print(f"Trade: {data['size']} @ {data['price']} ({data['side']})") + >>> + >>> @suite.events.on(EventType.MARKET_DEPTH_UPDATE) + >>> async def on_depth_change(event): + ... data = event.data ... print( ... f"New best bid: {data['bids'][0]['price'] if data['bids'] else 'None'}" ... ) - >>> # V3: Events automatically flow through EventBus + >>> # Events automatically flow through EventBus """ async with self._callback_lock: logger.warning( diff --git a/src/project_x_py/realtime_data_manager/data_access.py b/src/project_x_py/realtime_data_manager/data_access.py index 7bf3af7..8c0ad2e 100644 --- a/src/project_x_py/realtime_data_manager/data_access.py +++ b/src/project_x_py/realtime_data_manager/data_access.py @@ -566,40 +566,3 @@ async def get_data_or_none( if data is None or len(data) < min_bars: return None return data - - def get_latest_bar_sync( - self, - timeframe: str = "5min", - ) -> dict[str, float] | None: - """ - Synchronous method to get latest bar for use in properties. - - This is a special sync method for use in property getters where - async methods cannot be used. - - Args: - timeframe: Timeframe to retrieve (default: "5min") - - Returns: - dict: Latest bar as dictionary or None - - Note: - This method assumes data_lock is not held by the calling thread. - Use with caution in async contexts. - """ - if timeframe not in self.data: - return None - - df = self.data[timeframe] - if df.is_empty(): - return None - - row = df.row(-1, named=True) # Get last row - return { - "timestamp": row["timestamp"], - "open": float(row["open"]), - "high": float(row["high"]), - "low": float(row["low"]), - "close": float(row["close"]), - "volume": float(row["volume"]), - } diff --git a/src/project_x_py/types/base.py b/src/project_x_py/types/base.py index 9dfd774..3cb7706 100644 --- a/src/project_x_py/types/base.py +++ b/src/project_x_py/types/base.py @@ -41,12 +41,8 @@ async def async_handler(data: dict[str, Any]) -> None: print(f"Async callback: {data}") - def sync_handler(data: dict[str, Any]) -> None: - print(f"Sync callback: {data}") - - # Use in function signatures - def register_callback(callback: CallbackType) -> None: + async def register_callback(callback: AsyncCallback) -> None: pass diff --git a/uv.lock b/uv.lock index 8426924..8c41a88 100644 --- a/uv.lock +++ b/uv.lock @@ -943,7 +943,7 @@ wheels = [ [[package]] name = "project-x-py" -version = "3.1.10" +version = "3.1.11" source = { editable = "." } dependencies = [ { name = "cachetools" },