Use S2F metrics for trend analysis.

This commit is contained in:
sam
2024-11-19 08:45:40 -08:00
parent f77cc955db
commit 7223144b13
+112 -71
View File
@@ -280,13 +280,30 @@ class MarketFundamentals:
return np.clip(adjustment, 0.65, 0.75) return np.clip(adjustment, 0.65, 0.75)
def calculate_confidence_adjustment(self, metrics, level): def calculate_confidence_adjustment(self, metrics, level):
"""Calculate how much to adjust confidence intervals based on market conditions.""" """Calculate confidence interval adjustments with more sensitive market metrics."""
depth_impact = np.clip(metrics["market_depth"] * 0.2, 0, 0.2) # More granular depth impact
vol_impact = np.clip(metrics["volume_to_supply"] * 30, 0, 0.2) if metrics["market_depth"] > 0.1:
total_adjustment = (depth_impact + vol_impact) * 0.5 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: if level >= 0.95:
total_adjustment *= 0.5 total_adjustment *= 0.4
return level + (1 - level) * total_adjustment return level + (1 - level) * total_adjustment
@@ -329,9 +346,10 @@ def compare_adjustments(df, fundamentals):
def analyze_trends(df): 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() df = df.copy()
fundamentals = MarketFundamentals()
# Get halving dates and calculate cycle position # Get halving dates and calculate cycle position
halving_dates = get_halving_dates() halving_dates = get_halving_dates()
@@ -340,41 +358,63 @@ def analyze_trends(df):
) )
df["Cycle_Days"] = (df["Cycle_Position"] * 4 * 365).round().astype(int) 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_Price"] = np.log(df["Close"])
df["Log_Return"] = df["Log_Price"].diff() 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() 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 window = 60
smoothed_returns = position_returns.rolling( smoothed_cycle_returns = position_returns.rolling(
window=window, window=window,
center=True, center=True,
min_periods=int(window / 2), min_periods=int(window / 2),
).mean() ).mean()
# Fill any NaN values at the edges smoothed_s2f_impact = s2f_impact.rolling(
smoothed_returns = smoothed_returns.fillna(method="bfill").fillna(method="ffill") window=window,
center=True,
min_periods=int(window / 2),
).mean()
# Apply asymmetric dampening to extreme values # Fill NaN values
returns_std = smoothed_returns.std() 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): # Combine cycle returns with S2F impact
if x > 2 * returns_std: s2f_weight = 0.3 # Adjustable parameter
return x * 0.6 # Stronger dampening for positive extremes combined_returns = (
elif x < -2 * returns_std: smoothed_cycle_returns * (1 - s2f_weight) + smoothed_s2f_impact * s2f_weight
return x * 0.7 # Slightly less dampening for negative extremes )
return x
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 def adaptive_dampen(x):
magnitude_factor = 0.9 # Global dampening factor if x > 2 * combined_returns.std():
smoothed_returns = smoothed_returns * magnitude_factor 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( 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): def get_projection_adjustments(days_forward, current_cycle_position, df):
""" """Enhanced projection adjustments using market fundamentals."""
Enhanced projection adjustments with dynamic uncertainty scaling.
"""
adjustments = np.ones(days_forward) adjustments = np.ones(days_forward)
fundamentals = MarketFundamentals()
# Get market condition metrics # Pre-calculate metrics
conditions = calculate_market_conditions(df) 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 current_metrics = fundamentals.get_market_maturity_metrics(df, df["Date"].max())
base_uncertainty = 0.016 # Standard rate 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): for i in range(days_forward):
# Conservative growth with cycle and condition awareness
time_factor = min( time_factor = min(
1 1
+ (i / 365) + (i / 365)
* base_uncertainty * base_uncertainty
* vol_factor * vol_factor
* trend_factor * market_depth_factor
* drawdown_factor, * s2f_factor,
1.20, 1.20,
) )
# 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: local_cycle_factor = 1.15 if cycle_position > 0.75 else 1.0
local_cycle_factor = 1.15
else:
local_cycle_factor = 1.0
# Apply all factors including the initial cycle factor adjustments[i] = time_factor * local_cycle_factor * regime_scale
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) adjustments[i] = max(adjustments[i], 1.02 + (i / 365) * 0.01)
return adjustments return adjustments
@@ -765,21 +793,34 @@ def project_prices(
# Calculate confidence intervals # Calculate confidence intervals
for level in confidence_levels: for level in confidence_levels:
# Get market metrics for confidence interval adjustment
metrics = fundamentals.get_market_maturity_metrics(df, current_date) metrics = fundamentals.get_market_maturity_metrics(df, current_date)
# Calculate adjusted confidence level # Calculate intervals by projection horizon
effective_level = fundamentals.calculate_confidence_adjustment(metrics, level) lower_bounds = []
upper_bounds = []
lower_percentile = (1 - effective_level) * 100 / 2 for day in range(days_forward):
upper_percentile = 100 - lower_percentile time_factor = (
0.85 if day > 365 else 0.9 if day > 180 else 0.95 if day > 90 else 1.0
)
results[f"Lower_{int(level*100)}"] = np.percentile( effective_level = (
simulated_paths, lower_percentile, axis=1 fundamentals.calculate_confidence_adjustment(metrics, level)
) * time_factor
results[f"Upper_{int(level*100)}"] = np.percentile( )
simulated_paths, upper_percentile, axis=1
) lower_percentile = (1 - effective_level) * 100 / 2
upper_percentile = 100 - lower_percentile
lower_bounds.append(
np.percentile(simulated_paths[day, :], lower_percentile)
)
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 return results