Switch to simpler log-based projection.
This commit is contained in:
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import seaborn as sns
|
import seaborn as sns
|
||||||
from scipy.stats import norm
|
from scipy.stats import norm
|
||||||
|
from scipy.signal import savgol_filter
|
||||||
|
|
||||||
|
|
||||||
# Utility functions
|
# Utility functions
|
||||||
@@ -102,50 +103,49 @@ def get_nice_price_points(min_price, max_price):
|
|||||||
# Analysis functions
|
# Analysis functions
|
||||||
|
|
||||||
|
|
||||||
def analyze_cycles_with_halvings(df):
|
def analyze_trends(df):
|
||||||
"""Analyze Bitcoin market cycles aligned with halving events"""
|
"""
|
||||||
|
Analyze Bitcoin price trends using log returns.
|
||||||
|
"""
|
||||||
df = df.copy()
|
df = df.copy()
|
||||||
|
|
||||||
# Get halving dates
|
# Get halving dates and calculate cycle position
|
||||||
halving_dates = get_halving_dates()
|
halving_dates = get_halving_dates()
|
||||||
|
|
||||||
# Calculate cycle position for each date
|
|
||||||
df["Cycle_Position"] = df["Date"].apply(
|
df["Cycle_Position"] = df["Date"].apply(
|
||||||
lambda x: get_cycle_position(x, halving_dates)
|
lambda x: get_cycle_position(x, halving_dates)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert to days within cycle (0 to ~1460 days)
|
|
||||||
df["Cycle_Days"] = (df["Cycle_Position"] * 4 * 365).round().astype(int)
|
df["Cycle_Days"] = (df["Cycle_Position"] * 4 * 365).round().astype(int)
|
||||||
|
|
||||||
# Calculate returns at different scales
|
# Calculate log returns
|
||||||
df["Returns_30d"] = df["Close"].pct_change(periods=30)
|
df["Log_Price"] = np.log(df["Close"])
|
||||||
df["Returns_90d"] = df["Close"].pct_change(periods=90)
|
df["Log_Return"] = df["Log_Price"].diff()
|
||||||
df["Returns_365d"] = df["Close"].pct_change(periods=365)
|
|
||||||
|
|
||||||
# Group by position in cycle and calculate average returns
|
# Group by position in cycle
|
||||||
cycle_returns = df.groupby(df["Cycle_Days"])["Daily_Return"].mean()
|
position_returns = df.groupby("Cycle_Days")["Log_Return"].mean()
|
||||||
cycle_volatility = df.groupby(df["Cycle_Days"])["Daily_Return"].std()
|
|
||||||
|
|
||||||
# Smooth the cycle returns to reduce noise
|
|
||||||
from scipy.signal import savgol_filter
|
|
||||||
|
|
||||||
|
# Smooth the returns using Savitzky-Golay filter
|
||||||
window = 91 # About 3 months
|
window = 91 # About 3 months
|
||||||
if len(cycle_returns) > window:
|
if len(position_returns) > window:
|
||||||
cycle_returns = pd.Series(
|
position_returns = pd.Series(
|
||||||
savgol_filter(cycle_returns, window, 3), index=cycle_returns.index
|
savgol_filter(position_returns, window, 3), index=position_returns.index
|
||||||
)
|
)
|
||||||
|
|
||||||
return cycle_returns, cycle_volatility
|
return position_returns
|
||||||
|
|
||||||
|
|
||||||
def project_prices_with_cycles(
|
def project_prices(
|
||||||
df, days_forward=365, simulations=1000, confidence_levels=[0.95, 0.68]
|
df, days_forward=365, simulations=1000, confidence_levels=[0.95, 0.68]
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Project future Bitcoin prices using Monte Carlo simulation with halving-aligned cycles.
|
Project future Bitcoin prices using Monte Carlo simulation.
|
||||||
"""
|
"""
|
||||||
# Analyze historical cycles
|
# Calculate log returns for volatility estimation
|
||||||
cycle_returns, cycle_volatility = analyze_cycles_with_halvings(df)
|
df = df.copy()
|
||||||
|
df["Log_Price"] = np.log(df["Close"])
|
||||||
|
df["Log_Return"] = df["Log_Price"].diff()
|
||||||
|
|
||||||
|
# Get cycle-based trends
|
||||||
|
cycle_trends = analyze_trends(df)
|
||||||
|
|
||||||
# Get current position in halving cycle
|
# Get current position in halving cycle
|
||||||
halving_dates = get_halving_dates()
|
halving_dates = get_halving_dates()
|
||||||
@@ -153,7 +153,7 @@ def project_prices_with_cycles(
|
|||||||
cycle_position = get_cycle_position(current_date, halving_dates)
|
cycle_position = get_cycle_position(current_date, halving_dates)
|
||||||
current_cycle_days = int(cycle_position * 4 * 365)
|
current_cycle_days = int(cycle_position * 4 * 365)
|
||||||
|
|
||||||
# Current price (last known price)
|
# Current price and date
|
||||||
last_price = df["Close"].iloc[-1]
|
last_price = df["Close"].iloc[-1]
|
||||||
last_date = df["Date"].iloc[-1]
|
last_date = df["Date"].iloc[-1]
|
||||||
|
|
||||||
@@ -167,15 +167,11 @@ def project_prices_with_cycles(
|
|||||||
(current_cycle_days + i) % (4 * 365) for i in range(days_forward)
|
(current_cycle_days + i) % (4 * 365) for i in range(days_forward)
|
||||||
]
|
]
|
||||||
expected_returns = np.array(
|
expected_returns = np.array(
|
||||||
[cycle_returns.get(day, cycle_returns.mean()) for day in future_cycle_days]
|
[cycle_trends.get(day, cycle_trends.mean()) for day in future_cycle_days]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Calculate base volatility (recent)
|
# Calculate volatility using recent data
|
||||||
recent_volatility = df["Daily_Return"].tail(90).std()
|
recent_volatility = df["Log_Return"].tail(90).std()
|
||||||
|
|
||||||
# Add long-term trend component (very gentle decay)
|
|
||||||
long_term_decay = 0.9 ** (np.arange(days_forward) / 365) # 10% reduction per year
|
|
||||||
expected_returns = expected_returns * long_term_decay
|
|
||||||
|
|
||||||
# Run Monte Carlo simulation
|
# Run Monte Carlo simulation
|
||||||
np.random.seed(42) # For reproducibility
|
np.random.seed(42) # For reproducibility
|
||||||
@@ -186,9 +182,9 @@ def project_prices_with_cycles(
|
|||||||
returns = np.random.normal(
|
returns = np.random.normal(
|
||||||
loc=expected_returns, scale=recent_volatility, size=days_forward
|
loc=expected_returns, scale=recent_volatility, size=days_forward
|
||||||
)
|
)
|
||||||
|
# Since we're using log returns, we can simply sum them
|
||||||
# Calculate price path
|
cumulative_returns = np.cumsum(returns)
|
||||||
price_path = last_price * np.exp(np.cumsum(returns))
|
price_path = last_price * np.exp(cumulative_returns)
|
||||||
simulated_paths[:, sim] = price_path
|
simulated_paths[:, sim] = price_path
|
||||||
|
|
||||||
# Calculate percentiles for confidence intervals
|
# Calculate percentiles for confidence intervals
|
||||||
@@ -206,7 +202,7 @@ def project_prices_with_cycles(
|
|||||||
simulated_paths, upper_percentile, axis=1
|
simulated_paths, upper_percentile, axis=1
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add expected trend line (without randomness)
|
# Add expected trend line
|
||||||
results["Expected_Trend"] = last_price * np.exp(np.cumsum(expected_returns))
|
results["Expected_Trend"] = last_price * np.exp(np.cumsum(expected_returns))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
@@ -301,11 +297,11 @@ def create_plots(df, start=None, end=None, project_days=365):
|
|||||||
raise ValueError("No data found for the specified date range")
|
raise ValueError("No data found for the specified date range")
|
||||||
|
|
||||||
# Generate projections
|
# Generate projections
|
||||||
cycle_returns, cycle_volatility = analyze_cycles_with_halvings(plot_df)
|
# cycle_returns, cycle_volatility = analyze_trends(plot_df)
|
||||||
projections = project_prices_with_cycles(plot_df, days_forward=project_days)
|
projections = project_prices(plot_df, days_forward=project_days)
|
||||||
|
|
||||||
# Create cycle visualization
|
# Create cycle visualization
|
||||||
visualize_cycle_patterns(plot_df, cycle_returns, cycle_volatility)
|
# visualize_cycle_patterns(plot_df, cycle_returns, cycle_volatility)
|
||||||
|
|
||||||
# Set up the style
|
# Set up the style
|
||||||
plt.style.use("seaborn-v0_8")
|
plt.style.use("seaborn-v0_8")
|
||||||
@@ -743,9 +739,7 @@ def create_backtest_plot(
|
|||||||
raise ValueError("Insufficient training data before backtest date")
|
raise ValueError("Insufficient training data before backtest date")
|
||||||
|
|
||||||
# Generate historical projections using only training data
|
# Generate historical projections using only training data
|
||||||
historical_projections = project_prices_with_cycles(
|
historical_projections = project_prices(training_df, days_forward=project_days)
|
||||||
training_df, days_forward=project_days
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set up the plot
|
# Set up the plot
|
||||||
plt.style.use("seaborn-v0_8")
|
plt.style.use("seaborn-v0_8")
|
||||||
|
|||||||
Reference in New Issue
Block a user