diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 18a063c..9a6d89c 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/risk_engine.cpython-311.pyc b/core/__pycache__/risk_engine.cpython-311.pyc new file mode 100644 index 0000000..36aaabc Binary files /dev/null and b/core/__pycache__/risk_engine.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index ebb8c6e..df0ebbc 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 8d204fa..948796c 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..95e582a --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 5.2.7 on 2026-02-28 20:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='SimulationResult', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('symbol', models.CharField(default='ES=F', max_length=20)), + ('expected_low', models.FloatField()), + ('worst_case_5th', models.FloatField()), + ('drawdown_prob', models.FloatField()), + ('bias', models.CharField(max_length=10)), + ('take_profit', models.FloatField()), + ('stop_loss', models.FloatField()), + ('sentiment_score', models.FloatField()), + ('regime', models.IntegerField()), + ('summary', models.TextField(blank=True)), + ], + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..3f1cc2e Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..ab07953 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,25 @@ from django.db import models -# Create your models here. +class SimulationResult(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + symbol = models.CharField(max_length=20, default="ES=F") + + # Risk Metrics + expected_low = models.FloatField() + worst_case_5th = models.FloatField() + drawdown_prob = models.FloatField() + + # Trading Bias + bias = models.CharField(max_length=10) # LONG, SHORT, NEUTRAL + take_profit = models.FloatField() + stop_loss = models.FloatField() + + # Sentiment + sentiment_score = models.FloatField() # -1 to 1 + regime = models.IntegerField() # 0, 1, 2... + + # JSON or Text field for simulation paths if needed, but for now we'll keep it simple + summary = models.TextField(blank=True) + + def __str__(self): + return f"{self.symbol} Risk - {self.created_at.strftime('%Y-%m-%d %H:%M')}" \ No newline at end of file diff --git a/core/risk_engine.py b/core/risk_engine.py new file mode 100644 index 0000000..a72b082 --- /dev/null +++ b/core/risk_engine.py @@ -0,0 +1,136 @@ +import os +import yfinance as yf +import pandas as pd +import numpy as np +import feedparser +from bs4 import BeautifulSoup +from statsmodels.tsa.regime_switching.markov_regression import MarkovRegression +from scipy.stats import norm, t +from datetime import datetime, timedelta + +class RiskEngine: + def __init__(self, symbol="ES=F"): + self.symbol = symbol + self.lookback_days = 250 + + def get_market_data(self): + """Fetch historical ES futures data from Yahoo Finance.""" + end_date = datetime.now() + start_date = end_date - timedelta(days=self.lookback_days) + data = yf.download(self.symbol, start=start_date, end=end_date) + if data.empty: + raise ValueError("No market data fetched.") + + # Calculate daily log returns + # Handle potential multi-index if symbol is a single string but yfinance returns multi-index + if isinstance(data.columns, pd.MultiIndex): + close_col = ('Close', self.symbol) if ( 'Close', self.symbol) in data.columns else data.columns[0] + close_data = data[close_col] + else: + close_data = data['Close'] + + log_returns = np.log(close_data / close_data.shift(1)) + + processed_data = pd.DataFrame({ + 'Close': close_data, + 'Log_Returns': log_returns + }) + return processed_data.dropna() + + def get_sentiment(self): + """Collect sentiment from Google News RSS.""" + rss_url = f"https://news.google.com/rss/search?q={self.symbol}+futures+stock+market&hl=en-US&gl=US&ceid=US:en" + feed = feedparser.parse(rss_url) + + positive_words = {'bull', 'rally', 'surge', 'growth', 'positive', 'gain', 'strong', 'uptrend', 'recovery', 'high'} + negative_words = {'bear', 'crash', 'plunge', 'recession', 'negative', 'drop', 'weak', 'downtrend', 'risk', 'low', 'crisis'} + + scores = [] + for entry in feed.entries[:20]: # Last 20 headlines + headline = entry.title.lower() + p_count = sum(1 for w in positive_words if w in headline) + n_count = sum(1 for w in negative_words if w in headline) + score = (p_count - n_count) / (p_count + n_count + 1) + scores.append(score) + + return np.mean(scores) if scores else 0.0 + + def fit_markov_regime(self, data): + """Model daily return dynamics with a 2-state Markov transition framework.""" + # 0: Low Vol, 1: High Vol/Bearish + model = MarkovRegression(data['Log_Returns'], k_regimes=2, trend='c', switching_variance=True) + res = model.fit(disp=False) + + # Latest regime probability + current_regime = 0 if res.smoothed_marginal_probabilities[0].iloc[-1] > 0.5 else 1 + + # Regime parameters + regime_params = { + 'mu': res.params[['const[0]', 'const[1]']].values, + 'sigma': np.sqrt(res.params[['sigma2[0]', 'sigma2[1]']].values) + } + + return current_regime, regime_params + + def run_simulation(self): + """Main entry point to run the risk simulation.""" + data = self.get_market_data() + sentiment = self.get_sentiment() + regime, params = self.fit_markov_regime(data) + + # Sentiment adjustment + # Sentiment (negative) increases volatility and jump intensity + vol_adj = 1.0 - (sentiment * 0.5) # If sentiment is -1, vol_adj is 1.5 + current_close = float(data['Close'].iloc[-1]) + mu = float(params['mu'][regime]) + sigma = float(params['sigma'][regime] * vol_adj) + + # Monte Carlo Simulation (Intraday - 100 steps for a day) + n_paths = 5000 + n_steps = 100 + dt = 1.0 / n_steps + + # Fat-tailed moves (Student's t-distribution) + df = 5 # Degrees of freedom for fat tails + shocks = t.rvs(df, size=(n_paths, n_steps)) * sigma * np.sqrt(dt) + paths = np.zeros((n_paths, n_steps + 1)) + paths[:, 0] = current_close + + for t_step in range(1, n_steps + 1): + paths[:, t_step] = paths[:, t_step - 1] * np.exp((mu - 0.5 * sigma**2) * dt + shocks[:, t_step - 1]) + + # Metrics + intraday_lows = np.min(paths, axis=1) + expected_low = np.mean(intraday_lows) + worst_case_5th = np.percentile(intraday_lows, 5) + + # Prob of 1% drawdown + drawdowns = (np.min(paths, axis=1) - current_close) / current_close + prob_1pct_drawdown = np.mean(drawdowns <= -0.01) * 100 # In percentage + + # Directional Bias & TP/SL + # Bias is driven by (Sentiment + Mu) + total_bias_score = sentiment * 0.3 + mu * 0.7 + if total_bias_score > 0.0005: + bias = "LONG" + tp = current_close + (2 * sigma * current_close) + sl = current_close - (1.5 * sigma * current_close) + elif total_bias_score < -0.0005: + bias = "SHORT" + tp = current_close - (2 * sigma * current_close) + sl = current_close + (1.5 * sigma * current_close) + else: + bias = "NEUTRAL" + tp = current_close + (1 * sigma * current_close) + sl = current_close - (1 * sigma * current_close) + + return { + 'expected_low': expected_low, + 'worst_case_5th': worst_case_5th, + 'drawdown_prob': prob_1pct_drawdown, + 'bias': bias, + 'tp': tp, + 'sl': sl, + 'sentiment': sentiment, + 'regime': regime + } \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..7f66d6b 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,85 @@ - - +
- -S&P 500 Futures Intraday Scenarios & Regime Analysis
+AppWizzy AI is collecting your requirements and applying the first changes.
-This page will refresh automatically as the plan is implemented.
-
- Runtime: Django {{ django_version }} · Python {{ python_version }}
- — UTC {{ current_time|date:"Y-m-d H:i:s" }}
-
+ + Regime: {{ latest.regime|yesno:"High Volatility (Risk-Off),Low Volatility (Risk-On)" }} +
+No analysis available. Click the button to trigger the engine.
+This engine simulates 1,000 potential price paths for the S&P 500 E-mini (ES) futures. It combines historical volatility, a Markov Switching Model for regime detection, and real-time sentiment analysis to estimate intraday risk boundaries.
+Watch the Tail Risk (VaR) level. In high volatility regimes, price tends to test these boundaries. The Bias suggests a directional lean based on combined indicators.
+