Initial commit: FinanceIQ v5.2 — full-stack financial analytics platform

This commit is contained in:
vanshshah10002-prog 2026-02-25 05:00:18 +05:30
commit bac9eff6f6
20 changed files with 8496 additions and 0 deletions

21
.env.example Normal file
View File

@ -0,0 +1,21 @@
# FinanceIQ v5 — Environment Variables
# Copy this to .env and fill in your API keys
# Financial Modeling Prep (required for historical data & ratios)
FMP_API_KEY=your_fmp_api_key_here
# Finnhub (required for analyst ratings, insider trading)
FINNHUB_API_KEY=your_finnhub_api_key_here
# FRED (required for macroeconomic indicators)
FRED_API_KEY=your_fred_api_key_here
# Alpha Vantage (required for sector heatmap, currency conversion)
ALPHA_VANTAGE_KEY=your_alpha_vantage_key_here
# Ollama (optional — local AI model, defaults to http://localhost:11434)
OLLAMA_URL=http://localhost:11434
# Zerodha Kite Connect (optional — for portfolio management)
# KITE_API_KEY=your_kite_api_key_here
# KITE_API_SECRET=your_kite_api_secret_here

44
.gitignore vendored Normal file
View File

@ -0,0 +1,44 @@
# Python
__pycache__/
*.py[cod]
*.pyo
*.egg-info/
dist/
build/
*.egg
# Virtual Environment
venv/
.venv/
env/
# IDE
.vscode/
.idea/
*.swp
*.swo
.gemini/
# Environment Variables & Secrets
.env
# OS
.DS_Store
Thumbs.db
desktop.ini
# Logs
*.log
# Database (local state)
portfolio.db
portfolio.db-wal
portfolio.db-shm
# Data exports
*.xlsx
*.csv
!sample_data/
# Assignment PDFs
*.pdf

74
01_basic_stock_data.py Normal file
View File

@ -0,0 +1,74 @@
"""
=== FinanceToolkit - Lesson 1: Getting Basic Stock Data ===
This script shows you how to:
1. Initialize the Toolkit with one or more stock tickers
2. Fetch historical price data (OHLC, volume, returns)
3. Fetch financial statements (income, balance sheet, cash flow)
Run this file: python 01_basic_stock_data.py
"""
from financetoolkit import Toolkit
# ── 1. Setup ──────────────────────────────────────────────────────────
API_KEY = "wybWEsp1oB9abHfz3yPpQYwffxaN21B7"
# You can pass one ticker or a list of tickers.
# start_date limits how far back data goes.
companies = Toolkit(
tickers=["AAPL", "MSFT"],
api_key=API_KEY,
start_date="2020-01-01", # data from Jan 2020 onwards
)
# ── 2. Historical Price Data ─────────────────────────────────────────
print("=" * 60)
print("HISTORICAL PRICE DATA (first 10 rows)")
print("=" * 60)
historical = companies.get_historical_data()
print(historical.head(10))
print()
# Filter to just one stock using .xs()
print("--- Apple only ---")
apple_hist = historical.xs("AAPL", level=1, axis=1)
print(apple_hist.head(10))
print()
# ── 3. Income Statement ─────────────────────────────────────────────
print("=" * 60)
print("INCOME STATEMENT (Annual)")
print("=" * 60)
income = companies.get_income_statement()
print(income.head(10))
print()
# Filter to just Apple
print("--- Apple Income Statement ---")
apple_income = income.loc["AAPL"]
print(apple_income.head(10))
print()
# ── 4. Balance Sheet ─────────────────────────────────────────────────
print("=" * 60)
print("BALANCE SHEET STATEMENT (Annual)")
print("=" * 60)
balance = companies.get_balance_sheet_statement()
print(balance.head(10))
print()
# ── 5. Cash Flow Statement ───────────────────────────────────────────
print("=" * 60)
print("CASH FLOW STATEMENT (Annual)")
print("=" * 60)
cashflow = companies.get_cash_flow_statement()
print(cashflow.head(10))
print()
print("[DONE] You've just pulled real stock data for AAPL & MSFT.")
print(" Try changing the tickers list or start_date above and re-run!")

77
02_ratios_and_models.py Normal file
View File

@ -0,0 +1,77 @@
"""
=== FinanceToolkit - Lesson 2: Financial Ratios & Models ===
This script shows you how to:
1. Calculate profitability, liquidity, and valuation ratios
2. Run financial models (DuPont Analysis, WACC, etc.)
Run this file: python 02_ratios_and_models.py
"""
from financetoolkit import Toolkit
API_KEY = "wybWEsp1oB9abHfz3yPpQYwffxaN21B7"
companies = Toolkit(
tickers=["AAPL", "MSFT", "GOOGL"],
api_key=API_KEY,
start_date="2020-01-01",
)
# ── 1. Profitability Ratios ──────────────────────────────────────────
print("=" * 60)
print("PROFITABILITY RATIOS")
print("=" * 60)
profitability = companies.ratios.collect_profitability_ratios()
print(profitability)
print()
# ── 2. Liquidity Ratios ──────────────────────────────────────────────
print("=" * 60)
print("LIQUIDITY RATIOS")
print("=" * 60)
liquidity = companies.ratios.collect_liquidity_ratios()
print(liquidity)
print()
# ── 3. Valuation Ratios ──────────────────────────────────────────────
print("=" * 60)
print("VALUATION RATIOS (e.g. P/E, P/B, EV/EBITDA)")
print("=" * 60)
valuation = companies.ratios.collect_valuation_ratios()
print(valuation)
print()
# ── 4. Individual Ratio ──────────────────────────────────────────────
# Use get_ functions for a single ratio
print("=" * 60)
print("RETURN ON EQUITY (single ratio)")
print("=" * 60)
roe = companies.ratios.get_return_on_equity()
print(roe)
print()
# ── 5. DuPont Analysis ───────────────────────────────────────────────
print("=" * 60)
print("EXTENDED DUPONT ANALYSIS")
print("=" * 60)
dupont = companies.models.get_extended_dupont_analysis()
print(dupont)
print()
# ── 6. Weighted Average Cost of Capital (WACC) ───────────────────────
print("=" * 60)
print("WEIGHTED AVERAGE COST OF CAPITAL (WACC)")
print("=" * 60)
wacc = companies.models.get_weighted_average_cost_of_capital()
print(wacc)
print()
print("[DONE] You now know how to compute 50+ financial ratios")
print(" and run valuation models on any public company.")

View File

@ -0,0 +1,70 @@
"""
=== FinanceToolkit - Lesson 3: Technical Indicators, Risk & Performance ===
This script shows you how to:
1. Get technical indicators (Bollinger Bands, MACD, RSI, Ichimoku)
2. Measure risk (Value at Risk)
3. Evaluate performance (Fama-French factor correlations)
Run this file: python 03_technicals_risk_performance.py
"""
from financetoolkit import Toolkit
API_KEY = "wybWEsp1oB9abHfz3yPpQYwffxaN21B7"
companies = Toolkit(
tickers=["AAPL", "MSFT"],
api_key=API_KEY,
start_date="2020-01-01",
)
# ── 1. Bollinger Bands ───────────────────────────────────────────────
print("=" * 60)
print("BOLLINGER BANDS")
print("=" * 60)
bollinger = companies.technicals.get_bollinger_bands()
print(bollinger.head(15))
print()
# ── 2. MACD ───────────────────────────────────────────────────────────
print("=" * 60)
print("MACD (Moving Average Convergence Divergence)")
print("=" * 60)
macd = companies.technicals.get_moving_average_convergence_divergence()
print(macd.head(15))
print()
# ── 3. RSI ────────────────────────────────────────────────────────────
print("=" * 60)
print("RSI (Relative Strength Index)")
print("=" * 60)
rsi = companies.technicals.get_relative_strength_index()
print(rsi.head(15))
print()
# ── 4. Value at Risk ─────────────────────────────────────────────────
print("=" * 60)
print("VALUE AT RISK (Weekly)")
print("=" * 60)
var = companies.risk.get_value_at_risk(period="weekly")
print(var.head(15))
print()
# ── 5. Factor Correlations ───────────────────────────────────────────
print("=" * 60)
print("FAMA-FRENCH FACTOR CORRELATIONS (Quarterly)")
print("=" * 60)
correlations = companies.performance.get_factor_asset_correlations(
period="quarterly"
)
print(correlations)
print()
print("[DONE] You can now analyze stocks with technical indicators,")
print(" assess risk with VaR, and see performance factor exposures.")

View File

@ -0,0 +1,69 @@
"""
=== FinanceToolkit - Lesson 4: Economics Data & Exporting Results ===
This script shows you how to:
1. Access macroeconomic data (GDP, CPI, unemployment)
2. Export any DataFrame to CSV or Excel for further use
Run this file: python 04_economics_and_export.py
"""
from financetoolkit import Toolkit
API_KEY = "wybWEsp1oB9abHfz3yPpQYwffxaN21B7"
companies = Toolkit(
tickers=["AAPL"],
api_key=API_KEY,
start_date="2020-01-01",
)
# ── 1. Unemployment Rates ─────────────────────────────────────────────
print("=" * 60)
print("UNEMPLOYMENT RATES")
print("=" * 60)
unemployment = companies.economics.get_unemployment_rate()
print(unemployment.head(10))
print()
# ── 2. Consumer Price Index (CPI) ────────────────────────────────────
print("=" * 60)
print("CONSUMER PRICE INDEX (CPI)")
print("=" * 60)
cpi = companies.economics.get_consumer_price_index()
print(cpi.head(10))
print()
# ── 3. GDP ────────────────────────────────────────────────────────────
print("=" * 60)
print("GROSS DOMESTIC PRODUCT (GDP)")
print("=" * 60)
gdp = companies.economics.get_gross_domestic_product()
print(gdp.head(10))
print()
# ── 4. Exporting Data ────────────────────────────────────────────────
# Any DataFrame can be saved to CSV or Excel for use in reports,
# other tools, or further analysis.
# Export historical data to CSV
historical = companies.get_historical_data()
historical.to_csv("aapl_historical_data.csv")
print("[SAVED] aapl_historical_data.csv")
# Export income statement to Excel
income = companies.get_income_statement()
income.to_excel("aapl_income_statement.xlsx")
print("[SAVED] aapl_income_statement.xlsx")
# Export ratios to CSV
ratios = companies.ratios.collect_profitability_ratios()
ratios.to_csv("aapl_profitability_ratios.csv")
print("[SAVED] aapl_profitability_ratios.csv")
print()
print("[DONE] Your data has been exported. Open the CSV/Excel files")
print(" in Excel, Google Sheets, or any data analysis tool.")

150
README.md Normal file
View File

