Improve uncertainty estimation.

This commit is contained in:
sam
2024-11-16 20:23:59 -08:00
parent 453df222d6
commit f023f7e999
+115 -31
View File
@@ -222,44 +222,136 @@ def adjust_trend_expectations(expected_returns, cycle_position):
return expected_returns * damping_factor 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) 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 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: 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): for i in range(days_forward):
# Conservative growth with fixed cap # Conservative growth with cycle and condition awareness
time_factor = min(1 + (i / 365) * base_uncertainty, 1.055) # Lower cap 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 cycle_position = (current_cycle_position + i / 1460) % 1
if cycle_position > 0.75: if cycle_position > 0.75:
cycle_factor = 0.94 local_cycle_factor = 1.15
else: 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 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( 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 with simplified calibration. Modified projection function incorporating enhanced uncertainty estimation.
""" """
df = df.copy() df = df.copy()
df["Log_Price"] = np.log(df["Close"]) df["Log_Price"] = np.log(df["Close"])
df["Log_Return"] = df["Log_Price"].diff() df["Log_Return"] = df["Log_Price"].diff()
# Get current cycle position # Get halving dates and current cycle position
halving_dates = get_halving_dates() halving_dates = get_halving_dates()
current_date = df["Date"].max() current_date = df["Date"].max()
cycle_position = get_cycle_position(current_date, halving_dates) cycle_position = get_cycle_position(current_date, halving_dates)
@@ -305,11 +397,13 @@ def project_prices(
era_params = era_adjustments[current_era] era_params = era_adjustments[current_era]
# Get projection adjustments for scaling uncertainty over time # Get projection adjustments with market awareness
projection_adjustments = get_projection_adjustments(days_forward, cycle_position) projection_adjustments = get_projection_adjustments(
days_forward, cycle_position, df
)
# Run Monte Carlo simulation # Run Monte Carlo simulation
np.random.seed(42) np.random.seed(42) # Restored for reproducibility
simulated_paths = np.zeros((days_forward, simulations)) simulated_paths = np.zeros((days_forward, simulations))
for sim in range(simulations): for sim in range(simulations):
@@ -328,25 +422,15 @@ def project_prices(
price_path = last_price * np.exp(cumulative_returns) price_path = last_price * np.exp(cumulative_returns)
simulated_paths[:, sim] = price_path simulated_paths[:, sim] = price_path
# Calculate results # Calculate results with dynamic confidence intervals
results = pd.DataFrame(index=future_dates) results = pd.DataFrame(index=future_dates)
results["Median"] = np.percentile(simulated_paths, 50, axis=1) 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 # Calculate confidence intervals with dynamic adjustment
cumulative_drift = np.cumsum(drift) ci_results = calculate_confidence_intervals(simulated_paths, confidence_levels)
results["Expected_Trend"] = last_price * np.exp(cumulative_drift) for key, values in ci_results.items():
results[key] = values
# 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
)
return results return results