Skip to content

Commit f9b5e78

Browse files
Tom Schoenclaude
authored andcommitted
docs: make the repo a first-impression eyecatcher (hero banner + clear WHAT/HOW)
A first-time visitor now learns what VolScope is and how to run it in the first screenful: - A designed hero banner (docs/assets/hero.png): the ◈ VolScope wordmark, the one-line question ("Is this stock's implied volatility cheap or expensive?"), and the REAL Scope IV/HV chart rendered from live QQQ data — green where options are cheap, red where rich. Composed by scripts/ops/make_repo_hero.py over the actual chart export (docs/assets/iv-hv.png). - README restructured: centered hero + badges, then a crisp "what it is" paragraph, then a one-click "Get started" call-to-action pointing at the double-click launcher. Drops the old ASCII-art chart sketch (the real chart shows it now). The chart PNGs are committed, so nobody needs a render toolchain to see them; generating them needs `pip install kaleido==0.2.1` (maintainer-only, not an app dependency). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 94cdd10 commit f9b5e78

4 files changed

Lines changed: 149 additions & 31 deletions

File tree

README.md

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,27 @@
1+
<div align="center">
2+
13
# ◈ VolScope
24

3-
> ### Is the option market pricing more movement than the stock has delivered?
4-
>
5-
> That one question — **implied vs realized volatility** — is what VolScope
6-
> is built around. Type any ticker and its hero chart answers it at a glance,
7-
> wrapped in the context a vol desk reads: IV rank, a volatility cone, a
8-
> time-travelling term structure, 25Δ skew, and a plain-English verdict —
9-
> all from VolScope's **own** Black-Scholes solver (never Yahoo's IV). The
10-
> volatility screen you'd otherwise rent from Bloomberg, self-hosted in a
11-
> browser tab.
5+
### Is this stock's implied volatility cheap or expensive?
126