@ -0,0 +1,150 @@
<p align="center">
<h1 align="center">📊 FinanceIQ</h1>
<p align="center">
<strong>Real-time Financial Analytics & Portfolio Simulation Platform</strong>
</p>
<p align="center">
<img src="https://img.shields.io/badge/python-3.10+-blue?style=flat-square&logo=python" alt="Python">
<img src="https://img.shields.io/badge/flask-3.x-green?style=flat-square&logo=flask" alt="Flask">
<img src="https://img.shields.io/badge/version-5.2-purple?style=flat-square" alt="Version">
<img src="https://img.shields.io/badge/license-MIT-yellow?style=flat-square" alt="License">
</p>
</p>
---
A full-stack financial analytics web application built with **Flask**, **yfinance**, and **Google Gemini AI**. Features interactive charting, professional-grade portfolio simulation, options pricing, backtesting, AI-driven advisory, and a live market dashboard — all in a sleek, responsive single-page dashboard.
## ✨ Features
### 📈 Market Analysis
- **Interactive Candlestick Charts** — powered by TradingView Lightweight Charts
- **Technical Indicators** — RSI, MACD, Bollinger Bands, SMA/EMA, ATR, Stochastic
- **Candlestick Pattern Recognition** — Doji, Hammer, Engulfing, Morning Star, and more
- **Key Financial Metrics** — P/E, ROE, D/E, Quick Ratio, Dividend Yield
- **Sector Peer Comparison** — heatmap of competitors across 7 sectors
- **Valuation Models** — DCF, Graham Number, PEG Ratio, DDM
### 💼 Portfolio Simulation
- **$100K Paper Trading** — buy/sell with real-time prices from Yahoo Finance
- **Advanced Order Types** — Market, Limit, and Stop orders
- **Short Selling** — open and cover short positions with margin tracking
- **Slippage & Commission Modeling** — realistic IBKR-style execution costs
- **Equity Curve** — track portfolio value over time with interactive charts
- **Performance Analytics** — Sharpe, Sortino, Calmar ratios, profit factor, max drawdown
### 🎯 Options Pricing
- **Black-Scholes Model** — call/put pricing with real-time IV
- **Greeks Dashboard** — Delta, Gamma, Theta, Vega, Rho
- **Monte Carlo Simulation** — 10,000-path price projections
- **Interactive Payoff Charts** — visualize P&L at expiration
### 🤖 AI-Powered Advisory
- **Gemini AI Integration** — personalized investment analysis
- **Macro Dashboard** — Fed rates, Treasury yields, VIX, DXY, oil, gold
- **Risk Assessment** — automated risk scoring with traffic-light indicators
- **Natural Language Insights** — ask questions about any stock
### 📰 News & Sentiment
- **Real-time News Feed** — latest headlines for any ticker
- **AI Sentiment Analysis** — bullish/bearish/neutral classification
- **Currency Converter** — 150+ currency pairs with live rates
### 🔬 Backtesting Engine
- **Strategy Backtester** — SMA crossover, RSI, MACD, Bollinger Band strategies
- **Bi-directional Trading** — long and short strategy support
- **Performance Metrics** — Sharpe ratio, win rate, max drawdown, trade log
### 🌍 Market Dashboard
- **Global Indices** — S&P 500, NASDAQ, Dow, FTSE, Nikkei, DAX
- **Commodities** — Gold, Silver, Crude Oil, Natural Gas
- **Currency Pairs** — EUR/USD, GBP/USD, USD/JPY, and more
- **Live Pulse** — auto-refreshing with animated counters
## 🚀 Quick Start
### Prerequisites
- Python 3.10+
- Google Gemini API key ([Get one here](https://makersuite.google.com/app/apikey))
### Installation
```bash
# Clone the repo
git clone https://github.com/YOUR_USERNAME/FinanceIQ.git
cd FinanceIQ
# Install dependencies
pip install -r requirements.txt
# Set up environment variables
cp .env.example .env
# Edit .env and add your GEMINI_API_KEY
# Run the application
python app.py
```
Then open `http://localhost:5000` in your browser.
## 📁 Project Structure
```
FinanceIQ/
├── app.py # Flask server & API routes
├── portfolio_engine.py # Portfolio simulation engine (SQLite-backed)
├── options_engine.py # Black-Scholes, Greeks, Monte Carlo
├── backtester.py # Strategy backtesting engine
├── advisor.py # Gemini AI financial advisory
├── technical_analyzer.py # Technical indicators & patterns
├── news_analyzer.py # News fetching & sentiment analysis
├── requirements.txt # Python dependencies
├── .env.example # Environment variable template
├── templates/
│ └── index.html # Single-page dashboard UI
└── static/
├── script.js # Frontend logic & chart rendering
└── style.css # Glassmorphism UI design system
```
## 🛠️ Tech Stack
| Layer | Technology |
|-------|-----------|
| **Backend** | Flask, Python 3.10+ |
| **Data** | yfinance, Yahoo Finance API |
| **AI** | Google Gemini 2.0 Flash |
| **Database** | SQLite (WAL mode) |
| **Charts** | TradingView Lightweight Charts |
| **Frontend** | Vanilla JS, CSS (Glassmorphism) |
## 📊 API Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/analyze` | POST | Full stock analysis |
| `/api/suggest` | GET | Ticker autocomplete |
| `/api/portfolio` | GET | Portfolio summary |
| `/api/portfolio/buy` | POST | Buy long |
| `/api/portfolio/sell` | POST | Sell long |
| `/api/portfolio/short` | POST | Short sell |
| `/api/portfolio/cover` | POST | Cover short |
| `/api/portfolio/limit-order` | POST | Place limit order |
| `/api/portfolio/stop-order` | POST | Place stop order |
| `/api/portfolio/analytics` | GET | Performance metrics |
| `/api/portfolio/equity-curve` | GET | Equity curve data |
| `/api/market-summary` | GET | Market dashboard data |
| `/api/options/price` | POST | Options pricing |
| `/api/backtest` | POST | Run backtests |
| `/api/advisor/analyze` | POST | AI advisory |
| `/api/news` | GET | News & sentiment |
## 📜 License
This project is for educational purposes. Built as part of a university finance course.
---
<p align="center">
Built with ❤️ using Flask & yfinance
</p>

106
advisor.py Normal file
View File

@ -0,0 +1,106 @@
"""
Financial Advisory Algorithm - Phase 7 (Bi-Directional / Market Neutral)
Analyzes Technicals + News to suggest Long, Short, or Hedge positions.
"""
import technical_analyzer
import news_analyzer
import sys
API_KEY = "wybWEsp1oB9abHfz3yPpQYwffxaN21B7"
def get_user_input():
print("\n=== Financial Advisor (Bi-Directional Hedge Edition) ===")
ticker = input("Enter Stock Ticker (e.g., ^NSEBANK, NVDA): ").strip().upper()
if not ticker: ticker = "^NSEBANK"
try:
budget = float(input("Enter Monthly Budget (in USD/GBP): ").strip())
except ValueError: budget = 150.0
return ticker, budget
def determine_strategy(tech, sent):
"""
Combines news sentiment and technicals to pick direction.
"""
price = tech['price']
ema5 = tech.get('ema_5', price)
bb_upper = tech.get('bb_upper', 0)
bb_lower = tech.get('bb_lower', 0)
bb_width = (bb_upper - bb_lower) / price if price > 0 else 0
sentiment_score = sent['average_score']
analysis = []
# DIRECTIONAL BIAS
technical_bias = "BULLISH" if price > ema5 else "BEARISH"
sentiment_bias = "BULLISH" if sentiment_score > 0.1 else "BEARISH" if sentiment_score < -0.1 else "NEUTRAL"
analysis.append(f"Technical Bias (EMA 5): {technical_bias}")
analysis.append(f"Sentiment Bias (News): {sentiment_bias} ({sentiment_score})")
# DECISION LOGIC
if bb_width > 0.08:
# High volatility = Hedge
signal = "HEDGE (50% Long / 50% Short)"
reason = "Market volatility is extremely high (BB Width > 8%). Hedging will neutralize risk from weekend news gaps."
elif technical_bias == "BULLISH" and sentiment_bias != "BEARISH":
signal = "AGGRESSIVE LONG"
reason = "Price is trending above EMA 5 and news sentiment confirms strength."
elif technical_bias == "BEARISH" and sentiment_bias != "BULLISH":
signal = "AGGRESSIVE SHORT"
reason = "Price is breaking down and news sentiment is negative. Profit from the drop."
else:
# Conflict between news and tech
signal = "TACTICAL HEDGE / WAIT"
reason = "Conflicting signals (Tech vs Sentiment). Protecting capital is a priority."
return {
'signal': signal,
'reason': reason,
'details': analysis,
'sentiment_data': sent
}
def print_trade_plan(ticker, budget, tech, strategy):
print(f"\n--- {ticker} BI-DIRECTIONAL PLAN ---")
for detail in strategy['details']:
print(f" * {detail}")
print(f"\n RECOMMENDED ACTION: {strategy['signal']}")
print(f" RATIONALE: {strategy['reason']}")
price = tech['price']
if "LONG" in strategy['signal']:
print(f"\n [BULLISH EXECUTION]")
print(f" - Entry: {price:.2f}")
print(f" - Target: {price * 1.05:.2f} (+5%)")
print(f" - Stop: {price * 0.97:.2f} (-3%)")
elif "SHORT" in strategy['signal']:
print(f"\n [BEARISH EXECUTION]")
print(f" - Entry: {price:.2f} (Sell)")
print(f" - Target: {price * 0.95:.2f} (Profit from drop)")
print(f" - Stop: {price * 1.03:.2f} (Exit if price rises)")
elif "HEDGE" in strategy['signal']:
print(f"\n [NEUTRAL EXECUTION]")
print(f" - Long Px: {price:.2f} (50% Allocation)")
print(f" - Short Px: {price:.2f} (50% Allocation)")
print(f" - Effect: Portfolio value stays stable regardless of Monday open.")
def main():
ticker, budget = get_user_input()
print(f"\n... Analyzing {ticker} (Technicals + News) ...")
tech_data = technical_analyzer.get_technicals(ticker, API_KEY)
# Fetch real news
news_items = news_analyzer.fetch_news(ticker, limit=5)
sentiment = news_analyzer.analyze_sentiment(news_items)
if tech_data:
strategy = determine_strategy(tech_data, sentiment)
print_trade_plan(ticker, budget, tech_data, strategy)
else:
print("Failed to fetch data.")
if __name__ == "__main__":
main()

1672
app.py Normal file

File diff suppressed because it is too large Load Diff

177
backtester.py Normal file
View File

@ -0,0 +1,177 @@
"""
Backtesting Engine - Phase 7 (Bi-Directional Market Neutral)
Profits from both rising and falling markets.
Logic:
1. Long: Price > EMA 5
2. Short: Price < EMA 5
3. Hedge: If BB Width > 5% (High Volatility), hold both to stay neutral.
"""
import pandas as pd
import numpy as np
from financetoolkit import Toolkit
import sys
# Constants
API_KEY = "wybWEsp1oB9abHfz3yPpQYwffxaN21B7"
TICKERS = ["^NSEBANK", "NVDA", "TSLA"]
MONTHLY_BUDGET = 300.0
def fetch_portfolio_data():
"""Fetches 1 year of historical data with technicals."""
print(f"--- Fetching 1 Year Data for {TICKERS} ---")
try:
companies = Toolkit(
tickers=TICKERS,
api_key=API_KEY,
start_date="2024-01-01",
)
historical = companies.get_historical_data(period="daily")
portfolio = {}
for ticker in TICKERS:
if ticker in historical.columns.get_level_values(1):
df = historical.xs(ticker, level=1, axis=1).copy()
elif ticker in historical.columns.get_level_values(0):
df = historical.xs(ticker, level=0, axis=1).copy()
else: continue
# Technicals for signals
df['EMA_5'] = df['Close'].ewm(span=5, adjust=False).mean()
# Bollinger Band Width
std_20 = df['Close'].rolling(window=20).std()
ma_20 = df['Close'].rolling(window=20).mean()
df['BB_Width'] = (4 * std_20) / ma_20
df.dropna(inplace=True)
portfolio[ticker] = df
return portfolio
except Exception as e:
print(f"Error fetching data: {e}")
return {}
def simulate_market_neutral(data, ticker):
"""
Simulates Bi-Directional strategy.
Can be Long, Short, or Hedged.
"""
cash = 0
long_holdings = 0
short_holdings = 0
short_entry_price = 0
total_invested = 0
budget_per_month = MONTHLY_BUDGET / len(TICKERS)
day_count = 0
for date, row in data.iterrows():
day_count += 1
price = row['Close']
# Monthly Injection
if day_count % 20 == 0:
cash += budget_per_month
total_invested += budget_per_month
# 1. EXIT CURRENT POSITIONS IF SIGNAL CHANGES
# (Simplified: check daily if we should switch)
signal = "NEUTRAL"
if row['BB_Width'] > 0.08: # Hedge if high volatility (>8% width)
signal = "HEDGE"
elif price > row['EMA_5']:
signal = "LONG"
else:
signal = "SHORT"
# 2. EXECUTE
if signal == "LONG":
# Close Short if any
if short_holdings > 0:
profit = (short_entry_price - price) * short_holdings
cash += (short_holdings * short_entry_price) + profit
short_holdings = 0
# Open Long
if cash > 10:
long_holdings += cash / price
cash = 0
elif signal == "SHORT":
# Close Long if any
if long_holdings > 0:
cash += long_holdings * price
long_holdings = 0
# Open Short
if cash > 10:
short_holdings = cash / price
short_entry_price = price
cash = 0
elif signal == "HEDGE":
# Split 50/50
current_val = cash + (long_holdings * price) + (short_holdings * price if short_holdings > 0 else 0)
# Flatten everything first for simplicity in simulation
if short_holdings > 0:
profit = (short_entry_price - price) * short_holdings
current_val += profit # Adjust for short profit/loss
cash = current_val
long_holdings = (cash * 0.5) / price
short_holdings = (cash * 0.5) / price
short_entry_price = price
cash = 0
# Final Liquidation
final_price = data.iloc[-1]['Close']
short_profit = (short_entry_price - final_price) * short_holdings if short_holdings > 0 else 0
final_val = cash + (long_holdings * final_price) + (short_holdings * short_entry_price) + short_profit
profit = final_val - total_invested
roi = profit / total_invested if total_invested > 0 else 0
# Bench
start = data.iloc[0]['Close']
end = data.iloc[-1]['Close']
bench_roi = (end / start) - 1
return {
'ticker': ticker,
'final_val': final_val,
'invested': total_invested,
'roi': roi * 100,
'bench_roi': bench_roi * 100
}
def main():
portfolio = fetch_portfolio_data()
if not portfolio: return
print("\n--- Running Phase 7 Backtest (Bi-Directional Long/Short) ---")
results = []
for ticker, df in portfolio.items():
res = simulate_market_neutral(df, ticker)
results.append(res)
print("\n" + "="*60)
print(f"{'Ticker':<10} | {'Strategy ROI':<15} | {'Bench ROI':<10}")
print("-" * 60)
total_invested = 0
total_final = 0
for r in results:
print(f"{r['ticker']:<10} | {r['roi']:>14.2f}% | {r['bench_roi']:>9.2f}%")
total_invested += r['invested']
total_final += r['final_val']
final_roi = ((total_final - total_invested) / total_invested) * 100 if total_invested > 0 else 0
print("-" * 60)
print(f"{'TOTAL':<10} | {final_roi:>14.2f}% | {'---':>9}")
print("="*60)
print("\nNote: ROI includes profits from both price gains (Long) and price drops (Short).")
if __name__ == "__main__":
main()

101
news_analyzer.py Normal file
View File

@ -0,0 +1,101 @@
"""
Module for fetching financial news via Google News RSS and analyzing sentiment using VADER.
"""
import feedparser
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
from datetime import datetime
import time
def fetch_news(ticker, limit=5):
"""
Fetches the latest news for a given ticker from Google News RSS.
Args:
ticker (str): Stock ticker symbol (e.g., 'AAPL', 'NVDA', 'RELIANCE.NS').
limit (int): Maximum number of news items to return.
Returns:
list: A list of dictionaries containing 'title', 'link', 'published', and 'summary'.
"""
# Use standard Google News RSS search query
# "when:1d" parameter ensures recent news if supported, but RSS feed order is usually chronological
encoded_ticker = ticker.replace("&", "%26")
rss_url = f"https://news.google.com/rss/search?q={encoded_ticker}+stock+when:1d&hl=en-US&gl=US&ceid=US:en"
try:
feed = feedparser.parse(rss_url)
news_items = []
for entry in feed.entries[:limit]:
news_items.append({
'title': entry.title,
'link': entry.link,
'published': entry.get('published', datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT")),
'summary': entry.get('description', '')
})
return news_items
except Exception as e:
print(f"Error fetching news for {ticker}: {e}")
return []
def analyze_sentiment(news_items):
"""
Analyzes the sentiment of a list of news items.
Args:
news_items (list): List of news dictionaries.
Returns:
dict: Contains 'average_score', 'sentiment_label', and 'scored_news'.
"""
analyzer = SentimentIntensityAnalyzer()
total_score = 0
scored_news = []
if not news_items:
return {
'average_score': 0,
'sentiment_label': 'Neutral',
'scored_news': []
}
for item in news_items:
# Analyze title mostly as it contains the key info in RSS
text_to_analyze = f"{item['title']}. {item['summary']}"
sentiment = analyzer.polarity_scores(text_to_analyze)
compound_score = sentiment['compound']
total_score += compound_score
item_with_score = item.copy()
item_with_score['sentiment_score'] = compound_score
scored_news.append(item_with_score)
average_score = total_score / len(news_items)
if average_score >= 0.05:
label = "Positive"
elif average_score <= -0.05:
label = "Negative"
else:
label = "Neutral"
return {
'average_score': round(average_score, 4),
'sentiment_label': label,
'scored_news': scored_news
}
if __name__ == "__main__":
# Test Block
ticker = "AAPL"
print(f"--- Fetching News for {ticker} ---")
news = fetch_news(ticker)
for n in news:
print(f"- {n['title']}")
print(f"\n--- Analyzing Sentiment ---")
result = analyze_sentiment(news)
print(f"Average Score: {result['average_score']}")
print(f"Label: {result['sentiment_label']}")

185
options_engine.py Normal file
View File

@ -0,0 +1,185 @@
"""
FinanceIQ v5.1 Options Analytics Engine
Black-Scholes pricing, Greeks, implied volatility, and payoff diagrams.
"""
import math
from scipy.stats import norm
def d1(S, K, T, r, sigma):
"""Calculate d1 for Black-Scholes."""
if T <= 0 or sigma <= 0:
return 0.0
return (math.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * math.sqrt(T))
def d2(S, K, T, r, sigma):
"""Calculate d2 for Black-Scholes."""
return d1(S, K, T, r, sigma) - sigma * math.sqrt(T) if T > 0 and sigma > 0 else 0.0
def bs_call_price(S, K, T, r, sigma):
"""Black-Scholes call option price."""
if T <= 0:
return max(S - K, 0)
_d1 = d1(S, K, T, r, sigma)
_d2 = d2(S, K, T, r, sigma)
return S * norm.cdf(_d1) - K * math.exp(-r * T) * norm.cdf(_d2)
def bs_put_price(S, K, T, r, sigma):
"""Black-Scholes put option price."""
if T <= 0:
return max(K - S, 0)
_d1 = d1(S, K, T, r, sigma)
_d2 = d2(S, K, T, r, sigma)
return K * math.exp(-r * T) * norm.cdf(-_d2) - S * norm.cdf(-_d1)
def calculate_greeks(S, K, T, r, sigma, option_type="call"):
"""
Calculate all Greeks for an option.
S: underlying price, K: strike, T: time to expiry (years),
r: risk-free rate, sigma: implied volatility
"""
if T <= 0 or sigma <= 0:
intrinsic = max(S - K, 0) if option_type == "call" else max(K - S, 0)
return {
"delta": 1.0 if (option_type == "call" and S > K) else (-1.0 if option_type == "put" and K > S else 0.0),
"gamma": 0.0,
"theta": 0.0,
"vega": 0.0,
"rho": 0.0,
"price": intrinsic
}
_d1 = d1(S, K, T, r, sigma)
_d2 = d2(S, K, T, r, sigma)
sqrt_T = math.sqrt(T)
# Delta
delta = norm.cdf(_d1) if option_type == "call" else norm.cdf(_d1) - 1
# Gamma (same for calls & puts)
gamma = norm.pdf(_d1) / (S * sigma * sqrt_T)
# Theta (per day)
theta_common = -(S * norm.pdf(_d1) * sigma) / (2 * sqrt_T)
if option_type == "call":
theta = (theta_common - r * K * math.exp(-r * T) * norm.cdf(_d2)) / 365
else:
theta = (theta_common + r * K * math.exp(-r * T) * norm.cdf(-_d2)) / 365
# Vega (per 1% move in IV)
vega = S * sqrt_T * norm.pdf(_d1) / 100
# Rho (per 1% move in rate)
if option_type == "call":
rho = K * T * math.exp(-r * T) * norm.cdf(_d2) / 100
else:
rho = -K * T * math.exp(-r * T) * norm.cdf(-_d2) / 100
# Price
price = bs_call_price(S, K, T, r, sigma) if option_type == "call" else bs_put_price(S, K, T, r, sigma)
return {
"delta": round(delta, 4),
"gamma": round(gamma, 6),
"theta": round(theta, 4),
"vega": round(vega, 4),
"rho": round(rho, 4),
"price": round(price, 2)
}
def implied_volatility(market_price, S, K, T, r, option_type="call", tol=1e-6, max_iter=100):
"""
Calculate implied volatility using Newton-Raphson method.
"""
if T <= 0 or market_price <= 0:
return 0.0
sigma = 0.3 # initial guess
for _ in range(max_iter):
if option_type == "call":
price = bs_call_price(S, K, T, r, sigma)
else:
price = bs_put_price(S, K, T, r, sigma)
diff = price - market_price
if abs(diff) < tol:
return round(sigma, 6)
# Vega for Newton-Raphson step
_d1 = d1(S, K, T, r, sigma)
vega = S * math.sqrt(T) * norm.pdf(_d1)
if vega < 1e-12:
break
sigma -= diff / vega
sigma = max(sigma, 0.001) # floor
return round(sigma, 6)
def payoff_diagram(S, K, premium, option_type="call", is_long=True, num_points=50):
"""
Generate payoff diagram data points.
Returns list of {price, payoff, profit} dicts.
"""
low = K * 0.7
high = K * 1.3
step = (high - low) / num_points
points = []
for i in range(num_points + 1):
price = low + i * step
if option_type == "call":
payoff = max(price - K, 0)
else:
payoff = max(K - price, 0)
if is_long:
profit = payoff - premium
else:
profit = premium - payoff
points.append({
"price": round(price, 2),
"payoff": round(payoff, 2),
"profit": round(profit, 2)
})
return points
def enrich_chain_with_greeks(chain_data, current_price, expiry_date_str, risk_free_rate=0.05):
"""
Add Greeks to each option in a chain.
chain_data: list of dicts from yfinance option_chain
expiry_date_str: 'YYYY-MM-DD'
"""
from datetime import datetime
try:
expiry = datetime.strptime(expiry_date_str, "%Y-%m-%d")
today = datetime.now()
T = max((expiry - today).days / 365.0, 0.001)
except:
T = 0.1 # fallback
for opt in chain_data:
strike = opt.get("strike", 0)
last_price = opt.get("lastPrice", 0)
iv = opt.get("impliedVolatility", 0.3)
opt_type = "call" if opt.get("_type", "call") == "call" else "put"
if strike > 0 and current_price > 0 and iv > 0:
greeks = calculate_greeks(current_price, strike, T, risk_free_rate, iv, opt_type)
opt["delta"] = greeks["delta"]
opt["gamma"] = greeks["gamma"]
opt["theta"] = greeks["theta"]
opt["vega"] = greeks["vega"]
opt["rho"] = greeks["rho"]
opt["bs_price"] = greeks["price"]
else:
opt["delta"] = opt["gamma"] = opt["theta"] = opt["vega"] = opt["rho"] = 0
opt["bs_price"] = 0
return chain_data

741
portfolio_engine.py Normal file
View File

@ -0,0 +1,741 @@
"""
FinanceIQ v5.2 Enhanced Paper Trading Portfolio Engine
SQLite-backed virtual portfolio with $100K starting capital.
Features inspired by hftbacktest:
- Market, Limit, Stop-Loss, Take-Profit orders
- Slippage & commission modeling
- Short selling with margin tracking
- Equity curve tracking
- Enhanced analytics (Sortino, Calmar, profit factor, avg hold period)
"""
import sqlite3
import os
import math
from datetime import datetime
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "portfolio.db")
INITIAL_CASH = 100000.00
DEFAULT_SLIPPAGE_BPS = 5 # 0.05% default slippage
DEFAULT_COMMISSION_PER_SHARE = 0.005 # $0.005 per share (IBKR-style)
MIN_COMMISSION = 1.00 # $1 minimum per trade
def get_db():
"""Get database connection and ensure tables exist."""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("""
CREATE TABLE IF NOT EXISTS portfolio_meta (
id INTEGER PRIMARY KEY CHECK (id = 1),
cash REAL NOT NULL DEFAULT 100000.00,
slippage_bps REAL NOT NULL DEFAULT 5,
commission_per_share REAL NOT NULL DEFAULT 0.005,
margin_used REAL NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS positions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticker TEXT NOT NULL,
asset_type TEXT NOT NULL DEFAULT 'stock',
shares REAL NOT NULL DEFAULT 0,
avg_cost REAL NOT NULL DEFAULT 0,
side TEXT NOT NULL DEFAULT 'LONG',
opened_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(ticker, side)
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticker TEXT NOT NULL,
asset_type TEXT NOT NULL DEFAULT 'stock',
action TEXT NOT NULL,
side TEXT NOT NULL DEFAULT 'LONG',
shares REAL NOT NULL,
price REAL NOT NULL,
slippage REAL NOT NULL DEFAULT 0,
commission REAL NOT NULL DEFAULT 0,
total REAL NOT NULL,
pnl REAL DEFAULT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS pending_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticker TEXT NOT NULL,
asset_type TEXT NOT NULL DEFAULT 'stock',
order_type TEXT NOT NULL,
side TEXT NOT NULL DEFAULT 'BUY',
shares REAL NOT NULL,
target_price REAL NOT NULL,
status TEXT NOT NULL DEFAULT 'PENDING',
created_at TEXT NOT NULL DEFAULT (datetime('now')),
filled_at TEXT DEFAULT NULL,
expires_at TEXT DEFAULT NULL
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS equity_curve (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
total_value REAL NOT NULL,
cash REAL NOT NULL,
positions_value REAL NOT NULL,
daily_return REAL DEFAULT 0
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS portfolio_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL UNIQUE,
total_value REAL NOT NULL,
cash REAL NOT NULL,
positions_value REAL NOT NULL
)
""")
cursor = conn.execute("SELECT COUNT(*) FROM portfolio_meta")
if cursor.fetchone()[0] == 0:
conn.execute("INSERT INTO portfolio_meta (id, cash) VALUES (1, ?)", (INITIAL_CASH,))
conn.commit()
return conn
def _get_meta(conn):
row = conn.execute("SELECT * FROM portfolio_meta WHERE id = 1").fetchone()
return dict(row)
def _calc_slippage(price, shares, slippage_bps, side):
"""Calculate slippage based on order direction."""
slip_pct = slippage_bps / 10000.0
if side == "BUY":
return price * slip_pct # pay more when buying
else:
return -price * slip_pct # receive less when selling
def _calc_commission(shares, comm_per_share):
"""Calculate commission with minimum."""
return max(abs(shares) * comm_per_share, MIN_COMMISSION)
def get_cash():
conn = get_db()
row = conn.execute("SELECT cash FROM portfolio_meta WHERE id = 1").fetchone()
conn.close()
return row["cash"] if row else INITIAL_CASH
def get_positions():
conn = get_db()
rows = conn.execute("SELECT * FROM positions WHERE shares > 0").fetchall()
conn.close()
return [dict(r) for r in rows]
def buy(ticker, shares, price, asset_type="stock"):
"""Buy shares (long) with slippage and commission."""
if shares <= 0 or price <= 0:
return {"error": "Invalid shares or price"}
conn = get_db()
meta = _get_meta(conn)
cash = meta["cash"]
slippage_bps = meta["slippage_bps"]
comm_per_share = meta["commission_per_share"]
# Apply slippage
slip = _calc_slippage(price, shares, slippage_bps, "BUY")
exec_price = price + slip
# Calculate commission
commission = _calc_commission(shares, comm_per_share)
total_cost = shares * exec_price + commission
if total_cost > cash:
conn.close()
return {"error": f"Insufficient cash. Need ${total_cost:.2f}, have ${cash:.2f}"}
new_cash = cash - total_cost
conn.execute("UPDATE portfolio_meta SET cash = ?, updated_at = datetime('now') WHERE id = 1", (new_cash,))
existing = conn.execute("SELECT * FROM positions WHERE ticker = ? AND side = 'LONG'", (ticker,)).fetchone()
if existing:
old_shares = existing["shares"]
old_avg = existing["avg_cost"]
new_shares = old_shares + shares
new_avg = ((old_shares * old_avg) + (shares * exec_price)) / new_shares
conn.execute(
"UPDATE positions SET shares = ?, avg_cost = ?, updated_at = datetime('now') WHERE ticker = ? AND side = 'LONG'",
(new_shares, new_avg, ticker)
)
else:
conn.execute(
"INSERT INTO positions (ticker, asset_type, shares, avg_cost, side) VALUES (?, ?, ?, ?, 'LONG')",
(ticker, asset_type, shares, exec_price)
)
conn.execute(
"""INSERT INTO transactions (ticker, asset_type, action, side, shares, price, slippage, commission, total)
VALUES (?, ?, 'BUY', 'LONG', ?, ?, ?, ?, ?)""",
(ticker, asset_type, shares, exec_price, slip * shares, commission, total_cost)
)
conn.commit()
conn.close()
return {
"success": True, "action": "BUY", "ticker": ticker,
"shares": shares, "price": round(exec_price, 4),
"slippage": round(slip, 4), "commission": round(commission, 2),
"total": round(total_cost, 2), "remaining_cash": round(new_cash, 2)
}
def sell(ticker, shares, price):
"""Sell long shares with slippage and commission."""
if shares <= 0 or price <= 0:
return {"error": "Invalid shares or price"}
conn = get_db()
meta = _get_meta(conn)
slippage_bps = meta["slippage_bps"]
comm_per_share = meta["commission_per_share"]
existing = conn.execute("SELECT * FROM positions WHERE ticker = ? AND side = 'LONG'", (ticker,)).fetchone()
if not existing or existing["shares"] < shares:
conn.close()
available = existing["shares"] if existing else 0
return {"error": f"Insufficient shares. Have {available}, trying to sell {shares}"}
slip = _calc_slippage(price, shares, slippage_bps, "SELL")
exec_price = price + slip # slip is negative for sells
commission = _calc_commission(shares, comm_per_share)
total_proceeds = shares * exec_price - commission
new_shares = existing["shares"] - shares
cash = meta["cash"]
new_cash = cash + total_proceeds
conn.execute("UPDATE portfolio_meta SET cash = ?, updated_at = datetime('now') WHERE id = 1", (new_cash,))
if new_shares <= 0:
conn.execute("DELETE FROM positions WHERE ticker = ? AND side = 'LONG'", (ticker,))
else:
conn.execute(
"UPDATE positions SET shares = ?, updated_at = datetime('now') WHERE ticker = ? AND side = 'LONG'",
(new_shares, ticker)
)
pnl = (exec_price - existing["avg_cost"]) * shares - commission
conn.execute(
"""INSERT INTO transactions (ticker, asset_type, action, side, shares, price, slippage, commission, total, pnl)
VALUES (?, ?, 'SELL', 'LONG', ?, ?, ?, ?, ?, ?)""",
(ticker, existing["asset_type"], shares, exec_price, slip * shares, commission, total_proceeds, pnl)
)
conn.commit()
conn.close()
return {
"success": True, "action": "SELL", "ticker": ticker,
"shares": shares, "price": round(exec_price, 4),
"slippage": round(slip, 4), "commission": round(commission, 2),
"total": round(total_proceeds, 2), "pnl": round(pnl, 2),
"remaining_cash": round(new_cash, 2)
}
def short_sell(ticker, shares, price, asset_type="stock"):
"""Open a short position. Margin is deducted from cash."""
if shares <= 0 or price <= 0:
return {"error": "Invalid shares or price"}
conn = get_db()
meta = _get_meta(conn)
cash = meta["cash"]
slippage_bps = meta["slippage_bps"]
comm_per_share = meta["commission_per_share"]
slip = _calc_slippage(price, shares, slippage_bps, "SELL")
exec_price = price + slip # worse price when selling (slippage)
commission = _calc_commission(shares, comm_per_share)
# Margin requirement: 100% of position value + commission
margin_req = shares * exec_price + commission
if margin_req > cash:
conn.close()
return {"error": f"Insufficient funds. Need ${margin_req:.2f}, have ${cash:.2f}"}
# Deduct margin from cash (no proceeds added — paper trading model)
new_cash = cash - margin_req
new_margin = meta["margin_used"] + (shares * exec_price)
conn.execute("UPDATE portfolio_meta SET cash = ?, margin_used = ?, updated_at = datetime('now') WHERE id = 1",
(new_cash, new_margin))
existing = conn.execute("SELECT * FROM positions WHERE ticker = ? AND side = 'SHORT'", (ticker,)).fetchone()
if existing:
old_shares = existing["shares"]
old_avg = existing["avg_cost"]
new_shares = old_shares + shares
new_avg = ((old_shares * old_avg) + (shares * exec_price)) / new_shares
conn.execute(
"UPDATE positions SET shares = ?, avg_cost = ?, updated_at = datetime('now') WHERE ticker = ? AND side = 'SHORT'",
(new_shares, new_avg, ticker)
)
else:
conn.execute(
"INSERT INTO positions (ticker, asset_type, shares, avg_cost, side) VALUES (?, ?, ?, ?, 'SHORT')",
(ticker, asset_type, shares, exec_price)
)
conn.execute(
"""INSERT INTO transactions (ticker, asset_type, action, side, shares, price, slippage, commission, total)
VALUES (?, ?, 'SHORT', 'SHORT', ?, ?, ?, ?, ?)""",
(ticker, asset_type, shares, exec_price, abs(slip) * shares, commission, margin_req)
)
conn.commit()
conn.close()
return {
"success": True, "action": "SHORT", "ticker": ticker,
"shares": shares, "price": round(exec_price, 4),
"margin_required": round(margin_req, 2),
"commission": round(commission, 2)
}
def cover_short(ticker, shares, price):
"""Cover (buy back) a short position. Releases margin + P&L to cash."""
if shares <= 0 or price <= 0:
return {"error": "Invalid shares or price"}
conn = get_db()
meta = _get_meta(conn)
slippage_bps = meta["slippage_bps"]
comm_per_share = meta["commission_per_share"]
existing = conn.execute("SELECT * FROM positions WHERE ticker = ? AND side = 'SHORT'", (ticker,)).fetchone()
if not existing or existing["shares"] < shares:
conn.close()
available = existing["shares"] if existing else 0
return {"error": f"No short position. Have {available} short shares."}
slip = _calc_slippage(price, shares, slippage_bps, "BUY")
exec_price = price + slip # worse price when buying back (slippage)
commission = _calc_commission(shares, comm_per_share)
# P&L: profit when we shorted high and cover low
entry_value = shares * existing["avg_cost"]
cover_cost = shares * exec_price
pnl = entry_value - cover_cost - commission # short profit when price drops
# Release margin and return to cash with P&L
cash = meta["cash"]
margin_release = entry_value # we held 100% of entry value as margin
new_cash = cash + margin_release + pnl # margin back + profit/loss
new_margin = max(0, meta["margin_used"] - margin_release)
conn.execute("UPDATE portfolio_meta SET cash = ?, margin_used = ?, updated_at = datetime('now') WHERE id = 1",
(new_cash, new_margin))
new_shares = existing["shares"] - shares
if new_shares <= 0:
conn.execute("DELETE FROM positions WHERE ticker = ? AND side = 'SHORT'", (ticker,))
else:
conn.execute(
"UPDATE positions SET shares = ?, updated_at = datetime('now') WHERE ticker = ? AND side = 'SHORT'",
(new_shares, ticker)
)
conn.execute(
"""INSERT INTO transactions (ticker, asset_type, action, side, shares, price, slippage, commission, total, pnl)
VALUES (?, ?, 'COVER', 'SHORT', ?, ?, ?, ?, ?, ?)""",
(ticker, existing["asset_type"], shares, exec_price, abs(slip) * shares, commission, cover_cost + commission, pnl)
)
conn.commit()
conn.close()
return {
"success": True, "action": "COVER", "ticker": ticker,
"shares": shares, "price": round(exec_price, 4),
"pnl": round(pnl, 2), "commission": round(commission, 2),
"remaining_cash": round(new_cash, 2)
}
# ── Pending Orders (Limit / Stop) ────────────────────────
def place_limit_order(ticker, side, shares, limit_price, asset_type="stock", expires_hours=24):
"""Place a limit order that fills when market price hits the target."""
if shares <= 0 or limit_price <= 0:
return {"error": "Invalid order parameters"}
expires = datetime.now()
from datetime import timedelta
expires = (expires + timedelta(hours=expires_hours)).isoformat()
conn = get_db()
conn.execute(
"""INSERT INTO pending_orders (ticker, asset_type, order_type, side, shares, target_price, expires_at)
VALUES (?, ?, 'LIMIT', ?, ?, ?, ?)""",
(ticker, asset_type, side, shares, limit_price, expires)
)
conn.commit()
order_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
conn.close()
return {"success": True, "order_id": order_id, "order_type": "LIMIT",
"side": side, "ticker": ticker, "shares": shares,
"target_price": limit_price, "expires_at": expires}
def place_stop_order(ticker, side, shares, stop_price, asset_type="stock", expires_hours=24):
"""Place a stop order (stop-loss or take-profit)."""
if shares <= 0 or stop_price <= 0:
return {"error": "Invalid order parameters"}
expires = datetime.now()
from datetime import timedelta
expires = (expires + timedelta(hours=expires_hours)).isoformat()
conn = get_db()
conn.execute(
"""INSERT INTO pending_orders (ticker, asset_type, order_type, side, shares, target_price, expires_at)
VALUES (?, ?, 'STOP', ?, ?, ?, ?)""",
(ticker, asset_type, side, shares, stop_price, expires)
)
conn.commit()
order_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
conn.close()
return {"success": True, "order_id": order_id, "order_type": "STOP",
"side": side, "ticker": ticker, "shares": shares,
"target_price": stop_price, "expires_at": expires}
def get_pending_orders():
"""Get all pending orders."""
conn = get_db()
# Expire old orders
conn.execute("UPDATE pending_orders SET status = 'EXPIRED' WHERE status = 'PENDING' AND expires_at < datetime('now')")
conn.commit()
rows = conn.execute("SELECT * FROM pending_orders WHERE status = 'PENDING' ORDER BY created_at DESC").fetchall()
conn.close()
return [dict(r) for r in rows]
def cancel_order(order_id):
"""Cancel a pending order."""
conn = get_db()
conn.execute("UPDATE pending_orders SET status = 'CANCELLED' WHERE id = ? AND status = 'PENDING'", (order_id,))
conn.commit()
conn.close()
return {"success": True, "order_id": order_id, "status": "CANCELLED"}
def check_and_fill_orders(current_prices):
"""Check pending orders against current prices and fill if conditions met.
Called during live polling.
current_prices: dict {ticker: price}
"""
conn = get_db()
pending = conn.execute("SELECT * FROM pending_orders WHERE status = 'PENDING'").fetchall()
filled = []
for order in pending:
order = dict(order)
ticker = order["ticker"]
if ticker not in current_prices:
continue
market_price = current_prices[ticker]
should_fill = False
if order["order_type"] == "LIMIT":
if order["side"] == "BUY" and market_price <= order["target_price"]:
should_fill = True
elif order["side"] == "SELL" and market_price >= order["target_price"]:
should_fill = True
elif order["order_type"] == "STOP":
if order["side"] == "SELL" and market_price <= order["target_price"]:
should_fill = True # stop-loss triggered
elif order["side"] == "BUY" and market_price >= order["target_price"]:
should_fill = True # stop-buy triggered
if should_fill:
conn.execute("UPDATE pending_orders SET status = 'FILLED', filled_at = datetime('now') WHERE id = ?",
(order["id"],))
if order["side"] == "BUY":
result = buy(ticker, order["shares"], market_price, order["asset_type"])
else:
result = sell(ticker, order["shares"], market_price)
filled.append({"order_id": order["id"], "ticker": ticker, "result": result})
conn.commit()
conn.close()
return filled
# ── Equity Curve ──────────────────────────────────────
def record_equity_point(total_value, cash, positions_value):
"""Record a point on the equity curve."""
conn = get_db()
# Get last point for daily return calc
last = conn.execute("SELECT total_value FROM equity_curve ORDER BY id DESC LIMIT 1").fetchone()
daily_return = 0
if last and last["total_value"] > 0:
daily_return = (total_value - last["total_value"]) / last["total_value"]
conn.execute(
"INSERT INTO equity_curve (timestamp, total_value, cash, positions_value, daily_return) VALUES (datetime('now'), ?, ?, ?, ?)",
(total_value, cash, positions_value, daily_return)
)
conn.commit()
conn.close()
def get_equity_curve(limit=500):
"""Get equity curve data for charting."""
conn = get_db()
rows = conn.execute(
"SELECT timestamp, total_value, cash, positions_value, daily_return FROM equity_curve ORDER BY id DESC LIMIT ?",
(limit,)
).fetchall()
conn.close()
return [dict(r) for r in reversed(rows)]
# ── Portfolio Summary ─────────────────────────────────
def get_portfolio_summary(current_prices=None):
if current_prices is None:
current_prices = {}
conn = get_db()
meta = _get_meta(conn)
cash = meta["cash"]
margin_used = meta["margin_used"]
positions = conn.execute("SELECT * FROM positions WHERE shares > 0").fetchall()
conn.close()
pos_list = []
total_long_value = 0
total_short_value = 0
total_cost_basis = 0
for p in positions:
ticker = p["ticker"]
shares = p["shares"]
avg_cost = p["avg_cost"]
side = p["side"]
current_price = current_prices.get(ticker, avg_cost)
if side == "LONG":
market_value = shares * current_price
cost_basis = shares * avg_cost
pnl = market_value - cost_basis
total_long_value += market_value
else: # SHORT
market_value = shares * avg_cost # short value is entry value
cost_basis = shares * avg_cost
pnl = (avg_cost - current_price) * shares # profit when price drops
total_short_value += shares * current_price
pnl_pct = (pnl / cost_basis * 100) if cost_basis > 0 else 0
total_cost_basis += cost_basis
pos_list.append({
"ticker": ticker, "asset_type": p["asset_type"],
"shares": shares, "avg_cost": round(avg_cost, 2),
"current_price": round(current_price, 2),
"market_value": round(market_value, 2),
"side": side,
"cost_basis": round(cost_basis, 2),
"pnl": round(pnl, 2), "pnl_pct": round(pnl_pct, 2),
"allocation_pct": 0
})
positions_value = total_long_value
# total_value = available cash + long positions + margin held + unrealized short P&L
# margin_used tracks entry value of shorts (deducted from cash at open)
# short_unrealized_pnl = sum of (entry_price - current_price) * shares for shorts
short_unrealized_pnl = 0
for p in pos_list:
if p["side"] == "SHORT":
short_unrealized_pnl += p["pnl"]
total_value = cash + positions_value + margin_used + short_unrealized_pnl
total_pnl = total_value - INITIAL_CASH
total_pnl_pct = (total_pnl / INITIAL_CASH * 100)
for p in pos_list:
p["allocation_pct"] = round((abs(p["market_value"]) / total_value * 100) if total_value > 0 else 0, 2)
return {
"cash": round(cash, 2),
"positions_value": round(positions_value, 2),
"total_value": round(total_value, 2),
"total_pnl": round(total_pnl, 2),
"total_pnl_pct": round(total_pnl_pct, 2),
"initial_capital": INITIAL_CASH,
"margin_used": round(margin_used, 2),
"buying_power": round(cash, 2),
"positions": pos_list,
"cash_allocation_pct": round((cash / total_value * 100) if total_value > 0 else 100, 2),
"slippage_bps": round(meta["slippage_bps"], 1),
"commission_per_share": round(meta["commission_per_share"], 4),
}
def get_transactions(limit=50):
conn = get_db()
rows = conn.execute(
"SELECT * FROM transactions ORDER BY timestamp DESC LIMIT ?", (limit,)
).fetchall()
conn.close()
return [dict(r) for r in rows]
def get_analytics(current_prices=None):
"""Enhanced analytics with Sortino, Calmar, profit factor."""
conn = get_db()
txns = conn.execute("SELECT * FROM transactions ORDER BY timestamp ASC").fetchall()
equity = conn.execute("SELECT * FROM equity_curve ORDER BY id ASC").fetchall()
conn.close()
if not txns:
return _empty_analytics()
# Daily returns from equity curve
daily_returns = [dict(e)["daily_return"] for e in equity if dict(e)["daily_return"] != 0]
if len(daily_returns) >= 2:
avg_ret = sum(daily_returns) / len(daily_returns)
std_ret = math.sqrt(sum((r - avg_ret) ** 2 for r in daily_returns) / len(daily_returns))
neg_returns = [r for r in daily_returns if r < 0]
downside_dev = math.sqrt(sum(r ** 2 for r in neg_returns) / len(neg_returns)) if neg_returns else 0
sharpe = (avg_ret / std_ret * math.sqrt(252)) if std_ret > 0 else 0
sortino = (avg_ret / downside_dev * math.sqrt(252)) if downside_dev > 0 else 0
# Max drawdown
values = [dict(e)["total_value"] for e in equity]
peak = values[0] if values else INITIAL_CASH
max_dd = 0
for v in values:
if v > peak: peak = v
dd = (peak - v) / peak
if dd > max_dd: max_dd = dd
# Calmar ratio (annualized return / max drawdown)
total_return = (values[-1] / values[0] - 1) if values and values[0] > 0 else 0
calmar = (total_return / max_dd) if max_dd > 0 else 0
else:
sharpe = sortino = calmar = 0
max_dd = 0
# Trade analysis
pnls = []
for t in txns:
t = dict(t)
if t.get("pnl") is not None:
pnls.append(t["pnl"])
wins = [p for p in pnls if p > 0]
losses = [p for p in pnls if p <= 0]
total_trades = len(pnls)
win_rate = (len(wins) / total_trades * 100) if total_trades > 0 else 0
gross_profit = sum(wins) if wins else 0
gross_loss = abs(sum(losses)) if losses else 0
profit_factor = (gross_profit / gross_loss) if gross_loss > 0 else float('inf') if gross_profit > 0 else 0
# Total commissions and slippage
total_commission = sum(dict(t).get("commission", 0) for t in txns)
total_slippage = sum(abs(dict(t).get("slippage", 0)) for t in txns)
summary = get_portfolio_summary(current_prices)
return {
"sharpe_ratio": round(sharpe, 2),
"sortino_ratio": round(sortino, 2),
"calmar_ratio": round(calmar, 2),
"max_drawdown_pct": round(max_dd * 100, 2),
"profit_factor": round(profit_factor, 2) if profit_factor != float('inf') else "",
"win_rate": round(win_rate, 1),
"total_trades": total_trades,
"winning_trades": len(wins),
"losing_trades": len(losses),
"avg_win": round(sum(wins) / len(wins), 2) if wins else 0,
"avg_loss": round(sum(losses) / len(losses), 2) if losses else 0,
"best_trade": round(max(pnls), 2) if pnls else 0,
"worst_trade": round(min(pnls), 2) if pnls else 0,
"total_return_pct": round(summary["total_pnl_pct"], 2),
"total_commission": round(total_commission, 2),
"total_slippage": round(total_slippage, 2),
"gross_profit": round(gross_profit, 2),
"gross_loss": round(gross_loss, 2),
}
def _empty_analytics():
return {
"sharpe_ratio": 0, "sortino_ratio": 0, "calmar_ratio": 0,
"max_drawdown_pct": 0, "profit_factor": 0,
"win_rate": 0, "total_trades": 0,
"winning_trades": 0, "losing_trades": 0,
"avg_win": 0, "avg_loss": 0,
"best_trade": 0, "worst_trade": 0,
"total_return_pct": 0, "total_commission": 0, "total_slippage": 0,
"gross_profit": 0, "gross_loss": 0,
}
def save_daily_snapshot(total_value, cash, positions_value):
conn = get_db()
today = datetime.now().strftime("%Y-%m-%d")
conn.execute(
"INSERT OR REPLACE INTO portfolio_snapshots (date, total_value, cash, positions_value) VALUES (?, ?, ?, ?)",
(today, total_value, cash, positions_value)
)
conn.commit()
conn.close()
def reset_portfolio():
"""Reset portfolio to initial state — clears everything."""
conn = get_db()
conn.execute("DELETE FROM portfolio_meta")
conn.execute("INSERT INTO portfolio_meta (id, cash) VALUES (1, ?)", (INITIAL_CASH,))
conn.execute("DELETE FROM positions")
conn.execute("DELETE FROM transactions")
conn.execute("DELETE FROM pending_orders")
conn.execute("DELETE FROM equity_curve")
conn.execute("DELETE FROM portfolio_snapshots")
conn.commit()
conn.close()
return {"success": True, "message": "Portfolio reset to $100,000"}
def update_settings(slippage_bps=None, commission_per_share=None):
"""Update portfolio simulation settings."""
conn = get_db()
if slippage_bps is not None:
conn.execute("UPDATE portfolio_meta SET slippage_bps = ? WHERE id = 1", (slippage_bps,))
if commission_per_share is not None:
conn.execute("UPDATE portfolio_meta SET commission_per_share = ? WHERE id = 1", (commission_per_share,))
conn.commit()
conn.close()
return {"success": True}

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
flask>=3.0
yfinance>=1.0
financetoolkit>=2.0
openpyxl>=3.1
requests>=2.31
requests-cache>=1.2
python-dotenv>=1.0

1649
static/script.js Normal file

File diff suppressed because it is too large Load Diff

2212
static/style.css Normal file

File diff suppressed because it is too large Load Diff

205
technical_analyzer.py Normal file
View File

@ -0,0 +1,205 @@
"""
Module for Technical Analysis using FinanceToolkit.
Calculates RSI, EMA, Volatility, MACD, Bollinger Bands, and Fibonacci Retracement Levels.
"""
from financetoolkit import Toolkit
import pandas as pd
import numpy as np
def get_technicals(ticker, api_key, period="daily"):
"""
Fetches comprehensive technical indicators for a given ticker.
Args:
ticker (str): Stock ticker symbol.
api_key (str): FMP API Key.
period (str): 'daily', 'weekly', etc.
Returns:
dict: Latest technical values and signals for 'Pro' analysis.
"""
try:
companies = Toolkit(
tickers=[ticker],
api_key=api_key,
)
# Helper to safely get the latest value for the specific ticker
def get_latest_series(df, col_name=None):
if df.empty: return 0
# multi-index handling: typically (Symbol, Metric) or (Metric, Symbol)
if isinstance(df.columns, pd.MultiIndex):
# Try selecting ticker from level 1
if ticker in df.columns.get_level_values(1):
df = df.xs(ticker, level=1, axis=1)
# Try selecting ticker from level 0
elif ticker in df.columns.get_level_values(0):
df = df.xs(ticker, level=0, axis=1)
# If still DataFrame, try to pick the specific column or just the first
if isinstance(df, pd.DataFrame):
if col_name and col_name in df.columns:
return df[col_name].iloc[-1]
return df.iloc[-1, 0] # Default to first column
else:
return df.iloc[-1]
def get_value(val):
return val.values[0] if hasattr(val, 'values') else val
# 1. RSI (14 periods) - Good for Overbought/Oversold
rsi_df = companies.technicals.get_relative_strength_index(window=14)
current_rsi = get_value(get_latest_series(rsi_df))
# 2. EMA (50 periods) - Trend Filter
ema_df = companies.technicals.get_exponential_moving_average(window=50)
current_ema = get_value(get_latest_series(ema_df))
# 2b. EMA (10 periods) - Short Term Trend (Phase 3)
ema10_df = companies.technicals.get_exponential_moving_average(window=10)
current_ema10 = get_value(get_latest_series(ema10_df))
# 2c. EMA (5 periods) - Ultra-Short Term (Phase 5 Aggressive)
ema5_df = companies.technicals.get_exponential_moving_average(window=5)
current_ema5 = get_value(get_latest_series(ema5_df))
# 3. MACD (12, 26, 9) - Momentum
macd_df = companies.technicals.get_moving_average_convergence_divergence(short_window=12, long_window=26, signal_window=9)
# MACD usually returns MACD line and Signal line. We need both.
# Structure likely: [MACD, Signal, Histogram] columns per ticker
# Let's get the full series for the ticker first
if isinstance(macd_df.columns, pd.MultiIndex):
if ticker in macd_df.columns.get_level_values(1):
macd_data = macd_df.xs(ticker, level=1, axis=1)
elif ticker in macd_df.columns.get_level_values(0):
macd_data = macd_df.xs(ticker, level=0, axis=1)
else:
macd_data = macd_df
else:
macd_data = macd_df
# Assuming columns like 'MACD', 'Signal', 'Histogram' or similar exist
# If columns are just numbered or named differently, we take last row
if 'MACD' in macd_data.columns and 'Signal' in macd_data.columns:
current_macd = macd_data['MACD'].iloc[-1]
current_signal_line = macd_data['Signal'].iloc[-1]
else:
# Fallback: assume col 0 is MACD, col 1 is Signal (standard toolkit output order often)
current_macd = macd_data.iloc[-1, 0]
current_signal_line = macd_data.iloc[-1, 1] if macd_data.shape[1] > 1 else 0
# 4. Bollinger Bands (20, 2) - Volatility & Mean Reversion
bb_df = companies.technicals.get_bollinger_bands(window=20, num_std_dev=2)
# Usually returns: Upper, Lower, (maybe Middle)
if isinstance(bb_df.columns, pd.MultiIndex):
if ticker in bb_df.columns.get_level_values(1):
bb_data = bb_df.xs(ticker, level=1, axis=1)
elif ticker in bb_df.columns.get_level_values(0):
bb_data = bb_df.xs(ticker, level=0, axis=1)
else:
bb_data = bb_df
else:
bb_data = bb_df
# Inspect columns for Upper/Lower
# Typ. 'Bollinger High', 'Bollinger Low' or similar
# Fallback to column indices if names vary
if bb_data.shape[1] >= 2:
bb_upper = bb_data.iloc[-1, 0] # Often Upper is first or checking names
bb_lower = bb_data.iloc[-1, 1]
# Try to be more specific if possible, but for now robust fallback
# Let's look for 'Upper' or 'High' in columns
for col in bb_data.columns:
if 'Upper' in str(col) or 'High' in str(col): bb_upper = bb_data[col].iloc[-1]
if 'Lower' in str(col) or 'Low' in str(col): bb_lower = bb_data[col].iloc[-1]
else:
bb_upper = 0
bb_lower = 0
# 5. Volatility & Fibonacci & NEW EXPERT METRICS (SMA, Volume, ATR)
hist_data = companies.get_historical_data()
atr = 0
rvol = 0
sma_200 = 0
sma_50 = 0
if not hist_data.empty:
# Handle MultiIndex
if isinstance(hist_data.columns, pd.MultiIndex):
try:
if ticker in hist_data.columns.get_level_values(1):
hist_data = hist_data.xs(ticker, level=1, axis=1)
elif ticker in hist_data.columns.get_level_values(0):
hist_data = hist_data.xs(ticker, level=0, axis=1)
except: pass
daily_returns = hist_data['Return'].iloc[-20:]
volatility = daily_returns.std() * np.sqrt(252) * 100
current_close = hist_data['Close'].iloc[-1]
if isinstance(current_close, pd.Series): current_close = current_close.iloc[0]
# --- EXPERT METRICS ---
# SMA 50 & 200
sma_50 = hist_data['Close'].rolling(window=50).mean().iloc[-1]
sma_200 = hist_data['Close'].rolling(window=200).mean().iloc[-1]
# Volume Relative Strength (RVOL)
# Current Volume / Avg Volume 20
curr_vol = hist_data['Volume'].iloc[-1]
avg_vol_20 = hist_data['Volume'].rolling(window=20).mean().iloc[-1]
rvol = curr_vol / avg_vol_20 if avg_vol_20 > 0 else 0
# ATR (14)
high = hist_data['High']
low = hist_data['Low']
close = hist_data['Close']
tr1 = high - low
tr2 = (high - close.shift()).abs()
tr3 = (low - close.shift()).abs()
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
atr = tr.rolling(window=14).mean().iloc[-1]
# Fibonacci
last_year = hist_data.iloc[-252:]
high_price = last_year['High'].max()
low_price = last_year['Low'].min()
if isinstance(high_price, pd.Series): high_price = high_price.iloc[0]
if isinstance(low_price, pd.Series): low_price = low_price.iloc[0]
diff = high_price - low_price
fib_levels = {
'0.0%': high_price,
'23.6%': high_price - 0.236 * diff,
'38.2%': high_price - 0.382 * diff,
'50.0%': high_price - 0.5 * diff,
'61.8%': high_price - 0.618 * diff,
'100.0%': low_price
}
else:
volatility = 0
current_close = 0
fib_levels = {}
return {
'symbol': ticker,
'price': current_close,
'rsi': current_rsi,
'ema_50': current_ema,
'ema_10': current_ema10,
'ema_5': current_ema5,
'sma_50': sma_50,
'sma_200': sma_200,
'rvol': rvol,
'atr': atr,
'macd_line': current_macd,
'macd_signal': current_signal_line,
'bb_upper': bb_upper,
'bb_lower': bb_lower,
'volatility_annual_pct': volatility.values[0] if hasattr(volatility, 'values') else volatility,
'fibonacci_levels': fib_levels
}
except Exception as e:
print(f"Error fetching technicals for {ticker}: {e}")
return None

890
templates/index.html Normal file
View File

@ -0,0 +1,890 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FinanceIQ — Multi-Asset Analytics Platform</title>
<meta name="description"
content="Professional multi-asset analytics: stocks, futures, options, currencies. DCF, Monte Carlo, AI insights, and live charting.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css">
<script src="https://unpkg.com/lightweight-charts@4.2.1/dist/lightweight-charts.standalone.production.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
</head>
<body>
<!-- ═══════════════════════════════════════════════════ -->
<!-- WELCOME PAGE -->
<!-- ═══════════════════════════════════════════════════ -->
<div class="welcome-page" id="welcomePage">
<div class="welcome-bg"></div>
<div class="welcome-content">
<div class="welcome-logo">
<div class="welcome-icon-wrap">
<i data-lucide="bar-chart-3" class="welcome-lucide-icon"></i>
</div>
<h1 class="welcome-title">FinanceIQ</h1>
<span class="badge-version">v5.2</span>
</div>
<p class="welcome-subtitle">Multi-Asset Intelligence Platform</p>
<p class="welcome-desc">
Stocks · Futures · Options · Currencies<br>
AI-powered analysis, live charts, DCF valuation, Monte Carlo forecasting, and institutional-grade
analytics.
</p>
<div class="welcome-features">
<div class="wf-card">
<i data-lucide="candlestick-chart" class="wf-lucide"></i>
<h3>Live Charts</h3>
<p>Real-time candlestick charts with EMA, SMA, Bollinger, and drawing tools.</p>
</div>
<div class="wf-card">
<i data-lucide="calculator" class="wf-lucide"></i>
<h3>DCF Valuation</h3>
<p>Intrinsic value via discounted cash flows, WACC, and terminal growth.</p>
</div>
<div class="wf-card">
<i data-lucide="brain" class="wf-lucide"></i>
<h3>AI Analyst</h3>
<p>LLM-powered research synthesizing technicals, fundamentals, and sentiment.</p>
</div>
<div class="wf-card">
<i data-lucide="trending-up" class="wf-lucide"></i>
<h3>Monte Carlo</h3>
<p>1,000-path probabilistic simulation with percentile bands.</p>
</div>
<div class="wf-card">
<i data-lucide="target" class="wf-lucide"></i>
<h3>Analyst Consensus</h3>
<p>Live Wall Street ratings, price targets, and insider activity.</p>
</div>
<div class="wf-card">
<i data-lucide="globe" class="wf-lucide"></i>
<h3>Macro Intelligence</h3>
<p>FRED indicators, sector heatmaps, correlation and peer analysis.</p>
</div>
</div>
<button class="welcome-cta" id="letsBeginBtn">
<i data-lucide="arrow-right" style="width:20px;height:20px;"></i>
<span>Get Started</span>
</button>
<p class="welcome-footer">FMP · Finnhub · FRED · Alpha Vantage · Ollama AI</p>
</div>
</div>
<!-- ═══════════════════════════════════════════════════ -->
<!-- MAIN APP -->
<!-- ═══════════════════════════════════════════════════ -->
<div class="app-layout" id="mainApp" style="display:none;">
<!-- ── SIDEBAR ─────────────────────────────────── -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-brand">
<i data-lucide="bar-chart-3" class="brand-icon"></i>
<span class="brand-text">FinanceIQ</span>
<span class="badge-version badge-sm">v5.2</span>
</div>
<nav class="sidebar-nav">
<div class="nav-group-label">Analysis</div>
<button class="nav-item active" data-tab="overview" data-asset="stocks,futures,options,currencies">
<i data-lucide="layout-dashboard"></i>
<span>Overview</span>
</button>
<button class="nav-item" data-tab="technical" data-asset="stocks,futures,currencies">
<i data-lucide="activity"></i>
<span>Technical</span>
</button>
<button class="nav-item" data-tab="fundamentals" data-asset="stocks">
<i data-lucide="landmark"></i>
<span>Fundamentals</span>
</button>
<button class="nav-item" data-tab="sentiment" data-asset="stocks,futures,options,currencies">
<i data-lucide="heart-pulse"></i>
<span>Sentiment</span>
</button>
<button class="nav-item" data-tab="options" data-asset="options">
<i data-lucide="layers"></i>
<span>Options</span>
</button>
<div class="nav-group-label">Research</div>
<button class="nav-item" data-tab="intelligence" data-asset="stocks,futures,options,currencies">
<i data-lucide="sparkles"></i>
<span>Intelligence</span>
</button>
<button class="nav-item" data-tab="macro" data-asset="stocks,futures">
<i data-lucide="globe-2"></i>
<span>Macro</span>
</button>
<div class="nav-group-label">Trading</div>
<button class="nav-item" data-tab="portfolio" data-asset="stocks,futures,options,currencies">
<i data-lucide="briefcase"></i>
<span>Portfolio</span>
</button>
</nav>
<div class="sidebar-bottom">
<button class="nav-item" id="themeToggle">
<i data-lucide="moon" id="themeIcon"></i>
<span id="themeLabel">Dark Mode</span>
</button>
<button class="nav-item" id="exportBtnSidebar" style="display:none;">
<i data-lucide="download"></i>
<span>Export Excel</span>
</button>
</div>
</aside>
<!-- ── MAIN CONTENT ────────────────────────────── -->
<main class="main-content">
<!-- Top Bar -->
<header class="topbar">
<button class="sidebar-toggle" id="sidebarToggle">
<i data-lucide="menu"></i>
</button>
<div class="search-container">
<!-- Asset Type Selector -->
<div class="asset-selector">
<button class="asset-btn active" data-asset="stocks">
<i data-lucide="bar-chart-2"></i> Stocks
</button>
<button class="asset-btn" data-asset="futures">
<i data-lucide="timer"></i> Futures
</button>
<button class="asset-btn" data-asset="options">
<i data-lucide="layers"></i> Options
</button>
<button class="asset-btn" data-asset="currencies">
<i data-lucide="coins"></i> Currencies
</button>
</div>
<!-- Search Input -->
<div class="search-input-group">
<i data-lucide="search" class="search-icon"></i>
<div class="ticker-wrapper">
<input type="text" id="tickerInput" placeholder="Search ticker (e.g. AAPL, TSLA, NIFTY)"
autocomplete="off">
<div class="dropdown" id="dropdown"></div>
</div>
<select id="timeframeSelect" class="select-control">
<option value="1Y">1 Year</option>
<option value="6M">6 Months</option>
<option value="3M">3 Months</option>
<option value="1M">1 Month</option>
<option value="5Y">5 Years</option>
<option value="custom">Custom</option>
</select>
<select id="periodSelect" class="select-control">
<option value="yearly">Annual</option>
<option value="quarterly">Quarterly</option>
</select>
<select id="currencySelect" class="select-control">
<option value="GBP">£ GBP</option>
<option value="USD">$ USD</option>
<option value="INR">₹ INR</option>
</select>
<button id="analyzeBtn" class="btn-primary">
<i data-lucide="zap" style="width:16px;height:16px;"></i>
Analyze
</button>
</div>
<div class="custom-dates" id="customDates" style="display:none;">
<label>From <input type="date" id="startDate"></label>
<label>To <input type="date" id="endDate"></label>
</div>
</div>
</header>
<!-- Loader -->
<div class="loader-overlay" id="loader" style="display:none">
<div class="loader-spinner"></div>
<p class="loader-text" id="loaderText">Analyzing...</p>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- TAB: OVERVIEW -->
<!-- ═══════════════════════════════════════════ -->
<div class="tab-page active" id="page-overview">
<!-- ── Market Dashboard ── -->
<div class="market-dashboard" id="marketDashboard">
<div class="dashboard-header">
<h2 class="dashboard-title">
<i data-lucide="globe" style="width:22px;height:22px;"></i>
Market Pulse
<span class="live-dot"></span>
</h2>
<span class="dashboard-subtitle">Live global market snapshot · Auto-refreshes every 60s</span>
</div>
<div class="dashboard-section">
<h3 class="dash-section-title"><i data-lucide="trending-up" style="width:16px;height:16px;"></i>
Global Indices</h3>
<div class="market-grid" id="dashIndices">
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
</div>
</div>
<div class="dashboard-section">
<h3 class="dash-section-title"><i data-lucide="gem" style="width:16px;height:16px;"></i>
Commodities</h3>
<div class="market-grid" id="dashCommodities">
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
</div>
</div>
<div class="dashboard-section">
<h3 class="dash-section-title"><i data-lucide="coins" style="width:16px;height:16px;"></i>
Currency Pairs</h3>
<div class="market-grid" id="dashCurrencies">
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
<div class="market-card skeleton-card">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
</div>
</div>
</div>
<div class="results">
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="candlestick-chart"></i>
Interactive Price Chart
</div>
<span class="info-icon"
data-tooltip="Candlestick chart with volume. Toggle overlays below to add EMA, SMA, Bollinger Bands, and Fibonacci levels.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="chart-controls" id="chartControls" style="display:none">
<label class="chart-toggle"><input type="checkbox" id="togEma5"> EMA 5</label>
<label class="chart-toggle"><input type="checkbox" id="togEma10"> EMA 10</label>
<label class="chart-toggle"><input type="checkbox" id="togEma20"> EMA 20</label>
<label class="chart-toggle"><input type="checkbox" id="togSma200"> SMA 200</label>
<label class="chart-toggle"><input type="checkbox" id="togBb"> Bollinger</label>
<label class="chart-toggle"><input type="checkbox" id="togFib"> Fibonacci</label>
<div class="custom-ema">
<input type="number" id="customEmaPeriod" placeholder="Custom EMA" min="2" max="500">
<button class="btn-sm" id="addCustomEma">Add</button>
</div>
</div>
<div id="chartContainer"
style="width:100%;height:420px;border-radius:var(--radius);overflow:hidden;"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="gauge"></i>
Key Metrics
</div>
<span class="info-icon"
data-tooltip="Essential price and valuation metrics. RSI above 70 = overbought, below 30 = oversold.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="data-grid" id="keyMetrics"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="zap"></i>
Quick Signal
</div>
<span class="info-icon"
data-tooltip="Algorithmic signal based on RSI, MACD, and moving average crossovers.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="quickSignal"></div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- TAB: TECHNICAL -->
<!-- ═══════════════════════════════════════════ -->
<div class="tab-page" id="page-technical">
<div class="results">
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="activity"></i>
Technical Indicators
</div>
<span class="info-icon"
data-tooltip="RSI (14-period), MACD trend direction, Bollinger Bands volatility measure.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="data-grid" id="technicalIndicators"></div>
</div>
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="scan-line"></i>
Candlestick Patterns — Last 7 Days
</div>
<span class="info-icon"
data-tooltip="Patterns detected in the last 7 trading days with forward-looking predictions.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="patternOutlook" class="pattern-outlook-box"></div>
<div id="candlestickPatterns" class="patterns-grid"></div>
</div>
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="calendar-check"></i>
Earnings History (EPS)
</div>
<span class="info-icon"
data-tooltip="Quarterly EPS — Actual vs Estimate. Consistent beats are bullish.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="earningsTable" class="table-container"></div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- TAB: FUNDAMENTALS -->
<!-- ═══════════════════════════════════════════ -->
<div class="tab-page" id="page-fundamentals">
<div class="results">
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="landmark"></i>
DCF Intrinsic Valuation
</div>
<span class="info-icon"
data-tooltip="DCF projects future FCF, discounts by WACC, and derives intrinsic value. Intrinsic > price = undervalued.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="dcfValuation"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="shield-check"></i>
Altman Z-Score
</div>
<span class="info-icon"
data-tooltip="Bankruptcy risk. Z > 2.99 = Safe, 1.81-2.99 = Grey Zone, < 1.81 = Distress.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="zScore"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="banknote"></i>
Dividend Analysis
</div>
<span class="info-icon"
data-tooltip="Dividend yield, payout ratio, and history. Payout > 80% may be unsustainable.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="dividendAnalysis"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="file-text"></i>
Income Statement
</div>
<span class="info-icon"
data-tooltip="Revenue, operating income, and net income from the latest filings.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="data-grid" id="incomeStatement"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="pie-chart"></i>
Ratios & Valuation
</div>
<span class="info-icon" data-tooltip="P/E, EV/EBITDA, ROE and other valuation multiples.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="data-grid" id="ratiosGrid"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="wallet"></i>
Free Cash Flow
</div>
<span class="info-icon"
data-tooltip="Cash available after CapEx. Strong FCF = flexibility for dividends and buybacks.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="data-grid" id="fcffGrid"></div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- TAB: SENTIMENT -->
<!-- ═══════════════════════════════════════════ -->
<div class="tab-page" id="page-sentiment">
<div class="results">
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="heart-pulse"></i>
Fear & Greed Index
</div>
<span class="info-icon"
data-tooltip="CNN Fear & Greed (0-100). Extreme Fear (<25) = buying opportunity.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="fearGreedGauge"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="triangle-alert"></i>
VIX Volatility Index
</div>
<span class="info-icon"
data-tooltip="VIX < 15 = calm, 15-25 = normal, 25-35 = elevated, > 35 = panic.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="vixDisplay"></div>
</div>
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="newspaper"></i>
News Sentiment (VADER)
</div>
<span class="info-icon"
data-tooltip="Headlines analyzed with VADER. Compound score -1 to +1.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="newsSection"></div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- TAB: INTELLIGENCE -->
<!-- ═══════════════════════════════════════════ -->
<div class="tab-page" id="page-intelligence">
<div class="results">
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="target"></i>
Analyst Ratings & Price Target
</div>
<span class="info-icon"
data-tooltip="Wall Street consensus from Finnhub. Buy vs Hold vs Sell counts and price target range.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="analystRatings"></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">
<i data-lucide="eye"></i>
Insider Trading Activity
</div>
<span class="info-icon"
data-tooltip="Recent SEC insider filings. Insider buying is often bullish.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="insiderTrading"></div>
</div>
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="trending-up"></i>
Monte Carlo Price Forecast (60-Day)
</div>
<span class="info-icon"
data-tooltip="1,000 random simulations based on historical volatility. Toggle percentile bands below.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="mc-band-controls" id="mcBandControls">
<label class="chart-toggle"><input type="checkbox" id="togP90" checked> P90 (Bull)</label>
<label class="chart-toggle"><input type="checkbox" id="togP75" checked> P75</label>
<label class="chart-toggle"><input type="checkbox" id="togP50" checked> P50
(Median)</label>
<label class="chart-toggle"><input type="checkbox" id="togP25" checked> P25</label>
<label class="chart-toggle"><input type="checkbox" id="togP10" checked> P10 (Bear)</label>
</div>
<div id="monteCarloChart"
style="width:100%;height:340px;border-radius:var(--radius);overflow:hidden;"></div>
<div id="monteCarloStats" class="mc-stats"></div>
</div>
<div class="card card-full card-ai">
<div class="card-header">
<div class="card-title">
<i data-lucide="sparkles"></i>
AI Analyst
<span class="badge-ai">Ollama</span>
</div>
<span class="info-icon"
data-tooltip="Local AI analysis via Ollama (Llama 3.1). Synthesizes all data for Buy/Hold/Sell.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="ai-content" id="aiContent">
<p class="ai-placeholder">Run analysis first, then AI commentary will appear here.</p>
</div>
<div class="ai-actions">
<input class="ai-input" id="aiInput" placeholder="Ask a follow-up question...">
<button class="btn-primary btn-sm" id="askAi">
<i data-lucide="send" style="width:14px;height:14px;"></i> Ask
</button>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- TAB: MACRO -->
<!-- ═══════════════════════════════════════════ -->
<div class="tab-page" id="page-macro">
<div class="results">
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="landmark"></i>
Economic Indicators (FRED)
</div>
<span class="info-icon"
data-tooltip="Live macro data from the Federal Reserve. Hover to see impact.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="data-grid" id="macroIndicators"></div>
</div>
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="grid-3x3"></i>
Sector Performance Heatmap
</div>
<span class="info-icon"
data-tooltip="S&P 500 sector performance. Green = outperforming, Red = underperforming.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div class="heatmap-controls" id="heatmapControls"></div>
<div id="sectorHeatmap" class="heatmap-grid"></div>
</div>
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="link"></i>
Correlation Matrix
</div>
<span class="info-icon"
data-tooltip="Daily return correlation with benchmarks (SPY, QQQ, DIA, Gold, Bonds, VIX).">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="correlationMatrix" class="corr-matrix"></div>
</div>
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="building-2"></i>
Competitor Analysis
</div>
<span class="info-icon"
data-tooltip="Compares key ratios with sector peers. Analyzed stock is highlighted.">
<i data-lucide="info" style="width:14px;height:14px;"></i>
</span>
</div>
<div id="compSection" class="comp-container"></div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- TAB: OPTIONS -->
<!-- ═══════════════════════════════════════════ -->
<div class="tab-page" id="page-options">
<div class="results">
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="layers"></i>
Options Chain
</div>
<div class="options-controls">
<select id="optExpiry" class="select-control"></select>
<button class="btn-sm btn-primary" id="loadChainBtn">Load Chain</button>
</div>
</div>
<div class="options-toggle">
<button class="heatmap-btn active" data-chain="calls">Calls</button>
<button class="heatmap-btn" data-chain="puts">Puts</button>
</div>
<div id="optionsChainTable" class="table-container"></div>
</div>
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="trending-up"></i>
Options Payoff Diagram
</div>
</div>
<div class="payoff-controls">
<label>Strike: <input type="number" id="payoffStrike" class="input-sm" value="100"></label>
<label>Premium: <input type="number" id="payoffPremium" class="input-sm" value="5"
step="0.5"></label>
<select id="payoffType" class="select-control">
<option value="call">Call</option>
<option value="put">Put</option>
</select>
<select id="payoffDirection" class="select-control">
<option value="long">Long</option>
<option value="short">Short</option>
</select>
<button class="btn-sm btn-primary" id="drawPayoffBtn">Draw</button>
</div>
<div id="payoffChart" style="width:100%;height:300px;"></div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- TAB: PORTFOLIO -->
<!-- ═══════════════════════════════════════════ -->
<div class="tab-page" id="page-portfolio">
<div class="results">
<!-- Portfolio Summary -->
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="briefcase"></i>
Portfolio Summary
<span class="live-dot"></span>
</div>
<div style="display:flex;gap:8px;align-items:center;">
<span class="badge-sm" id="pfSlippageLabel" title="Slippage">Slip: 5 bps</span>
<span class="badge-sm" id="pfCommLabel" title="Commission">Comm: $0.005/sh</span>
<button class="btn-sm btn-danger" id="resetPortfolioBtn" title="Reset to $100K">
<i data-lucide="rotate-ccw" style="width:14px;height:14px;"></i> Reset
</button>
</div>
</div>
<div class="portfolio-summary-grid" id="portfolioSummary">
<div class="data-item">
<div class="data-label">Total Value</div>
<div class="data-value" id="pfTotalValue">$100,000.00</div>
</div>
<div class="data-item">
<div class="data-label">Cash</div>
<div class="data-value" id="pfCash">$100,000.00</div>
</div>
<div class="data-item">
<div class="data-label">Buying Power</div>
<div class="data-value" id="pfBuyingPower">$100,000.00</div>
</div>
<div class="data-item">
<div class="data-label">Positions</div>
<div class="data-value" id="pfPositionsValue">$0.00</div>
</div>
<div class="data-item">
<div class="data-label">Margin Used</div>
<div class="data-value" id="pfMarginUsed">$0.00</div>
</div>
<div class="data-item">
<div class="data-label">P&L</div>
<div class="data-value" id="pfPnL">$0.00</div>
</div>
<div class="data-item">
<div class="data-label">Return</div>
<div class="data-value" id="pfReturnPct">0.00%</div>
</div>
</div>
</div>
<!-- Equity Curve -->
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="line-chart"></i>
Equity Curve
</div>
</div>
<div id="equityCurveContainer"
style="width:100%;height:260px;border-radius:var(--radius);overflow:hidden;"></div>
</div>
<!-- Trade Form -->
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="arrow-left-right"></i>
Trade Terminal
</div>
</div>
<div class="trade-form">
<div class="trade-search-wrap">
<input type="text" id="tradeTicker" placeholder="Search ticker..." class="input-sm"
autocomplete="off">
<div class="trade-dropdown" id="tradeDropdown"></div>
</div>
<input type="number" id="tradeShares" placeholder="Qty" min="0.01" step="0.01"
class="input-sm" style="width:90px;">
<select id="tradeOrderType" class="input-sm" style="width:110px;">
<option value="market">Market</option>
<option value="limit">Limit</option>
<option value="stop">Stop</option>
</select>
<input type="number" id="tradeLimitPrice" placeholder="Price" step="0.01" class="input-sm"
style="width:100px;display:none;">
<select id="tradeAssetType" class="input-sm" style="width:100px;">
<option value="stock">Stock</option>
<option value="futures">Futures</option>
<option value="currency">Currency</option>
</select>
<div class="trade-btn-group">
<button class="btn-trade btn-buy" id="buyBtn">Buy Long</button>
<button class="btn-trade btn-sell" id="sellBtn">Sell</button>
<button class="btn-trade btn-short" id="shortBtn">Short</button>
<button class="btn-trade btn-cover" id="coverBtn">Cover</button>
</div>
</div>
<div id="tradeResult" class="trade-result"></div>
</div>
<!-- Pending Orders -->
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="clock"></i>
Pending Orders
</div>
</div>
<div id="pendingOrdersTable" class="table-container"></div>
</div>
<!-- Positions Table -->
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="list"></i>
Positions
</div>
</div>
<div id="positionsTable" class="table-container"></div>
</div>
<!-- Analytics -->
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="bar-chart-3"></i>
Performance Analytics
</div>
</div>
<div class="portfolio-analytics-grid" id="portfolioAnalytics"></div>
</div>
<!-- Transaction History -->
<div class="card card-full">
<div class="card-header">
<div class="card-title">
<i data-lucide="scroll-text"></i>
Transaction History
</div>
</div>
<div id="transactionHistory" class="table-container"></div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="footer">
<p>FinanceIQ v5.2 · FMP · Finnhub · FRED · Alpha Vantage · Ollama AI · Paper Trading · Educational
purposes only</p>
</footer>
</main>
</div>
<script src="/static/script.js"></script>
<script>lucide.createIcons();</script>
</body>
</html>

19
test_intraday.py Normal file
View File

@ -0,0 +1,19 @@
from financetoolkit import Toolkit
API_KEY = "wybWEsp1oB9abHfz3yPpQYwffxaN21B7"
TICKER = "NVDA"
try:
companies = Toolkit(tickers=[TICKER], api_key=API_KEY)
# Try fetching intraday
print("Attempting to fetch 1min data...")
# Note: verify correct method for intraday in financetoolkit
# Usually get_intraday_data or similar, or interval argument in get_historical_data
# Looking at docs/usage from memory or previous files:
# get_historical_data(period="daily") was used.
# Let's try interval='1m' if supported.
intra = companies.get_historical_data(period="1m") # Guessing '1m' is period
print(intra.head())
print("Success!")
except Exception as e:
print(f"Failed: {e}")

27
test_news.py Normal file
View File

@ -0,0 +1,27 @@
import feedparser
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
try:
# Test Feed: Google News for AAPL
rss_url = "https://news.google.com/rss/search?q=AAPL&hl=en-US&gl=US&ceid=US:en"
print(f"Fetching RSS from: {rss_url}")
feed = feedparser.parse(rss_url)
# feedparser doesn't always return .status for local files or some responses, but usually does for HTTP
# We check if entries are present
print(f"Entries found: {len(feed.entries)}")
if feed.entries:
title = feed.entries[0].title
print(f"Sample Title: {title}")
analyzer = SentimentIntensityAnalyzer()
score = analyzer.polarity_scores(title)
print(f"Sentiment: {score}")
print("✅ News + Sentiment Test PASSED")
else:
print("❌ No entries found in RSS feed")
except Exception as e:
print(f"❌ Test FAILED with error: {e}")