// RiskCalc.jsx — Position Sizing Calculator const RiskCalcPage = () => { const { useState } = React; const [inputs, setInputs] = useState({ accountSize: 25000, riskPct: 1, entryPrice: '', stopPrice: '', targetPrice: '', // Option specific mode: 'stock', // stock | option premiumPerContract: '', maxLossPerContract: '', contractsInput: '', }); const set = (k, v) => setInputs(i => ({ ...i, [k]: v })); const riskAmt = (inputs.accountSize * inputs.riskPct) / 100; // Stock calcs const stopDist = Math.abs((parseFloat(inputs.entryPrice) || 0) - (parseFloat(inputs.stopPrice) || 0)); const shares = stopDist > 0 ? Math.floor(riskAmt / stopDist) : 0; const positionSize = shares * (parseFloat(inputs.entryPrice) || 0); const targetDist = Math.abs((parseFloat(inputs.targetPrice) || 0) - (parseFloat(inputs.entryPrice) || 0)); const rr = stopDist > 0 && targetDist > 0 ? targetDist / stopDist : null; const expectedReturn = shares * targetDist; const maxLoss = shares * stopDist; // Option calcs const premPerCt = parseFloat(inputs.premiumPerContract) || 0; const maxLossPerCt = parseFloat(inputs.maxLossPerContract) || premPerCt * 100; const contracts = maxLossPerCt > 0 ? Math.floor(riskAmt / maxLossPerCt) : 0; const totalOptionCost = contracts * premPerCt * 100; const inp = (extra={}) => ({ background: 'var(--surface3)', border: '1px solid var(--border)', borderRadius: '8px', color: 'var(--text)', padding: '10px 14px', fontSize: '14px', outline: 'none', fontFamily: 'Space Grotesk, sans-serif', width: '100%', boxSizing: 'border-box', transition: 'border-color 0.15s', ...extra }); const labelS = { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', display: 'block', marginBottom: '6px' }; const ResultRow = ({ label, value, color = 'var(--text)', large = false, sub }) => React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--surface2)' } }, [ React.createElement('div', { key: 'l' }, [ React.createElement('div', { key: 'label', style: { fontSize: '13px', color: 'var(--text2)' } }, label), sub && React.createElement('div', { key: 'sub', style: { fontSize: '11px', color: 'var(--muted)', marginTop: '2px' } }, sub) ]), React.createElement('div', { key: 'v', style: { fontSize: large ? '24px' : '16px', fontWeight: large ? 700 : 500, color, fontFamily: 'JetBrains Mono, monospace' } }, value) ]); // Risk level indicator const posRiskPct = positionSize > 0 ? (positionSize / inputs.accountSize) * 100 : 0; const riskLevel = posRiskPct > 25 ? 'High' : posRiskPct > 10 ? 'Medium' : 'Low'; const riskLevelColor = posRiskPct > 25 ? '#f04060' : posRiskPct > 10 ? '#ffb83f' : '#00d27a'; // Presets const PRESETS = [ { label: '0.5%', val: 0.5 }, { label: '1%', val: 1 }, { label: '1.5%', val: 1.5 }, { label: '2%', val: 2 }, { label: '3%', val: 3 } ]; return React.createElement('div', { style: { padding: '28px 32px', maxWidth: '900px' } }, [ React.createElement('div', { key: 'hdr', style: { marginBottom: '28px' } }, [ React.createElement('h1', { key: 'h', style: { fontSize: '22px', fontWeight: 700, color: 'var(--text)', margin: 0 } }, 'Risk Calculator'), React.createElement('p', { key: 'sub', style: { fontSize: '13px', color: 'var(--muted)', margin: '4px 0 0' } }, 'Position sizing based on account risk') ]), React.createElement('div', { key: 'layout', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' } }, [ // Left: Inputs React.createElement('div', { key: 'inputs', style: { display: 'flex', flexDirection: 'column', gap: '16px' } }, [ // Account settings React.createElement('div', { key: 'acct', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '20px' } }, [ React.createElement('div', { key: 't', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '14px' } }, 'Account Settings'), React.createElement('div', { key: 'size' }, [ React.createElement('label', { key: 'l', style: labelS }, 'Account Size ($)'), React.createElement('input', { type: 'number', value: inputs.accountSize, onChange: e => set('accountSize', parseFloat(e.target.value) || 0), style: inp() }) ]), React.createElement('div', { key: 'risk', style: { marginTop: '12px' } }, [ React.createElement('div', { key: 'hdr', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' } }, [ React.createElement('label', { key: 'l', style: { ...labelS, margin: 0 } }, 'Risk Per Trade (%)'), React.createElement('span', { key: 'v', style: { fontSize: '14px', fontWeight: 700, color: '#5588ff', fontFamily: 'JetBrains Mono, monospace' } }, `${inputs.riskPct}%`) ]), React.createElement('input', { type: 'range', min: 0.1, max: 5, step: 0.1, value: inputs.riskPct, onChange: e => set('riskPct', parseFloat(e.target.value)), style: { width: '100%', accentColor: '#5588ff', cursor: 'pointer' } }), React.createElement('div', { key: 'presets', style: { display: 'flex', gap: '6px', marginTop: '8px' } }, PRESETS.map(p => React.createElement('button', { key: p.val, onClick: () => set('riskPct', p.val), style: { flex: 1, padding: '5px', fontSize: '12px', border: '1px solid', borderRadius: '5px', cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', borderColor: inputs.riskPct === p.val ? '#5588ff' : 'var(--border)', background: inputs.riskPct === p.val ? 'rgba(85,136,255,0.12)' : 'var(--surface3)', color: inputs.riskPct === p.val ? '#5588ff' : 'var(--muted)' } }, p.label)) ), React.createElement('div', { key: 'amt', style: { marginTop: '8px', fontSize: '13px', color: 'var(--text2)' } }, [ 'Max risk: ', React.createElement('span', { key: 'v', style: { fontFamily: 'JetBrains Mono, monospace', color: '#f04060', fontWeight: 600 } }, `$${riskAmt.toFixed(2)}`) ]) ]) ]), // Mode toggle React.createElement('div', { key: 'mode', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '20px' } }, [ React.createElement('div', { key: 't', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '12px' } }, 'Instrument'), React.createElement('div', { key: 'btns', style: { display: 'flex', gap: '6px', background: 'var(--surface3)', border: '1px solid var(--border)', borderRadius: '8px', padding: '4px', marginBottom: '14px' } }, [ React.createElement('button', { onClick: () => set('mode','stock'), type: 'button', style: { flex: 1, padding: '8px', fontSize: '13px', fontWeight: 500, border: 'none', borderRadius: '6px', cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', background: inputs.mode==='stock'?'#5588ff':'transparent', color: inputs.mode==='stock'?'#fff':'var(--muted)' } }, 'Stock'), React.createElement('button', { onClick: () => set('mode','option'), type: 'button', style: { flex: 1, padding: '8px', fontSize: '13px', fontWeight: 500, border: 'none', borderRadius: '6px', cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', background: inputs.mode==='option'?'#5588ff':'transparent', color: inputs.mode==='option'?'#fff':'var(--muted)' } }, 'Option'), ]), // Stock fields inputs.mode === 'stock' && React.createElement('div', { key: 'stock', style: { display: 'flex', flexDirection: 'column', gap: '10px' } }, [ React.createElement('div', { key: 'ep' }, [React.createElement('label', { key: 'l', style: labelS }, 'Entry Price'), React.createElement('input', { type: 'number', step: '0.01', value: inputs.entryPrice, onChange: e => set('entryPrice', e.target.value), placeholder: '150.00', style: inp() })]), React.createElement('div', { key: 'sp' }, [React.createElement('label', { key: 'l', style: labelS }, 'Stop Price'), React.createElement('input', { type: 'number', step: '0.01', value: inputs.stopPrice, onChange: e => set('stopPrice', e.target.value), placeholder: '145.00', style: inp({ borderColor: inputs.stopPrice ? '#f04060' : 'var(--border)' }) })]), React.createElement('div', { key: 'tp' }, [React.createElement('label', { key: 'l', style: labelS }, 'Target Price (optional)'), React.createElement('input', { type: 'number', step: '0.01', value: inputs.targetPrice, onChange: e => set('targetPrice', e.target.value), placeholder: '160.00', style: inp({ borderColor: inputs.targetPrice ? '#00d27a' : 'var(--border)' }) })]), ]), // Option fields inputs.mode === 'option' && React.createElement('div', { key: 'option', style: { display: 'flex', flexDirection: 'column', gap: '10px' } }, [ React.createElement('div', { key: 'pp' }, [React.createElement('label', { key: 'l', style: labelS }, 'Premium Per Contract ($)'), React.createElement('input', { type: 'number', step: '0.01', value: inputs.premiumPerContract, onChange: e => set('premiumPerContract', e.target.value), placeholder: '2.50', style: inp() })]), React.createElement('div', { key: 'ml' }, [ React.createElement('label', { key: 'l', style: labelS }, 'Max Loss Per Contract ($)'), React.createElement('input', { type: 'number', step: '0.01', value: inputs.maxLossPerContract, onChange: e => set('maxLossPerContract', e.target.value), placeholder: `${(premPerCt*100).toFixed(0) || '250'} (full premium)`, style: inp() }), React.createElement('div', { key: 'hint', style: { fontSize: '11px', color: 'var(--muted)', marginTop: '4px' } }, 'Leave blank to use full premium as max loss') ]), ]), ]), ]), // Right: Results React.createElement('div', { key: 'results', style: { display: 'flex', flexDirection: 'column', gap: '16px' } }, [ // Main result React.createElement('div', { key: 'main', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '22px', borderTop: `2px solid ${inputs.mode==='stock' && shares > 0 ? '#5588ff' : inputs.mode==='option' && contracts > 0 ? '#a064ff' : 'var(--border)'}` } }, [ React.createElement('div', { key: 't', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '16px' } }, 'Position Size'), inputs.mode === 'stock' ? React.createElement('div', { key: 'stock-res' }, [ React.createElement(ResultRow, { key: 'shares', label: 'Shares to Buy', value: shares > 0 ? shares.toLocaleString() : '—', large: true, color: '#5588ff' }), React.createElement(ResultRow, { key: 'pos', label: 'Total Position Value', value: positionSize > 0 ? `$${positionSize.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}` : '—', sub: positionSize > 0 ? `${posRiskPct.toFixed(1)}% of account` : undefined }), React.createElement(ResultRow, { key: 'risk', label: 'Max Risk ($)', value: maxLoss > 0 ? `-$${maxLoss.toFixed(2)}` : '—', color: '#f04060' }), rr && React.createElement(ResultRow, { key: 'rr', label: 'Risk/Reward Ratio', value: `${rr.toFixed(2)}:1`, color: rr >= 2 ? '#00d27a' : rr >= 1 ? '#ffb83f' : '#f04060' }), rr && React.createElement(ResultRow, { key: 'ret', label: 'Expected Return at Target', value: `+$${expectedReturn.toFixed(2)}`, color: '#00d27a' }), ]) : React.createElement('div', { key: 'opt-res' }, [ React.createElement(ResultRow, { key: 'cts', label: 'Contracts to Buy', value: contracts > 0 ? contracts.toString() : '—', large: true, color: '#a064ff' }), React.createElement(ResultRow, { key: 'cost', label: 'Total Cost', value: totalOptionCost > 0 ? `$${totalOptionCost.toFixed(2)}` : '—', sub: totalOptionCost > 0 ? `${((totalOptionCost / inputs.accountSize)*100).toFixed(1)}% of account` : undefined }), React.createElement(ResultRow, { key: 'risk', label: 'Max Risk ($)', value: contracts > 0 ? `-$${(contracts * maxLossPerCt).toFixed(2)}` : '—', color: '#f04060' }), React.createElement(ResultRow, { key: 'riskpct', label: 'Risk % of Account', value: contracts > 0 ? `${inputs.riskPct}%` : '—' }), ]) ]), // Risk gauge inputs.mode === 'stock' && shares > 0 && React.createElement('div', { key: 'gauge', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '18px 22px' } }, [ React.createElement('div', { key: 'hdr', style: { display: 'flex', justifyContent: 'space-between', marginBottom: '10px' } }, [ React.createElement('span', { key: 'l', style: { fontSize: '12px', color: 'var(--muted)' } }, 'Position Concentration'), React.createElement('span', { key: 'v', style: { fontSize: '12px', fontWeight: 600, color: riskLevelColor } }, riskLevel) ]), React.createElement('div', { key: 'bar', style: { height: '8px', background: 'var(--surface2)', borderRadius: '4px', overflow: 'hidden' } }, React.createElement('div', { style: { height: '100%', width: `${Math.min(posRiskPct, 100)}%`, background: riskLevelColor, borderRadius: '4px', transition: 'width 0.4s' } }) ), React.createElement('div', { key: 'pct', style: { fontSize: '12px', color: 'var(--muted)', marginTop: '6px' } }, `${posRiskPct.toFixed(1)}% of $${inputs.accountSize.toLocaleString()} account`) ]), // Scale plan (position sizing at multiple R targets) shares > 0 && inputs.mode === 'stock' && stopDist > 0 && React.createElement('div', { key: 'scale', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '18px 22px' } }, [ React.createElement('div', { key: 't', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '12px' } }, 'Profit Targets'), React.createElement('div', { key: 'targets', style: { display: 'flex', flexDirection: 'column', gap: '6px' } }, [1, 1.5, 2, 3].map(r => { const ep = parseFloat(inputs.entryPrice) || 0; const sp = parseFloat(inputs.stopPrice) || 0; const dir = ep >= sp ? 1 : -1; const target = ep + dir * r * stopDist; const profit = shares * Math.abs(target - ep); return React.createElement('div', { key: r, style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 10px', background: 'var(--surface3)', borderRadius: '6px' } }, [ React.createElement('span', { key: 'r', style: { fontSize: '12px', color: 'var(--muted)' } }, `${r}R target`), React.createElement('span', { key: 'p', style: { fontSize: '12px', fontFamily: 'JetBrains Mono, monospace', color: 'var(--text)' } }, `$${target.toFixed(2)}`), React.createElement('span', { key: 'pr', style: { fontSize: '12px', fontFamily: 'JetBrains Mono, monospace', color: '#00d27a', fontWeight: 600 } }, `+$${profit.toFixed(2)}`), ]); }) ) ]) ]) ]) ]); }; Object.assign(window, { RiskCalcPage });