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
|
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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user