Metadata-Version: 2.4
Name: chart-lookalike-transform
Version: 0.1.0
Summary: Find the affine transform that maps a candidate ticker's price series onto a familiar chart shape, and screen tickers that 'look like' a setup.
Author-email: smolkai <smolkai@users.noreply.github.com>
License: MIT
Keywords: trading,charts,pattern-matching,affine,screening,technical-analysis
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Provides-Extra: plot
Requires-Dist: matplotlib>=3.6; extra == "plot"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: matplotlib>=3.6; extra == "test"
Dynamic: license-file

# chart-lookalike-transform

Find the **affine transform** — vertical scale `a`, offset `b`, and optional
inversion — that best maps a candidate ticker's price series onto a chart shape
you already recognize, then **screen** a list of tickers by how well they "look
like" that setup.

A chart's *shape* is invariant to vertical scale and shift: `$10 → $11` over a
month traces the same curve as `$100 → $110`. So to ask "does NVDA right now
look like the cup-and-handle I'm watching for?" you fit

```
reference ≈ a · candidate + b          (a < 0 ⇒ candidate is the mirror image)
```

by least squares (closed form), report the fit quality as a `0..1` similarity,
and overlay the transformed candidate on the reference with moving-average
indicators.

This is **not** dynamic time warping — the time axis is held fixed, so
"lookalike" means the literal same shape over the same window, just rescaled
vertically. (DTW would match shapes that drift in time; deliberately out of
scope.)

## Install

```bash
pip install -e ".[test]"      # includes matplotlib for plotting + pytest
# or, runtime only:
pip install -e .              # numpy only; add ".[plot]" for PNG overlays
```

Python ≥ 3.10. Core dependency is just NumPy; matplotlib is optional (only for
`--plot`).

## Quick start (no network)

Everything works offline against deterministic synthetic data via `--offline`
(or `CHART_LOOKALIKE_API=mock`), so you can try it instantly:

```bash
# List the built-in reference shapes.
chart-lookalike shapes

# Fit one ticker to a recognized shape and print the transform.
chart-lookalike match AAPL --shape cup-with-handle --offline

# Screen a basket, keep the best 5, and use a real ticker as the reference shape.
chart-lookalike screen AAPL MSFT NVDA TSLA AMD --ref-ticker NVDA --offline --top 5

# Write a PNG overlay (transformed candidate + reference + SMAs).
chart-lookalike match TSLA --shape v-bottom --offline --plot match.png
```

Drop `--offline` to pull **live daily closes from Yahoo Finance** (no API key
needed). The data layer is provider-pluggable behind the `CHART_LOOKALIKE_API`
env var.

## Reference shapes

Three ways to specify the target shape (choose exactly one):

| Flag | Meaning |
| --- | --- |
| `--shape <name>` | a built-in shape (`chart-lookalike shapes` lists them) |
| `--ref-csv <path>` | a file of reference prices (one number per line, or `date,close` rows — the last numeric field per line is used) |
| `--ref-ticker <sym>` | use another ticker's own series as the target shape |

Built-in shapes: `v-bottom`, `inverse-v-top`, `cup`, `cup-with-handle`,
`head-and-shoulders`, `uptrend`, `downtrend`, `double-bottom`.

## How the fit works

For a reference `t` and candidate `c` (resampled to a common length), the
least-squares affine fit has a closed form:

```
a = cov(c, t) / var(c)
b = mean(t) − a · mean(c)
```

When inversion is allowed, the mirror candidate `−c` is also fit and the
lower-residual solution wins (a negative `a` ⇒ `inverted = True`). Fit quality:

```
rmse       = sqrt( mean( (a·c + b − t)² ) )
similarity = clip( 1 − rmse / rms(t − mean(t)),  0, 1 )
```

`similarity = 1.0` is a perfect shape match; `0.0` is no better than predicting a
flat line at the reference's mean.

## Library API

```python
import numpy as np
from chart_lookalike import fit_affine, apply_affine, get_provider, screen
from chart_lookalike.shapes import get_shape

provider = get_provider("mock")              # or "yahoo"
series   = provider.history("AAPL", days=180)
ref      = get_shape("cup-with-handle")

fit = fit_affine(series.closes, ref)         # AffineFit(scale, offset, inverted, rmse, similarity, n)
overlay = apply_affine(series.closes, fit)   # candidate mapped onto the reference's units

ranked = screen(ref, ["AAPL", "MSFT", "NVDA"], provider, top=3)
for r in ranked:
    print(r.symbol, round(r.similarity, 3), "inverted" if r.fit.inverted else "")
```

## Live-data dependency & testing

The Yahoo provider hits `query1.finance.yahoo.com/v8/finance/chart` at runtime —
that endpoint is undocumented and rate-limited, so it is the one piece that can
break outside our control. The JSON **parser** (`parse_yahoo_chart`) is split out
as a pure function and unit-tested against a **saved fixture**
(`fixtures/yahoo_aapl.json`, including a null-close hole and the error envelope) —
the test suite never touches the network.

```bash
pytest -q
```

## Disclaimer

This is a charting/screening aid, not investment advice. A high similarity score
means two curves have a similar shape over a window — nothing about what happens
next.

## License

MIT — see [LICENSE](LICENSE).