13-
[![CI](https://github.com/SchoenTom/volscope/actions/workflows/ci.yml/badge.svg)](https://github.com/SchoenTom/volscope/actions/workflows/ci.yml)
14-
![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12-blue)
15-
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
16-
![Status](https://img.shields.io/badge/status-IV%20research-brightgreen)
7+
<img src="docs/assets/hero.png" alt="VolScope — implied vs realized volatility, green where options are cheap, red where rich" width="900">
178

18-
## The chart at its heart — Implied vs Realized Volatility
9+
[![CI](https://github.com/SchoenTom/volscope/actions/workflows/ci.yml/badge.svg)](https://github.com/SchoenTom/volscope/actions/workflows/ci.yml)
10+
&nbsp;![Python](https://img.shields.io/badge/python-3.11%2B-blue)
11+
&nbsp;[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
12+
&nbsp;![Status](https://img.shields.io/badge/status-IV%20research-brightgreen)
1913

20-
This is what VolScope *is*. Everything else exists to add context to it.
14+
</div>
2115

22-
```
23-
vol % AAPL · IMPLIED vs REALIZED
24-
25-
│ ╭──╮ ← IV 30d (what options cost)
26-
│ ╭─────╯ ╰────╮ ╭────
27-
│ ─────╯ · · · · · ╰──────╯· · ← HV 20d (what the stock did)
28-
│ ░░░░░░ ▒▒▒▒▒▒▒▒ ░░░░░ ▓▓▓ ← background shaded by vol regime
29-
└────────────────────────────────► time ◆ = earnings
30-
IV above HV → options RICH · IV below HV → options CHEAP
31-
```
16+
**VolScope is a self-hosted volatility research terminal.** Type any ticker and
17+
the chart above answers one question at a glance — *is the option market pricing
18+
more movement than the stock has actually delivered?* Around it sits the context
19+
a vol desk reads: IV rank, a volatility cone, a time-travelling term structure,
20+
25Δ skew, and a one-line plain-English verdict — all computed by VolScope's
21+
**own** Black-Scholes solver, **never** Yahoo's IV. The volatility screen you'd
22+
otherwise rent from Bloomberg, running in a browser tab on your own machine.
3223

33-
When the **implied-vol** line (what options are charging) sits above
34-
**realized vol** (what the stock has actually moved), the market is paying
35-
up for movement — options are *rich*. When it dips below, they're *cheap*.
36-
The background is shaded by volatility regime, earnings dates are flagged,
37-
and a one-line verdict sits above it. It is the first, largest chart on the
38-
Scope page — front and centre, because it is the product.
24+
> **▶ Get started in one click:** download the ZIP, double-click **`VolScope.app`** — no terminal, no commands. [Full steps ↓](#run-volscope--just-double-click)
3925
4026
## Why VolScope
4127

docs/assets/hero.png

133 KB
Loading

docs/assets/iv-hv.png

198 KB
Loading

scripts/ops/make_repo_hero.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/env python
2+
"""Compose the README hero banner: wordmark + tagline + the real IV/HV chart.
3+
4+
Expects docs/assets/iv-hv.png (the rendered Scope chart) to already exist.
5+
Produces docs/assets/hero.png — the single eyecatcher at the top of the README.
6+
7+
.venv/bin/python scripts/ops/make_repo_hero.py
8+
"""
9+
from __future__ import annotations
10+
11+
from pathlib import Path
12+
13+
from PIL import Image, ImageDraw, ImageFont
14+
15+
_ROOT = Path(__file__).resolve().parents[2]
16+
_CHART = _ROOT / "docs" / "assets" / "iv-hv.png"
17+
_OUT = _ROOT / "docs" / "assets" / "hero.png"
18+
19+
# Brand palette (theme.py COLORS)
20+
_BG_TOP = (10, 11, 15)
21+
_BG_BOT = (16, 17, 24)
22+
_ACCENT = (0, 212, 170)
23+
_ACCENT2 = (91, 140, 255)
24+
_WARN = (255, 68, 102)
25+
_TEXT = (224, 228, 239)
26+
_MUTED = (138, 143, 158)
27+
_FAINT = (90, 96, 112)
28+
29+
W = 1280
30+
31+
32+
def _font(size: int, bold: bool = False):
33+
cands = (
34+
["/System/Library/Fonts/SFNSRounded.ttf", "/System/Library/Fonts/SFNS.ttf"]
35+
if bold else
36+
["/System/Library/Fonts/SFNS.ttf", "/Library/Fonts/Arial.ttf"]
37+
)
38+
cands += ["/System/Library/Fonts/Helvetica.ttc", "/Library/Fonts/Arial.ttf"]
39+
for p in cands:
40+
if Path(p).exists():
41+
try:
42+
return ImageFont.truetype(p, size)
43+
except Exception:
44+
continue
45+
return ImageFont.load_default()
46+
47+
48+
def _vgrad(w: int, h: int, top, bot) -> Image.Image:
49+
base = Image.new("RGB", (1, h))
50+
for y in range(h):
51+
t = y / max(1, h - 1)
52+
base.putpixel((0, y), tuple(int(top[i] + (bot[i] - top[i]) * t) for i in range(3)))
53+
return base.resize((w, h))
54+
55+
56+
def _hgrad_text(draw_size, text, font, c1, c2) -> Image.Image:
57+
"""Render `text` filled with a horizontal c1->c2 gradient (transparent bg)."""
58+
tmp = Image.new("L", draw_size, 0)
59+
ImageDraw.Draw(tmp).text((0, 0), text, font=font, fill=255)
60+
grad = Image.new("RGB", draw_size)
61+
for x in range(draw_size[0]):
62+
t = x / max(1, draw_size[0] - 1)
63+
col = tuple(int(c1[i] + (c2[i] - c1[i]) * t) for i in range(3))
64+
for y in range(draw_size[1]):
65+
grad.putpixel((x, y), col)
66+
out = Image.new("RGBA", draw_size, (0, 0, 0, 0))
67+
out.paste(grad, (0, 0), tmp)
68+
return out
69+
70+
71+
def main() -> int:
72+
chart = Image.open(_CHART).convert("RGB")
73+
cw, ch = chart.size
74+
chart_w = W - 96
75+
chart_h = int(ch * (chart_w / cw))
76+
chart = chart.resize((chart_w, chart_h), Image.LANCZOS)
77+
78+
header_h = 248
79+
caption_h = 46
80+
H = header_h + chart_h + caption_h + 40
81+
82+
canvas = _vgrad(W, H, _BG_TOP, _BG_BOT).convert("RGBA")
83+
d = ImageDraw.Draw(canvas)
84+
85+
# Brand mark — a small ◈ diamond left of the wordmark
86+
mx, my = 60, 70
87+
d.line([(mx, my - 18), (mx + 18, my), (mx, my + 18), (mx - 18, my), (mx, my - 18)],
88+
fill=_ACCENT, width=4, joint="curve")
89+
d.ellipse([mx - 4, my - 4, mx + 4, my + 4], fill=_ACCENT)
90+
91+
# Wordmark with gradient fill
92+
wf = _font(46, bold=True)
93+
wm = _hgrad_text((360, 64), "VolScope", wf, _ACCENT, _ACCENT2)
94+
canvas.alpha_composite(wm, (90, 42))
95+
96+
# Tagline
97+
d.text((62, 128), "Is this stock's implied volatility cheap or expensive?",
98+
font=_font(27, bold=True), fill=_TEXT)
99+
# Sub-line
100+
d.text((62, 172),
101+
"A self-hosted volatility terminal — its own Black-Scholes solver "
102+
"(never Yahoo's IV), in a browser tab.",
103+
font=_font(17), fill=_MUTED)
104+
105+
# Chart
106+
cx = (W - chart_w) // 2
107+
cy = header_h
108+
canvas.paste(chart, (cx, cy))
109+
# hairline frame around the chart
110+
d.rectangle([cx, cy, cx + chart_w - 1, cy + chart_h - 1],
111+
outline=(30, 32, 56), width=1)
112+
113+
# Caption with the colour key
114+
cap_y = cy + chart_h + 16
115+
f = _font(15)
116+
d.text((cx, cap_y), "Implied vs realized volatility — ", font=f, fill=_MUTED)
117+
w1 = d.textlength("Implied vs realized volatility — ", font=f)
118+
d.text((cx + w1, cap_y), "green where options are cheap", font=f, fill=_ACCENT)
119+
w2 = d.textlength("green where options are cheap", font=f)
120+
d.text((cx + w1 + w2, cap_y), ", ", font=f, fill=_MUTED)
121+
w3 = d.textlength(", ", font=f)
122+
d.text((cx + w1 + w2 + w3, cap_y), "red where rich", font=f, fill=_WARN)
123+
w4 = d.textlength("red where rich", font=f)
124+
d.text((cx + w1 + w2 + w3 + w4, cap_y), ". (QQQ, live data)", font=f, fill=_FAINT)
125+
126+
canvas.convert("RGB").save(_OUT)
127+
print(f"wrote {_OUT} ({_OUT.stat().st_size} bytes, {W}x{H})")
128+
return 0
129+
130+
131+
if __name__ == "__main__":
132+
raise SystemExit(main())

0 commit comments

Comments
 (0)