Improve uncertainty estimation.
This commit is contained in:
@@ -222,44 +222,136 @@ def adjust_trend_expectations(expected_returns, cycle_position):
|
||||
return expected_returns * damping_factor
|
||||
|
||||
|
||||
def get_projection_adjustments(days_forward, current_cycle_position):
|
||||
def calculate_market_conditions(df, lookback_window=180):
|
||||
"""
|
||||
Final projection adjustments with precise uncertainty scaling.
|
||||
Calculate market condition metrics to inform uncertainty scaling.
|
||||
"""
|
||||
df = df.copy() # Avoid modifying original dataframe
|
||||
metrics = {}
|
||||
|
||||
# Use log returns for stability
|
||||
df["Log_Return"] = np.log(df["Close"]).diff()
|
||||
|
||||
# Handle initial NaN values
|
||||
df["Log_Return"] = df["Log_Return"].fillna(method="bfill")
|
||||
|
||||
# Recent vs historical volatility ratio
|
||||
recent_vol = max(df["Log_Return"].tail(30).std(), 1e-8) # Prevent division by zero
|
||||
historical_vol = max(df["Log_Return"].tail(lookback_window).std(), 1e-8)
|
||||
metrics["vol_ratio"] = recent_vol / historical_vol
|
||||
|
||||
# Trend strength using log prices
|
||||
log_prices = np.log(df["Close"])
|
||||
ma50 = log_prices.rolling(50, min_periods=1).mean()
|
||||
ma200 = log_prices.rolling(200, min_periods=1).mean()
|
||||
metrics["trend_strength"] = (ma50.iloc[-1] - ma200.iloc[-1]) / historical_vol
|
||||
|
||||
# Drawdown intensity
|
||||
rolling_max = df["Close"].rolling(lookback_window, min_periods=1).max()
|
||||
current_drawdown = df["Close"].iloc[-1] / rolling_max.iloc[-1] - 1
|
||||
metrics["drawdown"] = abs(min(current_drawdown, 0))
|
||||
|
||||
return metrics
|
||||
|
||||
|
||||
def get_projection_adjustments(days_forward, current_cycle_position, df):
|
||||
"""
|
||||
Enhanced projection adjustments with dynamic uncertainty scaling.
|
||||
"""
|
||||
adjustments = np.ones(days_forward)
|
||||
|
||||
# Fixed base uncertainty with slight cycle variation
|
||||
# Get market condition metrics
|
||||
conditions = calculate_market_conditions(df)
|
||||
|
||||
# Base uncertainty varies with market conditions
|
||||
base_uncertainty = 0.016 # Standard rate
|
||||
|
||||
# Increase uncertainty if volatility is unusually high or low
|
||||
vol_factor = 1 + 0.2 * abs(1 - conditions["vol_ratio"])
|
||||
|
||||
# Increase uncertainty during strong trends (both up and down)
|
||||
trend_factor = 1 + 0.15 * abs(conditions["trend_strength"])
|
||||
|
||||
# Increase uncertainty during significant drawdowns
|
||||
drawdown_factor = 1 + 0.25 * conditions["drawdown"]
|
||||
|
||||
# Combine factors with cycle position
|
||||
if current_cycle_position > 0.75:
|
||||
base_uncertainty *= 1.1 # 10% increase late cycle
|
||||
cycle_factor = 1.15 # Higher uncertainty late in cycle
|
||||
else:
|
||||
cycle_factor = 1.0
|
||||
|
||||
# Calculate time-varying uncertainty
|
||||
for i in range(days_forward):
|
||||
# Conservative growth with fixed cap
|
||||
time_factor = min(1 + (i / 365) * base_uncertainty, 1.055) # Lower cap
|
||||
# Conservative growth with cycle and condition awareness
|
||||
time_factor = min(
|
||||
1
|
||||
+ (i / 365)
|
||||
* base_uncertainty
|
||||
* vol_factor
|
||||
* trend_factor
|
||||
* drawdown_factor,
|
||||
1.20,
|
||||
)
|
||||
|
||||
# Simpler cycle factors
|
||||
# Update cycle position for this future point
|
||||
cycle_position = (current_cycle_position + i / 1460) % 1
|
||||
if cycle_position > 0.75:
|
||||
cycle_factor = 0.94
|
||||
local_cycle_factor = 1.15
|
||||
else:
|
||||
cycle_factor = 0.96
|
||||
local_cycle_factor = 1.0
|
||||
|
||||
adjustments[i] = time_factor * cycle_factor
|
||||
# Apply all factors including the initial cycle factor
|
||||
adjustments[i] = time_factor * local_cycle_factor * cycle_factor
|
||||
|
||||
# Add minimum floor to prevent overconfidence
|
||||
adjustments[i] = max(adjustments[i], 1.02 + (i / 365) * 0.01)
|
||||
|
||||
return adjustments
|
||||
|
||||
|
||||
def calculate_confidence_intervals(simulated_paths, confidence_levels=[0.95, 0.68]):
|
||||
"""
|
||||
Calculate confidence intervals with dynamic quantile selection based on market conditions.
|
||||
"""
|
||||
results = {}
|
||||
|
||||
for level in confidence_levels:
|
||||
# Calculate standard error of the median
|
||||
median_std = np.std(
|
||||
[np.median(simulated_paths[:, i]) for i in range(simulated_paths.shape[1])]
|
||||
)
|
||||
|
||||
# Adjust quantiles based on estimation uncertainty
|
||||
adjustment = min(0.1, median_std / np.median(simulated_paths)) # Cap adjustment
|
||||
|
||||
# Widen intervals slightly when uncertainty is high
|
||||
effective_level = level + (1 - level) * adjustment
|
||||
|
||||
lower_percentile = (1 - effective_level) * 100 / 2
|
||||
upper_percentile = 100 - lower_percentile
|
||||
|
||||
results[f"Lower_{int(level*100)}"] = np.percentile(
|
||||
simulated_paths, lower_percentile, axis=1
|
||||
)
|
||||
results[f"Upper_{int(level*100)}"] = np.percentile(
|
||||
simulated_paths, upper_percentile, axis=1
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def project_prices(
|
||||
df, days_forward=365, simulations=1000, confidence_levels=[0.95, 0.68]
|
||||
):
|
||||
"""
|
||||
Project future Bitcoin prices with simplified calibration.
|
||||
Modified projection function incorporating enhanced uncertainty estimation.
|
||||
"""
|
||||
df = df.copy()
|
||||
df["Log_Price"] = np.log(df["Close"])
|
||||
df["Log_Return"] = df["Log_Price"].diff()
|
||||
|
||||
# Get current cycle position
|
||||
# Get halving dates and current cycle position
|
||||
halving_dates = get_halving_dates()
|
||||
current_date = df["Date"].max()
|
||||
cycle_position = get_cycle_position(current_date, halving_dates)
|
||||
@@ -305,11 +397,13 @@ def project_prices(
|
||||
|
||||
era_params = era_adjustments[current_era]
|
||||
|
||||
# Get projection adjustments for scaling uncertainty over time
|
||||
projection_adjustments = get_projection_adjustments(days_forward, cycle_position)
|
||||
# Get projection adjustments with market awareness
|
||||
projection_adjustments = get_projection_adjustments(
|
||||
days_forward, cycle_position, df
|
||||
)
|
||||
|
||||
# Run Monte Carlo simulation
|
||||
np.random.seed(42)
|
||||
np.random.seed(42) # Restored for reproducibility
|
||||
simulated_paths = np.zeros((days_forward, simulations))
|
||||
|
||||
for sim in range(simulations):
|
||||
@@ -328,25 +422,15 @@ def project_prices(
|
||||
price_path = last_price * np.exp(cumulative_returns)
|
||||
simulated_paths[:, sim] = price_path
|
||||
|
||||
# Calculate results
|
||||
# Calculate results with dynamic confidence intervals
|
||||
results = pd.DataFrame(index=future_dates)
|
||||
results["Median"] = np.percentile(simulated_paths, 50, axis=1)
|
||||
results["Expected_Trend"] = last_price * np.exp(np.cumsum(drift))
|
||||
|
||||
# Calculate Expected_Trend using adjusted drift
|
||||
cumulative_drift = np.cumsum(drift)
|
||||
results["Expected_Trend"] = last_price * np.exp(cumulative_drift)
|
||||
|
||||
# Calculate confidence intervals
|
||||
for level in confidence_levels:
|
||||
lower_percentile = (1 - level) * 100 / 2
|
||||
upper_percentile = 100 - lower_percentile
|
||||
|
||||
results[f"Lower_{int(level*100)}"] = np.percentile(
|
||||
simulated_paths, lower_percentile, axis=1
|
||||
)
|
||||
results[f"Upper_{int(level*100)}"] = np.percentile(
|
||||
simulated_paths, upper_percentile, axis=1
|
||||
)
|
||||
# Calculate confidence intervals with dynamic adjustment
|
||||
ci_results = calculate_confidence_intervals(simulated_paths, confidence_levels)
|
||||
for key, values in ci_results.items():
|
||||
results[key] = values
|
||||
|
||||
return results
|
||||
|
||||
|
||||
Reference in New Issue
Block a user