199 lines
7.0 KiB
Python
199 lines
7.0 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Simple alignment test with synthetic data to clearly show timeframe alignment.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import pandas as pd
|
||
|
|
import matplotlib.pyplot as plt
|
||
|
|
import matplotlib.dates as mdates
|
||
|
|
from matplotlib.patches import Rectangle
|
||
|
|
import sys
|
||
|
|
import os
|
||
|
|
|
||
|
|
# Add the project root to Python path
|
||
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
|
|
|
||
|
|
from IncrementalTrader.utils import aggregate_minute_data_to_timeframe, parse_timeframe_to_minutes
|
||
|
|
|
||
|
|
|
||
|
|
def create_simple_test_data():
|
||
|
|
"""Create simple test data for clear visualization."""
|
||
|
|
start_time = pd.Timestamp('2024-01-01 09:00:00')
|
||
|
|
minute_data = []
|
||
|
|
|
||
|
|
# Create exactly 60 minutes of data (4 complete 15-min bars)
|
||
|
|
for i in range(60):
|
||
|
|
timestamp = start_time + pd.Timedelta(minutes=i)
|
||
|
|
# Create a simple price pattern that's easy to follow
|
||
|
|
base_price = 100.0
|
||
|
|
minute_in_hour = i % 60
|
||
|
|
price_trend = base_price + (minute_in_hour * 0.1) # Gradual uptrend
|
||
|
|
|
||
|
|
minute_data.append({
|
||
|
|
'timestamp': timestamp,
|
||
|
|
'open': price_trend,
|
||
|
|
'high': price_trend + 0.2,
|
||
|
|
'low': price_trend - 0.2,
|
||
|
|
'close': price_trend + 0.1,
|
||
|
|
'volume': 1000
|
||
|
|
})
|
||
|
|
|
||
|
|
return minute_data
|
||
|
|
|
||
|
|
|
||
|
|
def plot_timeframe_bars(ax, data, timeframe, color, alpha=0.7, show_labels=True):
|
||
|
|
"""Plot timeframe bars with clear boundaries."""
|
||
|
|
if not data:
|
||
|
|
return
|
||
|
|
|
||
|
|
timeframe_minutes = parse_timeframe_to_minutes(timeframe)
|
||
|
|
|
||
|
|
for i, bar in enumerate(data):
|
||
|
|
timestamp = bar['timestamp']
|
||
|
|
open_price = bar['open']
|
||
|
|
high_price = bar['high']
|
||
|
|
low_price = bar['low']
|
||
|
|
close_price = bar['close']
|
||
|
|
|
||
|
|
# Calculate bar boundaries (end timestamp mode)
|
||
|
|
bar_start = timestamp - pd.Timedelta(minutes=timeframe_minutes)
|
||
|
|
bar_end = timestamp
|
||
|
|
|
||
|
|
# Draw the bar as a rectangle spanning the full time period
|
||
|
|
body_height = abs(close_price - open_price)
|
||
|
|
body_bottom = min(open_price, close_price)
|
||
|
|
|
||
|
|
# Bar body
|
||
|
|
rect = Rectangle((bar_start, body_bottom),
|
||
|
|
bar_end - bar_start, body_height,
|
||
|
|
facecolor=color, edgecolor='black',
|
||
|
|
alpha=alpha, linewidth=1)
|
||
|
|
ax.add_patch(rect)
|
||
|
|
|
||
|
|
# High-low wick at center
|
||
|
|
bar_center = bar_start + (bar_end - bar_start) / 2
|
||
|
|
ax.plot([bar_center, bar_center], [low_price, high_price],
|
||
|
|
color='black', linewidth=2, alpha=alpha)
|
||
|
|
|
||
|
|
# Add labels if requested
|
||
|
|
if show_labels:
|
||
|
|
ax.text(bar_center, high_price + 0.1, f"{timeframe}\n#{i+1}",
|
||
|
|
ha='center', va='bottom', fontsize=8, fontweight='bold')
|
||
|
|
|
||
|
|
|
||
|
|
def create_alignment_visualization():
|
||
|
|
"""Create a clear visualization of timeframe alignment."""
|
||
|
|
print("🎯 Creating Timeframe Alignment Visualization")
|
||
|
|
print("=" * 50)
|
||
|
|
|
||
|
|
# Create test data
|
||
|
|
minute_data = create_simple_test_data()
|
||
|
|
print(f"📊 Created {len(minute_data)} minute data points")
|
||
|
|
print(f"📅 Range: {minute_data[0]['timestamp']} to {minute_data[-1]['timestamp']}")
|
||
|
|
|
||
|
|
# Aggregate to different timeframes
|
||
|
|
timeframes = ["5min", "15min", "30min", "1h"]
|
||
|
|
colors = ['red', 'green', 'blue', 'purple']
|
||
|
|
alphas = [0.8, 0.6, 0.4, 0.2]
|
||
|
|
|
||
|
|
aggregated_data = {}
|
||
|
|
for tf in timeframes:
|
||
|
|
aggregated_data[tf] = aggregate_minute_data_to_timeframe(minute_data, tf, "end")
|
||
|
|
print(f" {tf}: {len(aggregated_data[tf])} bars")
|
||
|
|
|
||
|
|
# Create visualization
|
||
|
|
fig, ax = plt.subplots(1, 1, figsize=(16, 10))
|
||
|
|
fig.suptitle('Timeframe Alignment Visualization\n(Smaller timeframes should fit inside larger ones)',
|
||
|
|
fontsize=16, fontweight='bold')
|
||
|
|
|
||
|
|
# Plot timeframes from largest to smallest (background to foreground)
|
||
|
|
for i, tf in enumerate(reversed(timeframes)):
|
||
|
|
color = colors[timeframes.index(tf)]
|
||
|
|
alpha = alphas[timeframes.index(tf)]
|
||
|
|
show_labels = (tf in ["5min", "15min"]) # Only label smaller timeframes for clarity
|
||
|
|
|
||
|
|
plot_timeframe_bars(ax, aggregated_data[tf], tf, color, alpha, show_labels)
|
||
|
|
|
||
|
|
# Format the plot
|
||
|
|
ax.set_ylabel('Price (USD)', fontsize=12)
|
||
|
|
ax.set_xlabel('Time', fontsize=12)
|
||
|
|
ax.grid(True, alpha=0.3)
|
||
|
|
|
||
|
|
# Format x-axis
|
||
|
|
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
||
|
|
ax.xaxis.set_major_locator(mdates.MinuteLocator(interval=15))
|
||
|
|
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
|
||
|
|
|
||
|
|
# Add legend
|
||
|
|
legend_elements = []
|
||
|
|
for i, tf in enumerate(timeframes):
|
||
|
|
legend_elements.append(plt.Rectangle((0,0),1,1,
|
||
|
|
facecolor=colors[i],
|
||
|
|
alpha=alphas[i],
|
||
|
|
label=f"{tf} ({len(aggregated_data[tf])} bars)"))
|
||
|
|
|
||
|
|
ax.legend(handles=legend_elements, loc='upper left', fontsize=10)
|
||
|
|
|
||
|
|
# Add explanation
|
||
|
|
explanation = ("Each bar spans its full time period.\n"
|
||
|
|
"5min bars should fit exactly inside 15min bars.\n"
|
||
|
|
"15min bars should fit exactly inside 30min and 1h bars.")
|
||
|
|
ax.text(0.02, 0.98, explanation, transform=ax.transAxes,
|
||
|
|
verticalalignment='top', fontsize=10,
|
||
|
|
bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.9))
|
||
|
|
|
||
|
|
plt.tight_layout()
|
||
|
|
|
||
|
|
# Print alignment verification
|
||
|
|
print(f"\n🔍 Alignment Verification:")
|
||
|
|
bars_5m = aggregated_data["5min"]
|
||
|
|
bars_15m = aggregated_data["15min"]
|
||
|
|
|
||
|
|
for i, bar_15m in enumerate(bars_15m):
|
||
|
|
print(f"\n15min bar {i+1}: {bar_15m['timestamp']}")
|
||
|
|
bar_15m_start = bar_15m['timestamp'] - pd.Timedelta(minutes=15)
|
||
|
|
|
||
|
|
contained_5m = []
|
||
|
|
for bar_5m in bars_5m:
|
||
|
|
bar_5m_start = bar_5m['timestamp'] - pd.Timedelta(minutes=5)
|
||
|
|
bar_5m_end = bar_5m['timestamp']
|
||
|
|
|
||
|
|
# Check if 5min bar is contained within 15min bar
|
||
|
|
if bar_15m_start <= bar_5m_start and bar_5m_end <= bar_15m['timestamp']:
|
||
|
|
contained_5m.append(bar_5m)
|
||
|
|
|
||
|
|
print(f" Contains {len(contained_5m)} x 5min bars:")
|
||
|
|
for j, bar_5m in enumerate(contained_5m):
|
||
|
|
print(f" {j+1}. {bar_5m['timestamp']}")
|
||
|
|
|
||
|
|
return fig
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""Main function."""
|
||
|
|
print("🚀 Simple Timeframe Alignment Test")
|
||
|
|
print("=" * 40)
|
||
|
|
|
||
|
|
try:
|
||
|
|
fig = create_alignment_visualization()
|
||
|
|
plt.show()
|
||
|
|
|
||
|
|
print("\n✅ Alignment test completed!")
|
||
|
|
print("📊 In the chart, you should see:")
|
||
|
|
print(" - Each 15min bar contains exactly 3 x 5min bars")
|
||
|
|
print(" - Each 30min bar contains exactly 6 x 5min bars")
|
||
|
|
print(" - Each 1h bar contains exactly 12 x 5min bars")
|
||
|
|
print(" - All bars are properly aligned with no gaps or overlaps")
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
print(f"❌ Error: {e}")
|
||
|
|
import traceback
|
||
|
|
traceback.print_exc()
|
||
|
|
return False
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
success = main()
|
||
|
|
sys.exit(0 if success else 1)
|