Use S2F metrics for trend analysis.
This commit is contained in:
@@ -280,13 +280,30 @@ class MarketFundamentals:
|
||||
return np.clip(adjustment, 0.65, 0.75)
|
||||
|
||||
def calculate_confidence_adjustment(self, metrics, level):
|
||||
"""Calculate how much to adjust confidence intervals based on market conditions."""
|
||||
depth_impact = np.clip(metrics["market_depth"] * 0.2, 0, 0.2)
|
||||
vol_impact = np.clip(metrics["volume_to_supply"] * 30, 0, 0.2)
|
||||
total_adjustment = (depth_impact + vol_impact) * 0.5
|
||||
"""Calculate confidence interval adjustments with more sensitive market metrics."""
|
||||
# More granular depth impact
|
||||
if metrics["market_depth"] > 0.1:
|
||||
depth_factor = 0.5
|
||||
elif metrics["market_depth"] > 0.05:
|
||||
depth_factor = 0.7
|
||||
else:
|
||||
depth_factor = 1.0
|
||||
|
||||
# More granular volume impact
|
||||
if metrics["volume_to_supply"] > 0.015:
|
||||
volume_factor = 0.5
|
||||
elif metrics["volume_to_supply"] > 0.008:
|
||||
volume_factor = 0.7
|
||||
else:
|
||||
volume_factor = 1.0
|
||||
|
||||
depth_impact = np.clip(metrics["market_depth"] * 0.15 * depth_factor, 0, 0.15)
|
||||
vol_impact = np.clip(metrics["volume_to_supply"] * 20 * volume_factor, 0, 0.15)
|
||||
|
||||
total_adjustment = (depth_impact + vol_impact) * 0.4
|
||||
|
||||
if level >= 0.95:
|
||||
total_adjustment *= 0.5
|
||||
total_adjustment *= 0.4
|
||||
|
||||
return level + (1 - level) * total_adjustment
|
||||
|
||||
@@ -329,9 +346,10 @@ def compare_adjustments(df, fundamentals):
|
||||
|
||||
def analyze_trends(df):
|
||||
"""
|
||||
Analyze Bitcoin price trends using log returns with asymmetric dampening.
|
||||
Analyze Bitcoin price trends using log returns with S2F awareness.
|
||||
"""
|
||||
df = df.copy()
|
||||
fundamentals = MarketFundamentals()
|
||||
|
||||
# Get halving dates and calculate cycle position
|
||||
halving_dates = get_halving_dates()
|
||||
@@ -340,41 +358,63 @@ def analyze_trends(df):
|
||||
)
|
||||
df["Cycle_Days"] = (df["Cycle_Position"] * 4 * 365).round().astype(int)
|
||||
|
||||
# Calculate log returns
|
||||
# Calculate S2F metrics for each date
|
||||
supply_metrics = [
|
||||
fundamentals.calculate_supply_metrics(date) for date in df["Date"]
|
||||
]
|
||||
df["S2F_Ratio"] = [m["stock_to_flow"] for m in supply_metrics]
|
||||
df["S2F_Change"] = df["S2F_Ratio"].pct_change()
|
||||
|
||||
# Calculate log returns and basic cycle returns
|
||||
df["Log_Price"] = np.log(df["Close"])
|
||||
df["Log_Return"] = df["Log_Price"].diff()
|
||||
|
||||
# Group by position in cycle
|
||||
# Calculate cycle-based returns
|
||||
position_returns = df.groupby("Cycle_Days")["Log_Return"].mean()
|
||||
|
||||
# Simple moving average smoothing
|
||||
# Calculate S2F impact on returns
|
||||
s2f_impact = df.groupby("Cycle_Days")["S2F_Change"].mean()
|
||||
|
||||
# Smooth both components
|
||||
window = 60
|
||||
smoothed_returns = position_returns.rolling(
|
||||
smoothed_cycle_returns = position_returns.rolling(
|
||||
window=window,
|
||||
center=True,
|
||||
min_periods=int(window / 2),
|
||||
).mean()
|
||||
|
||||
# Fill any NaN values at the edges
|
||||
smoothed_returns = smoothed_returns.fillna(method="bfill").fillna(method="ffill")
|
||||
smoothed_s2f_impact = s2f_impact.rolling(
|
||||
window=window,
|
||||
center=True,
|
||||
min_periods=int(window / 2),
|
||||
).mean()
|
||||
|
||||
# Apply asymmetric dampening to extreme values
|
||||
returns_std = smoothed_returns.std()
|
||||
# Fill NaN values
|
||||
smoothed_cycle_returns = smoothed_cycle_returns.fillna(method="bfill").fillna(
|
||||
method="ffill"
|
||||
)
|
||||
smoothed_s2f_impact = smoothed_s2f_impact.fillna(method="bfill").fillna(
|
||||
method="ffill"
|
||||
)
|
||||
|
||||
def asymmetric_dampen(x):
|
||||
if x > 2 * returns_std:
|
||||
return x * 0.6 # Stronger dampening for positive extremes
|
||||
elif x < -2 * returns_std:
|
||||
return x * 0.7 # Slightly less dampening for negative extremes
|
||||
return x
|
||||
# Combine cycle returns with S2F impact
|
||||
s2f_weight = 0.3 # Adjustable parameter
|
||||
combined_returns = (
|
||||
smoothed_cycle_returns * (1 - s2f_weight) + smoothed_s2f_impact * s2f_weight
|
||||
)
|
||||
|
||||
smoothed_returns = smoothed_returns.map(asymmetric_dampen)
|
||||
# Apply dampening using current market metrics
|
||||
latest_metrics = fundamentals.get_market_maturity_metrics(df, df["Date"].max())
|
||||
market_adjustment = fundamentals.calculate_volatility_adjustment(latest_metrics)
|
||||
|
||||
# Additional dampening based on absolute magnitude
|
||||
magnitude_factor = 0.9 # Global dampening factor
|
||||
smoothed_returns = smoothed_returns * magnitude_factor
|
||||
def adaptive_dampen(x):
|
||||
if x > 2 * combined_returns.std():
|
||||
return x * (0.6 * market_adjustment)
|
||||
elif x < -2 * combined_returns.std():
|
||||
return x * (0.7 * market_adjustment)
|
||||
return x * market_adjustment
|
||||
|
||||
return smoothed_returns
|
||||
return combined_returns.map(adaptive_dampen)
|
||||
|
||||
|
||||
def calculate_adaptive_volatility(
|
||||
@@ -615,56 +655,44 @@ def calculate_market_conditions(df, lookback_window=180):
|
||||
|
||||
|
||||
def get_projection_adjustments(days_forward, current_cycle_position, df):
|
||||
"""
|
||||
Enhanced projection adjustments with dynamic uncertainty scaling.
|
||||
"""
|
||||
"""Enhanced projection adjustments using market fundamentals."""
|
||||
adjustments = np.ones(days_forward)
|
||||
fundamentals = MarketFundamentals()
|
||||
|
||||
# Get market condition metrics
|
||||
conditions = calculate_market_conditions(df)
|
||||
# Pre-calculate metrics
|
||||
lookback = min(365, len(df))
|
||||
historical_metrics = [
|
||||
fundamentals.get_market_maturity_metrics(df, date)
|
||||
for date in df["Date"].tail(lookback)
|
||||
]
|
||||
historical_avg_volume_to_supply = np.mean(
|
||||
[m["volume_to_supply"] for m in historical_metrics]
|
||||
)
|
||||
|
||||
# Base uncertainty varies with market conditions
|
||||
base_uncertainty = 0.016 # Standard rate
|
||||
current_metrics = fundamentals.get_market_maturity_metrics(df, df["Date"].max())
|
||||
base_uncertainty = 0.016 * (1 + current_metrics["supply_growth_rate"] * 365)
|
||||
vol_factor = 1 + 0.2 * abs(1 - current_metrics["volume_to_supply"] * 50)
|
||||
market_depth_factor = 1 / np.sqrt(1 + current_metrics["market_depth"] * 5)
|
||||
s2f_factor = 1 / np.log1p(current_metrics["stock_to_flow"])
|
||||
regime_scale = np.clip(
|
||||
current_metrics["volume_to_supply"] / historical_avg_volume_to_supply, 0.88, 1.2
|
||||
)
|
||||
|
||||
# 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:
|
||||
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 cycle and condition awareness
|
||||
time_factor = min(
|
||||
1
|
||||
+ (i / 365)
|
||||
* base_uncertainty
|
||||
* vol_factor
|
||||
* trend_factor
|
||||
* drawdown_factor,
|
||||
* market_depth_factor
|
||||
* s2f_factor,
|
||||
1.20,
|
||||
)
|
||||
|
||||
# Update cycle position for this future point
|
||||
cycle_position = (current_cycle_position + i / 1460) % 1
|
||||
if cycle_position > 0.75:
|
||||
local_cycle_factor = 1.15
|
||||
else:
|
||||
local_cycle_factor = 1.0
|
||||
local_cycle_factor = 1.15 if cycle_position > 0.75 else 1.0
|
||||
|
||||
# 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] = time_factor * local_cycle_factor * regime_scale
|
||||
adjustments[i] = max(adjustments[i], 1.02 + (i / 365) * 0.01)
|
||||
|
||||
return adjustments
|
||||
@@ -765,22 +793,35 @@ def project_prices(
|
||||
|
||||
# Calculate confidence intervals
|
||||
for level in confidence_levels:
|
||||
# Get market metrics for confidence interval adjustment
|
||||
metrics = fundamentals.get_market_maturity_metrics(df, current_date)
|
||||
|
||||
# Calculate adjusted confidence level
|
||||
effective_level = fundamentals.calculate_confidence_adjustment(metrics, level)
|
||||
# Calculate intervals by projection horizon
|
||||
lower_bounds = []
|
||||
upper_bounds = []
|
||||
|
||||
for day in range(days_forward):
|
||||
time_factor = (
|
||||
0.85 if day > 365 else 0.9 if day > 180 else 0.95 if day > 90 else 1.0
|
||||
)
|
||||
|
||||
effective_level = (
|
||||
fundamentals.calculate_confidence_adjustment(metrics, level)
|
||||
* time_factor
|
||||
)
|
||||
|
||||
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
|
||||
lower_bounds.append(
|
||||
np.percentile(simulated_paths[day, :], lower_percentile)
|
||||
)
|
||||
results[f"Upper_{int(level*100)}"] = np.percentile(
|
||||
simulated_paths, upper_percentile, axis=1
|
||||
upper_bounds.append(
|
||||
np.percentile(simulated_paths[day, :], upper_percentile)
|
||||
)
|
||||
|
||||
results[f"Lower_{int(level*100)}"] = lower_bounds
|
||||
results[f"Upper_{int(level*100)}"] = upper_bounds
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user