-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Open
Description
Nifty 50 Options Trading Assistant - PWA App
Complete Implementation Guide
1. Project Structure
nifty-options-pwa/
├── index.html
├── css/
│ └── styles.css
├── js/
│ ├── app.js
│ ├── calculator.js
│ ├── signals.js
│ ├── storage.js
│ └── ui.js
├── manifest.json
├── service-worker.js
└── README.md
2. HTML (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Nifty 50 Options Trading Assistant - AI-Powered Entry/Exit Tips">
<meta name="theme-color" content="#1a73e8">
<title>Nifty 50 Options Trading Assistant</title>
<link rel="manifest" href="manifest.json">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 192 192'><rect fill='%231a73e8' width='192' height='192'/><text x='50%' y='50%' font-size='100' fill='white' text-anchor='middle' dy='.3em'>N50</text></svg>">
<link rel="apple-touch-icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 192 192'><rect fill='%231a73e8' width='192' height='192'/><text x='50%' y='50%' font-size='100' fill='white' text-anchor='middle' dy='.3em'>N50</text></svg>">
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>📈 Nifty 50 Options Assistant</h1>
<p class="subtitle">Entry/Exit Tips & Greeks Calculator</p>
</header>
<!-- Navigation Tabs -->
<div class="tabs">
<button class="tab-btn active" data-tab="dashboard">Dashboard</button>
<button class="tab-btn" data-tab="input">Input Data</button>
<button class="tab-btn" data-tab="calculator">Calculator</button>
<button class="tab-btn" data-tab="signals">Signals</button>
<button class="tab-btn" data-tab="history">History</button>
<button class="tab-btn" data-tab="settings">Settings</button>
</div>
<!-- Dashboard Tab -->
<section id="dashboard" class="tab-content active">
<div class="dashboard-grid">
<div class="card">
<h3>Current Status</h3>
<div id="status-display" class="status-box">
<p>Nifty Spot: <span id="status-spot">-</span></p>
<p>Option Strike: <span id="status-strike">-</span></p>
<p>Premium: <span id="status-premium">-</span></p>
<p>Delta: <span id="status-delta">-</span></p>
</div>
</div>
<div class="card">
<h3>Next Action</h3>
<div id="signal-display" class="signal-box">
<p id="signal-text" class="signal-neutral">� No data. Enter market data to begin.</p>
</div>
</div>
<div class="card">
<h3>Entry/Exit Targets</h3>
<div id="targets-display" class="targets-box">
<p>Entry: <span id="target-entry">-</span></p>
<p>Target: <span id="target-target">-</span></p>
<p>Stop Loss: <span id="target-sl">-</span></p>
<p>Risk:Reward: <span id="target-rr">-</span></p>
</div>
</div>
<div class="card">
<h3>Quick Actions</h3>
<div class="action-buttons">
<button id="btn-refresh-data" class="btn btn-primary">🔄 Refresh Data</button>
<button id="btn-export-data" class="btn btn-secondary">📥 Export</button>
</div>
</div>
</div>
</section>
<!-- Input Data Tab -->
<section id="input" class="tab-content">
<div class="card">
<h3>Enter Market Data</h3>
<form id="market-form">
<div class="form-group">
<label>Nifty 50 Spot Price</label>
<input type="number" id="input-spot" step="0.05" placeholder="e.g., 25843.15" required>
</div>
<div class="form-group">
<label>Strike Price</label>
<input type="number" id="input-strike" step="50" placeholder="e.g., 26150" required>
</div>
<div class="form-group">
<label>Option Type</label>
<select id="input-type" required>
<option value="CE">Call (CE)</option>
<option value="PE">Put (PE)</option>
</select>
</div>
<div class="form-group">
<label>Current Premium</label>
<input type="number" id="input-premium" step="0.05" placeholder="e.g., 51.90" required>
</div>
<div class="form-group">
<label>Delta</label>
<input type="number" id="input-delta" step="0.01" min="0" max="1" placeholder="e.g., 0.25" required>
</div>
<div class="form-group">
<label>Implied Volatility (%)</label>
<input type="number" id="input-iv" step="0.1" placeholder="e.g., 9.0" required>
</div>
<div class="form-group">
<label>Days to Expiry</label>
<input type="number" id="input-dte" step="1" min="1" placeholder="e.g., 7" required>
</div>
<div class="form-group">
<label>Open Interest (Optional)</label>
<input type="number" id="input-oi" step="1" placeholder="e.g., 500000">
</div>
<button type="submit" class="btn btn-primary btn-block">💾 Save & Analyze</button>
</form>
</div>
</section>
<!-- Calculator Tab -->
<section id="calculator" class="tab-content">
<div class="card">
<h3>Greeks & Price Calculator</h3>
<form id="calc-form">
<div class="form-group">
<label>Nifty Move (points)</label>
<input type="number" id="calc-move" step="1" placeholder="e.g., 100" value="0">
</div>
<button type="button" id="btn-calculate" class="btn btn-primary btn-block">🔢 Calculate New Premium</button>
</form>
<div id="calc-results" class="calculation-results" style="display:none;">
<h4>Calculation Results</h4>
<p>New Premium: <span id="calc-new-premium">-</span></p>
<p>Premium Change: <span id="calc-premium-change">-</span></p>
<p>Expected P&L (per lot): <span id="calc-pnl">-</span> (75 contracts)</p>
<p>New Delta: <span id="calc-new-delta">-</span> (estimated)</p>
<p>Breakeven Price: <span id="calc-breakeven">-</span></p>
</div>
</div>
<div class="card">
<h3>Scenario Analysis</h3>
<table id="scenario-table" class="scenario-table">
<thead>
<tr>
<th>Nifty Move</th>
<th>New Premium</th>
<th>P&L (per lot)</th>
<th>Signal</th>
</tr>
</thead>
<tbody id="scenario-tbody">
</tbody>
</table>
</div>
</section>
<!-- Signals Tab -->
<section id="signals" class="tab-content">
<div class="card">
<h3>Trading Signals & AI Tips</h3>
<div id="signals-container" class="signals-container">
<p class="text-neutral">Enter data to generate signals.</p>
</div>
</div>
<div class="card">
<h3>Signal Rules Configuration</h3>
<form id="rules-form">
<div class="form-group">
<label>Entry Delta Threshold (Min)</label>
<input type="number" id="rule-delta-min" step="0.05" min="0" max="1" placeholder="e.g., 0.20" value="0.20">
</div>
<div class="form-group">
<label>Entry Delta Threshold (Max)</label>
<input type="number" id="rule-delta-max" step="0.05" min="0" max="1" placeholder="e.g., 0.60" value="0.60">
</div>
<div class="form-group">
<label>Profit Target (%)</label>
<input type="number" id="rule-target-pct" step="5" placeholder="e.g., 30" value="30">
</div>
<div class="form-group">
<label>Stop Loss (%)</label>
<input type="number" id="rule-sl-pct" step="5" placeholder="e.g., 15" value="15">
</div>
<button type="submit" class="btn btn-primary btn-block">💾 Save Rules</button>
</form>
</div>
</section>
<!-- History Tab -->
<section id="history" class="tab-content">
<div class="card">
<h3>Trade History & Analysis</h3>
<div id="history-container" class="history-container">
<p class="text-neutral">No trades recorded yet.</p>
</div>
<button id="btn-clear-history" class="btn btn-danger">🗑� Clear History</button>
</div>
</section>
<!-- Settings Tab -->
<section id="settings" class="tab-content">
<div class="card">
<h3>App Settings</h3>
<form id="settings-form">
<div class="form-group">
<label>
<input type="checkbox" id="setting-notifications" checked>
Enable Browser Notifications
</label>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="setting-dark-mode">
Dark Mode
</label>
</div>
<div class="form-group">
<label>Default Option Type</label>
<select id="setting-default-type">
<option value="CE">Call (CE)</option>
<option value="PE">Put (PE)</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-block">💾 Save Settings</button>
</form>
</div>
<div class="card">
<h3>Data Management</h3>
<button id="btn-export-all" class="btn btn-secondary btn-block">📤 Export All Data</button>
<button id="btn-import-data" class="btn btn-secondary btn-block">📥 Import Data</button>
<input type="file" id="import-file" accept=".json" style="display:none;">
<button id="btn-reset-app" class="btn btn-danger btn-block">🔄 Reset App</button>
</div>
<div class="card">
<h3>About</h3>
<p><strong>Nifty 50 Options Trading Assistant</strong></p>
<p>Version: 1.0.0</p>
<p>A lightweight PWA for options traders with no API dependency.</p>
<p>Works offline with local data storage.</p>
</div>
</section>
</div>
<!-- Notification -->
<div id="notification" class="notification"></div>
<!-- Scripts -->
<script src="js/storage.js"></script>
<script src="js/calculator.js"></script>
<script src="js/signals.js"></script>
<script src="js/ui.js"></script>
<script src="js/app.js"></script>
<!-- Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js').then(reg => {
console.log('Service Worker registered:', reg);
}).catch(err => {
console.log('Service Worker registration failed:', err);
});
}
</script>
</body>
</html>
3. CSS (css/styles.css)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #1a73e8;
--success-color: #34a853;
--warning-color: #fbbc04;
--danger-color: #ea4335;
--bg-color: #ffffff;
--text-color: #202124;
--border-color: #dadce0;
--card-shadow: 0 1px 2px 0 rgba(60,64,67,.3), 0 1px 3px 1px rgba(60,64,67,.15);
}
body.dark-mode {
--bg-color: #202124;
--text-color: #e8eaed;
--border-color: #5f6368;
--card-shadow: 0 1px 2px 0 rgba(0,0,0,.3), 0 1px 3px 1px rgba(0,0,0,.15);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
transition: all 0.3s ease;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px 0;
background: linear-gradient(135deg, var(--primary-color) 0%, #1557b0 100%);
color: white;
border-radius: 10px;
}
header h1 {
font-size: 28px;
margin-bottom: 5px;
}
header .subtitle {
font-size: 14px;
opacity: 0.9;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
border-bottom: 2px solid var(--border-color);
}
.tab-btn {
padding: 12px 20px;
background: none;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: var(--text-color);
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
}
.tab-btn:hover {
color: var(--primary-color);
}
.tab-btn.active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.card {
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
box-shadow: var(--card-shadow);
}
.card h3 {
font-size: 16px;
margin-bottom: 15px;
color: var(--primary-color);
}
.card h4 {
font-size: 14px;
margin-bottom: 10px;
}
.status-box, .signal-box, .targets-box {
background: var(--border-color);
padding: 15px;
border-radius: 6px;
font-size: 14px;
line-height: 1.8;
}
.status-box p, .targets-box p {
display: flex;
justify-content: space-between;
}
.signal-box {
text-align: center;
font-size: 16px;
font-weight: 500;
}
.signal-buy {
color: var(--success-color);
}
.signal-sell {
color: var(--danger-color);
}
.signal-hold {
color: var(--warning-color);
}
.signal-neutral {
color: var(--text-color);
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
font-size: 14px;
}
.form-group input,
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 14px;
background-color: var(--bg-color);
color: var(--text-color);
transition: border-color 0.3s ease;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1);
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: #1557b0;
box-shadow: var(--card-shadow);
}
.btn-secondary {
background-color: var(--border-color);
color: var(--text-color);
}
.btn-secondary:hover {
background-color: #dadce0;
}
.btn-danger {
background-color: var(--danger-color);
color: white;
}
.btn-danger:hover {
background-color: #d33627;
}
.btn-block {
width: 100%;
display: block;
margin-top: 10px;
}
.action-buttons {
display: flex;
gap: 10px;
}
.action-buttons .btn {
flex: 1;
}
.calculation-results {
background: var(--border-color);
padding: 15px;
border-radius: 6px;
margin-top: 15px;
}
.calculation-results p {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
}
.scenario-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
font-size: 13px;
}
.scenario-table th,
.scenario-table td {
padding: 10px;
text-align: right;
border-bottom: 1px solid var(--border-color);
}
.scenario-table th {
background-color: var(--border-color);
font-weight: 600;
}
.scenario-table td:first-child,
.scenario-table th:first-child {
text-align: left;
}
.signals-container {
display: flex;
flex-direction: column;
gap: 10px;
}
.signal-item {
background: var(--border-color);
padding: 12px;
border-radius: 6px;
border-left: 4px solid var(--primary-color);
}
.signal-item.bullish {
border-left-color: var(--success-color);
}
.signal-item.bearish {
border-left-color: var(--danger-color);
}
.signal-item.neutral {
border-left-color: var(--warning-color);
}
.signal-item strong {
display: block;
margin-bottom: 5px;
}
.history-container {
max-height: 400px;
overflow-y: auto;
}
.history-item {
background: var(--border-color);
padding: 12px;
border-radius: 6px;
margin-bottom: 10px;
font-size: 13px;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
background: var(--success-color);
color: white;
border-radius: 6px;
box-shadow: var(--card-shadow);
display: none;
z-index: 1000;
animation: slideIn 0.3s ease;
}
.notification.show {
display: block;
}
.notification.error {
background: var(--danger-color);
}
.notification.warning {
background: var(--warning-color);
color: var(--text-color);
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
header h1 {
font-size: 20px;
}
.tabs {
flex-direction: column;
}
.tab-btn {
border-bottom: none;
border-radius: 6px;
background: var(--border-color);
margin-bottom: 5px;
}
.tab-btn.active {
background: var(--primary-color);
color: white;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
}
4. JavaScript - Storage Module (js/storage.js)
class TradeStorage {
constructor() {
this.storageKey = 'nifty-options-data';
this.historyKey = 'nifty-trade-history';
this.rulesKey = 'nifty-trading-rules';
this.settingsKey = 'nifty-app-settings';
}
saveMarketData(data) {
const timestamp = new Date().toISOString();
const dataWithTime = { ...data, timestamp };
localStorage.setItem(this.storageKey, JSON.stringify(dataWithTime));
return dataWithTime;
}
getMarketData() {
const data = localStorage.getItem(this.storageKey);
return data ? JSON.parse(data) : null;
}
addToHistory(trade) {
const history = this.getHistory();
history.push({ ...trade, id: Date.now() });
localStorage.setItem(this.historyKey, JSON.stringify(history));
return history;
}
getHistory() {
const data = localStorage.getItem(this.historyKey);
return data ? JSON.parse(data) : [];
}
clearHistory() {
localStorage.removeItem(this.historyKey);
}
saveRules(rules) {
localStorage.setItem(this.rulesKey, JSON.stringify(rules));
return rules;
}
getRules() {
const data = localStorage.getItem(this.rulesKey);
return data ? JSON.parse(data) : {
deltaMin: 0.20,
deltaMax: 0.60,
targetPct: 30,
slPct: 15
};
}
saveSettings(settings) {
localStorage.setItem(this.settingsKey, JSON.stringify(settings));
return settings;
}
getSettings() {
const data = localStorage.getItem(this.settingsKey);
return data ? JSON.parse(data) : {
notifications: true,
darkMode: false,
defaultType: 'CE'
};
}
exportData() {
return {
market: this.getMarketData(),
history: this.getHistory(),
rules: this.getRules(),
settings: this.getSettings(),
exportTime: new Date().toISOString()
};
}
importData(jsonData) {
const data = JSON.parse(jsonData);
if (data.market) this.saveMarketData(data.market);
if (data.history) localStorage.setItem(this.historyKey, JSON.stringify(data.history));
if (data.rules) this.saveRules(data.rules);
if (data.settings) this.saveSettings(data.settings);
return true;
}
reset() {
localStorage.removeItem(this.storageKey);
localStorage.removeItem(this.historyKey);
localStorage.removeItem(this.rulesKey);
localStorage.removeItem(this.settingsKey);
}
}
// Initialize global storage
const storage = new TradeStorage();
5. JavaScript - Calculator Module (js/calculator.js)
class OptionsCalculator {
constructor() {
this.riskFreeRate = 0.06; // Assumed 6% annual
this.dividendYield = 0.015; // Assumed 1.5% dividend yield
this.contractSize = 75; // Nifty contracts are 75 units per lot
}
// Black-Scholes Option Pricing Model (simplified)
blackScholes(spot, strike, dte, iv, optionType = 'CE') {
const daysPerYear = 365;
const t = dte / daysPerYear;
const r = this.riskFreeRate;
const q = this.dividendYield;
const sigma = iv / 100;
const d1 = (Math.log(spot / strike) + (r - q + 0.5 * sigma * sigma) * t) / (sigma * Math.sqrt(t));
const d2 = d1 - sigma * Math.sqrt(t);
const N_d1 = this.normCDF(d1);
const N_d2 = this.normCDF(d2);
const N_minus_d1 = this.normCDF(-d1);
const N_minus_d2 = this.normCDF(-d2);
let price;
if (optionType === 'CE') {
price = spot * Math.exp(-q * t) * N_d1 - strike * Math.exp(-r * t) * N_d2;
} else {
price = strike * Math.exp(-r * t) * N_minus_d2 - spot * Math.exp(-q * t) * N_minus_d1;
}
return Math.max(price, 0);
}
// Cumulative Normal Distribution Function
normCDF(x) {
const a1 = 0.254829592;
const a2 = -0.284496736;
const a3 = 1.421413741;
const a4 = -1.453152027;
const a5 = 1.061405429;
const p = 0.3275911;
const sign = x < 0 ? -1 : 1;
x = Math.abs(x) / Math.sqrt(2);
const t = 1.0 / (1.0 + p * x);
const t2 = t * t;
const t3 = t2 * t;
const t4 = t3 * t;
const t5 = t4 * t;
const y = 1.0 - (((((a5 * t5 + a4 * t4) + a3 * t3) + a2 * t2) + a1 * t) * t * Math.exp(-x * x));
return 0.5 * (1.0 + sign * y);
}
// Calculate Delta
calculateDelta(spot, strike, dte, iv, optionType = 'CE') {
const daysPerYear = 365;
const t = dte / daysPerYear;
const r = this.riskFreeRate;
const q = this.dividendYield;
const sigma = iv / 100;
const d1 = (Math.log(spot / strike) + (r - q + 0.5 * sigma * sigma) * t) / (sigma * Math.sqrt(t));
if (optionType === 'CE') {
return Math.exp(-q * t) * this.normCDF(d1);
} else {
return Math.exp(-q * t) * (this.normCDF(d1) - 1);
}
}
// Calculate Gamma
calculateGamma(spot, strike, dte, iv) {
const daysPerYear = 365;
const t = dte / daysPerYear;
const q = this.dividendYield;
const sigma = iv / 100;
const d1 = (Math.log(spot / strike) + (this.riskFreeRate - q + 0.5 * sigma * sigma) * t) / (sigma * Math.sqrt(t));
return (Math.exp(-q * t) * this.normPDF(d1)) / (spot * sigma * Math.sqrt(t));
}
// Calculate Vega
calculateVega(spot, strike, dte, iv) {
const daysPerYear = 365;
const t = dte / daysPerYear;
const q = this.dividendYield;
const sigma = iv / 100;
const d1 = (Math.log(spot / strike) + (this.riskFreeRate - q + 0.5 * sigma * sigma) * t) / (sigma * Math.sqrt(t));
return spot * Math.exp(-q * t) * this.normPDF(d1) * Math.sqrt(t) / 100;
}
// Normal PDF
normPDF(x) {
return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);
}
// Estimate new premium based on delta and nifty move
estimateNewPremium(currentPremium, delta, niftyMove, gamma = 0) {
// First order approximation: premium change = delta * move + 0.5 * gamma * move^2
const deltaChange = delta * niftyMove;
const gammaChange = 0.5 * gamma * niftyMove * niftyMove;
const newPremium = currentPremium + deltaChange + gammaChange;
return Math.max(newPremium, 0);
}
// Calculate P&L
calculatePnL(entryPrice, exitPrice, quantity = 1, contractSize = 75) {
const priceDiff = exitPrice - entryPrice;
return priceDiff * quantity * contractSize;
}
// Calculate breakeven price
calculateBreakeven(currentPremium, spot, strike, optionType = 'CE') {
if (optionType === 'CE') {
return strike + currentPremium;
} else {
return strike - currentPremium;
}
}
// Implied Volatility Range Analysis
ivPercentile(currentIV, historicalIVs = []) {
if (historicalIVs.length === 0) return 0.5;
const lowerCount = historicalIVs.filter(iv => iv < currentIV).length;
return lowerCount / historicalIVs.length;
}
}
const calculator = new OptionsCalculator();
6. JavaScript - Signals Module (js/signals.js)
class SignalGenerator {
constructor() {
this.signals = [];
}
generateSignals(marketData, rules) {
this.signals = [];
const { spot, strike, premium, delta, iv, dte, optionType } = marketData;
// Signal 1: Delta-based Entry
if (delta >= rules.deltaMin && delta <= rules.deltaMax) {
this.signals.push({
type: 'entry',
confidence: this.calculateConfidence(delta, rules),
message: `✅ ENTRY SIGNAL: Delta ${delta.toFixed(2)} in ideal range [${rules.deltaMin}-${rules.deltaMax}]`,
signal: 'BUY'
});
} else if (delta < rules.deltaMin) {
this.signals.push({
type: 'wait',
confidence: 0.3,
message: `� WAIT: Delta ${delta.toFixed(2)} too low. Good for rich premium selling but risky for buying.`,
signal: 'NEUTRAL'
});
}
// Signal 2: IV Analysis
const ivPercentile = this.analyzeIV(iv);
if (ivPercentile > 0.75) {
this.signals.push({
type: 'volatility',
confidence: 0.7,
message: `⚠� HIGH IV (${iv}): Consider selling options or widening stops.`,
signal: optionType === 'CE' ? 'SELL' : 'BUY'
});
} else if (ivPercentile < 0.25) {
this.signals.push({
type: 'volatility',
confidence: 0.7,
message: `📈 LOW IV (${iv}): Good buying opportunity. IV likely to expand.`,
signal: optionType === 'CE' ? 'BUY' : 'SELL'
});
}
// Signal 3: Time Decay Analysis
if (dte <= 2) {
this.signals.push({
type: 'expiry',
confidence: 0.8,
message: `🔴 EXPIRY NEAR (${dte} days): High theta decay. Square off or roll.`,
signal: 'HOLD/EXIT'
});
} else if (dte <= 7) {
this.signals.push({
type: 'expiry',
confidence: 0.6,
message: `🟡 EXPIRY WEEK: Moderate theta decay. Monitor closely.`,
signal: 'HOLD'
});
}
// Signal 4: ATM or OTM Analysis
const moneyness = spot / strike;
if (Math.abs(Math.log(moneyness)) < 0.01) {
this.signals.push({
type: 'moneyness',
confidence: 0.75,
message: `🎯 ATM STRIKE: Maximum theta decay and gamma. High risk/reward.`,
signal: optionType === 'CE' ? 'NEUTRAL' : 'NEUTRAL'
});
}
// Signal 5: Premium Value
const premiumVsStrike = (premium / strike) * 100;
if (premiumVsStrike < 0.5) {
this.signals.push({
type: 'premium',
confidence: 0.6,
message: `💰 CHEAP: Premium only ${premiumVsStrike.toFixed(2)}% of strike. Good value.`,
signal: 'BUY'
});
} else if (premiumVsStrike > 3) {
this.signals.push({
type: 'premium',
confidence: 0.7,
message: `🤑 EXPENSIVE: Premium ${premiumVsStrike.toFixed(2)}% of strike. Consider taking profits.`,
signal: 'SELL'
});
}
return this.signals;
}
calculateConfidence(delta, rules) {
const midpoint = (rules.deltaMin + rules.deltaMax) / 2;
const range = (rules.deltaMax - rules.deltaMin) / 2;
return 1 - (Math.abs(delta - midpoint) / range);
}
analyzeIV(iv) {
// Simple IV percentile: assuming 5-25 is the normal range for Nifty
const min = 5, max = 25;
const percentile = Math.max(0, Math.min(1, (iv - min) / (max - min)));
return percentile;
}
getOverallSignal(signals) {
if (signals.length === 0) return 'NEUTRAL';
const buySignals = signals.filter(s => s.signal === 'BUY').length;
const sellSignals = signals.filter(s => s.signal === 'SELL').length;
const holdSignals = signals.filter(s => s.signal === 'HOLD' || s.signal === 'NEUTRAL').length;
if (buySignals > sellSignals && buySignals > holdSignals) return 'BUY';
if (sellSignals > buySignals && sellSignals > holdSignals) return 'SELL';
return 'HOLD';
}
generateTargetsAndStops(marketData, rules) {
const { premium, spot, strike, optionType } = marketData;
const targetPct = rules.targetPct / 100;
const slPct = rules.slPct / 100;
const targetPrice = premium * (1 + targetPct);
const stopPrice = premium * (1 - slPct);
return {
entry: premium,
target: Math.max(targetPrice, 1),
stopLoss: Math.max(stopPrice, 0.05),
riskReward: (targetPrice - premium) / (premium - stopPrice)
};
}
}
const signalGen = new SignalGenerator();
7. JavaScript - UI Module (js/ui.js)
class UIManager {
constructor() {
this.setupEventListeners();
}
setupEventListeners() {
// Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => this.switchTab(e.target.dataset.tab));
});
// Market data form
document.getElementById('market-form').addEventListener('submit', (e) => {
e.preventDefault();
this.handleMarketDataSubmit();
});
// Calculator form
document.getElementById('calc-form').addEventListener('submit', (e) => {
e.preventDefault();
});
document.getElementById('btn-calculate').addEventListener('click', () => this.handleCalculate());
// Rules form
document.getElementById('rules-form').addEventListener('submit', (e) => {
e.preventDefault();
this.handleRulesSubmit();
});
// Settings form
document.getElementById('settings-form').addEventListener('submit', (e) => {
e.preventDefault();
this.handleSettingsSubmit();
});
// Buttons
document.getElementById('btn-refresh-data').addEventListener('click', () => this.refreshDashboard());
document.getElementById('btn-export-data').addEventListener('click', () => this.exportData());
document.getElementById('btn-clear-history').addEventListener('click', () => this.clearHistory());
document.getElementById('btn-export-all').addEventListener('click', () => this.exportAllData());
document.getElementById('btn-import-data').addEventListener('click', () => {
document.getElementById('import-file').click();
});
document.getElementById('import-file').addEventListener('change', (e) => this.handleImport(e));
document.getElementById('btn-reset-app').addEventListener('click', () => this.resetApp());
// Dark mode
document.getElementById('setting-dark-mode').addEventListener('change', (e) => {
document.body.classList.toggle('dark-mode', e.target.checked);
});
// Load saved settings
this.loadSettings();
this.refreshDashboard();
}
switchTab(tabName) {
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.getElementById(tabName).classList.add('active');
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
}
handleMarketDataSubmit() {
const marketData = {
spot: parseFloat(document.getElementById('input-spot').value),
strike: parseFloat(document.getElementById('input-strike').value),
optionType: document.getElementById('input-type').value,
premium: parseFloat(document.getElementById('input-premium').value),
delta: parseFloat(document.getElementById('input-delta').value),
iv: parseFloat(document.getElementById('input-iv').value),
dte: parseInt(document.getElementById('input-dte').value),
oi: parseInt(document.getElementById('input-oi').value) || 0
};
storage.saveMarketData(marketData);
this.showNotification('✅ Market data saved!', 'success');
this.refreshDashboard();
// Generate and show signals
const rules = storage.getRules();
const signals = signalGen.generateSignals(marketData, rules);
this.displaySignals(signals);
}
handleCalculate() {
const marketData = storage.getMarketData();
if (!marketData) {
this.showNotification('Please enter market data first.', 'error');
return;
}
const move = parseFloat(document.getElementById('calc-move').value);
const newPremium = calculator.estimateNewPremium(marketData.premium, marketData.delta, move);
const premiumChange = newPremium - marketData.premium;
const pnl = calculator.calculatePnL(marketData.premium, newPremium, 1, 75);
const resultsDiv = document.getElementById('calc-results');
resultsDiv.style.display = 'block';
document.getElementById('calc-new-premium').textContent = newPremium.toFixed(2);
document.getElementById('calc-premium-change').textContent = premiumChange.toFixed(2);
document.getElementById('calc-pnl').textContent = '₹' + pnl.toFixed(0);
document.getElementById('calc-new-delta').textContent = (marketData.delta + 0.02 * move / 100).toFixed(3);
document.getElementById('calc-breakeven').textContent = calculator.calculateBreakeven(newPremium, marketData.spot, marketData.strike, marketData.optionType).toFixed(2);
// Scenario analysis
this.generateScenarioTable(marketData);
}
generateScenarioTable(marketData) {
const tbody = document.getElementById('scenario-tbody');
tbody.innerHTML = '';
const moves = [-200, -100, -50, 0, 50, 100, 200];
moves.forEach(move => {
const newPremium = calculator.estimateNewPremium(marketData.premium, marketData.delta, move);
const pnl = calculator.calculatePnL(marketData.premium, newPremium, 1, 75);
const signal = pnl > 0 ? '📈 Profit' : pnl < 0 ? '📉 Loss' : '➡� Breakeven';
const row = `<tr>
<td>${move >= 0 ? '+' : ''}${move}</td>
<td>${newPremium.toFixed(2)}</td>
<td style="color: ${pnl > 0 ? 'var(--success-color)' : 'var(--danger-color)'}">₹${pnl.toFixed(0)}</td>
<td>${signal}</td>
</tr>`;
tbody.innerHTML += row;
});
}
handleRulesSubmit() {
const rules = {
deltaMin: parseFloat(document.getElementById('rule-delta-min').value),
deltaMax: parseFloat(document.getElementById('rule-delta-max').value),
targetPct: parseFloat(document.getElementById('rule-target-pct').value),
slPct: parseFloat(document.getElementById('rule-sl-pct').value)
};
storage.saveRules(rules);
this.showNotification('✅ Trading rules saved!', 'success');
}
handleSettingsSubmit() {
const settings = {
notifications: document.getElementById('setting-notifications').checked,
darkMode: document.getElementById('setting-dark-mode').checked,
defaultType: document.getElementById('setting-default-type').value
};
storage.saveSettings(settings);
this.showNotification('✅ Settings saved!', 'success');
}
refreshDashboard() {
const marketData = storage.getMarketData();
if (!marketData) {
document.getElementById('status-display').innerHTML = '<p class="text-neutral">No data entered yet. Go to "Input Data" tab.</p>';
return;
}
// Update status
document.getElementById('status-spot').textContent = marketData.spot.toFixed(2);
document.getElementById('status-strike').textContent = marketData.strike;
document.getElementById('status-premium').textContent = marketData.premium.toFixed(2);
document.getElementById('status-delta').textContent = marketData.delta.toFixed(3);
// Generate signals
const rules = storage.getRules();
const signals = signalGen.generateSignals(marketData, rules);
const overallSignal = signalGen.getOverallSignal(signals);
// Display overall signal
const signalDiv = document.getElementById('signal-display');
let signalHTML = `<p id="signal-text" class="signal-${overallSignal.toLowerCase()}">`;
signalHTML += overallSignal === 'BUY' ? '🟢 BUY SIGNAL' : overallSignal === 'SELL' ? '🔴 SELL SIGNAL' : '🟡 HOLD SIGNAL';
signalHTML += `</p>`;
signalDiv.innerHTML = signalHTML;
// Display targets
const targets = signalGen.generateTargetsAndStops(marketData, rules);
document.getElementById('target-entry').textContent = targets.entry.toFixed(2);
document.getElementById('target-target').textContent = targets.target.toFixed(2);
document.getElementById('target-sl').textContent = targets.stopLoss.toFixed(2);
document.getElementById('target-rr').textContent = targets.riskReward.toFixed(2);
// Update history
this.displayHistory();
}
displaySignals(signals) {
const container = document.getElementById('signals-container');
if (signals.length === 0) {
container.innerHTML = '<p class="text-neutral">No signals generated. Check your data.</p>';
return;
}
let html = '';
signals.forEach(signal => {
const confidence = (signal.confidence * 100).toFixed(0);
const signalClass = signal.signal === 'BUY' ? 'bullish' : signal.signal === 'SELL' ? 'bearish' : 'neutral';
html += `<div class="signal-item ${signalClass}">
<strong>${signal.message}</strong>
<small>Confidence: ${confidence}% | Type: ${signal.type}</small>
</div>`;
});
container.innerHTML = html;
}
displayHistory() {
const history = storage.getHistory();
const container = document.getElementById('history-container');
if (history.length === 0) {
container.innerHTML = '<p class="text-neutral">No trades recorded yet.</p>';
return;
}
let html = '';
history.reverse().slice(0, 10).forEach(trade => {
const date = new Date(trade.timestamp).toLocaleString();
html += `<div class="history-item">
<strong>${trade.strike} ${trade.optionType}</strong> - ${date}<br>
Entry: ₹${trade.premium} | Status: ${trade.status || 'Active'}
</div>`;
});
container.innerHTML = html;
}
exportData() {
const marketData = storage.getMarketData();
if (!marketData) {
this.showNotification('No data to export.', 'warning');
return;
}
const dataStr = JSON.stringify(storage.exportData(), null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `nifty-options-${new Date().toISOString().split('T')[0]}.json`;
a.click();
this.showNotification('✅ Data exported!', 'success');
}
exportAllData() {
const dataStr = JSON.stringify(storage.exportData(), null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `nifty-complete-${new Date().toISOString().split('T')[0]}.json`;
a.click();
this.showNotification('✅ Complete data exported!', 'success');
}
handleImport(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
storage.importData(e.target.result);
this.showNotification('✅ Data imported successfully!', 'success');
this.refreshDashboard();
} catch (err) {
this.showNotification('� Import failed: Invalid file format', 'error');
}
};
reader.readAsText(file);
}
clearHistory() {
if (confirm('Are you sure you want to clear all trade history?')) {
storage.clearHistory();
this.showNotification('✅ History cleared!', 'success');
this.displayHistory();
}
}
resetApp() {
if (confirm('This will reset the entire app. Are you sure?')) {
storage.reset();
location.reload();
}
}
loadSettings() {
const settings = storage.getSettings();
document.getElementById('setting-notifications').checked = settings.notifications;
document.getElementById('setting-dark-mode').checked = settings.darkMode;
document.getElementById('setting-default-type').value = settings.defaultType;
if (settings.darkMode) {
document.body.classList.add('dark-mode');
}
}
showNotification(message, type = 'success') {
const notif = document.getElementById('notification');
notif.textContent = message;
notif.className = `notification show ${type}`;
setTimeout(() => {
notif.classList.remove('show');
}, 3000);
}
}
const ui = new UIManager();
8. JavaScript - Main App (js/app.js)
// Main app initialization
class NiftyApp {
constructor() {
console.log('Nifty 50 Options Trading Assistant v1.0.0 loaded');
console.log('App running in standby mode with local storage');
this.initializeApp();
}
initializeApp() {
// Check if app was installed
if ('storage' in navigator && 'serviceWorker' in navigator) {
console.log('PWA features available');
}
// Check for updates periodically
this.setupPeriodicChecks();
}
setupPeriodicChecks() {
// Check every 5 minutes if we should remind user to update data
setInterval(() => {
const lastData = storage.getMarketData();
if (lastData) {
const lastTime = new Date(lastData.timestamp);
const now = new Date();
const minutesElapsed = (now - lastTime) / (1000 * 60);
// Remind after 30 minutes
if (minutesElapsed > 30 && storage.getSettings().notifications) {
const settings = storage.getSettings();
if (settings.notifications && 'Notification' in window && Notification.permission === 'granted') {
new Notification('Update Market Data', {
body: `Your data is ${Math.floor(minutesElapsed)} minutes old. Update for fresh signals!`,
icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192"><rect fill="%231a73e8" width="192" height="192"/><text x="50%" y="50%" font-size="100" fill="white" text-anchor="middle" dy=".3em">N50</text></svg>'
});
}
}
}
}, 5 * 60 * 1000);
}
}
// Initialize app when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
const app = new NiftyApp();
});
// Request notification permission
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}
9. Service Worker (service-worker.js)
const CACHE_NAME = 'nifty-options-v1.0.0';
const urlsToCache = [
'/',
'/index.html',
'/css/styles.css',
'/js/app.js',
'/js/calculator.js',
'/js/signals.js',
'/js/storage.js',
'/js/ui.js',
'/manifest.json'
];
// Install event
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
console.log('Opened cache:', CACHE_NAME);
return cache.addAll(urlsToCache.map(url => new Request(url, { cache: 'reload' })).catch(() => null));
})
);
self.skipWaiting();
});
// Activate event
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim();
});
// Fetch event
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).catch(() => {
return caches.match('/index.html');
});
})
);
});
// Background sync for data persistence
self.addEventListener('sync', event => {
if (event.tag === 'sync-data') {
event.waitUntil(Promise.resolve());
}
});
10. Manifest File (manifest.json)
{
"name": "Nifty 50 Options Trading Assistant",
"short_name": "N50 Trader",
"description": "AI-powered entry/exit tips for Nifty 50 options with Greeks calculator",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait-primary",
"background_color": "#ffffff",
"theme_color": "#1a73e8",
"icons": [
{
"src": "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 192 192'><rect fill='%231a73e8' width='192' height='192'/><text x='50%' y='50%' font-size='100' fill='white' text-anchor='middle' dy='.3em'>N50</text></svg>",
"sizes": "192x192",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><rect fill='%231a73e8' width='512' height='512'/><text x='50%' y='50%' font-size='300' fill='white' text-anchor='middle' dy='.3em'>N50</text></svg>",
"sizes": "512x512",
"type": "image/svg+xml",
"purpose": "any maskable"
}
],
"categories": ["finance", "productivity"],
"screenshots": [
{
"src": "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 540 720'><rect fill='%231a73e8' width='540' height='720'/><text x='50%' y='50%' font-size='100' fill='white' text-anchor='middle'>N50 Trader</text></svg>",
"sizes": "540x720",
"type": "image/svg+xml",
"form_factor": "narrow"
}
],
"shortcuts": [
{
"name": "Enter Market Data",
"short_name": "New Trade",
"description": "Enter current market data for analysis",
"url": "/?tab=input",
"icons": [
{
"src": "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 192 192'><rect fill='%231a73e8' width='192' height='192'/><text x='50%' y='50%' font-size='100' fill='white' text-anchor='middle' dy='.3em'>+</text></svg>",
"sizes": "192x192"
}
]
}
]
}
Metadata
Metadata
Assignees
Labels
No labels