# Makes the invented price files used by the technique pages.
#
# Every number comes from a random number generator with a fixed seed, so running this script
# again writes exactly the same files. None of these prices ever traded anywhere. They exist so
# that code can be taught without pointing at any real market.
#
# Run it from the folder you want the files in:  python make_invented_data.py
import csv
import random
from datetime import date, timedelta


def weekdays(start, count):
    """Return `count` dates from `start`, skipping Saturdays and Sundays."""
    days = []
    day = start
    while len(days) < count:
        if day.weekday() < 5:
            days.append(day)
        day += timedelta(days=1)
    return days


def write(name, header, rows):
    with open(name, "w", newline="") as f:
        writer = csv.writer(f, lineterminator="\n")
        writer.writerow(header)
        writer.writerows(rows)
    print("wrote", name, "with", len(rows), "rows")


# 1. invented-prices.csv: one instrument, 750 days.
# A plain random walk. Each day moves up or down by a random amount with no drift, no trend,
# no weekday effect and no memory. By construction there is nothing in it for a rule to find.
rng = random.Random(20261009)
rows = []
price = 100.0
for day in weekdays(date(2023, 1, 2), 750):
    open_ = price
    close = open_ * (1 + rng.gauss(0, 0.010))
    high = max(open_, close) * (1 + abs(rng.gauss(0, 0.004)))
    low = min(open_, close) * (1 - abs(rng.gauss(0, 0.004)))
    rows.append([day.isoformat(), f"{open_:.2f}", f"{high:.2f}", f"{low:.2f}", f"{close:.2f}"])
    price = round(close, 2)
write("invented-prices.csv", ["date", "open", "high", "low", "close"], rows)


# 2. invented-pair.csv: two instruments, 500 days.
# Both follow one shared random walk. The gap between them is pulled back towards zero for the
# first 300 days, because this script pulls it back. From day 300 the pull is switched off and
# the gap is given a steady push instead, so the link between the two breaks. That break is
# deliberate: it is there to show what happens to a pairs rule when a relationship ends.
rng = random.Random(20261010)
rows = []
shared = 100.0
gap = 0.0
for number, day in enumerate(weekdays(date(2024, 1, 1), 500)):
    shared += rng.gauss(0, 0.8)
    if number < 300:
        gap = 0.9 * gap + rng.gauss(0, 0.5)
    else:
        gap = gap + 0.06 + rng.gauss(0, 0.5)
    rows.append([day.isoformat(), f"{shared + gap / 2:.2f}", f"{shared - gap / 2:.2f}"])
write("invented-pair.csv", ["date", "a", "b"], rows)


# 3. invented-universe.csv: 30 instruments, 250 days.
# Each one starts at 100 and follows its own random walk with no drift. An instrument that
# closes below 60 is removed from the list, the way a failed company leaves an index, and its
# cells are left empty from the next day on.
rng = random.Random(20261011)
names = [f"INV{n:02d}" for n in range(1, 31)]
prices = {name: 100.0 for name in names}
listed = {name: True for name in names}
rows = []
for day in weekdays(date(2025, 1, 6), 250):
    row = [day.isoformat()]
    for name in names:
        if not listed[name]:
            row.append("")
            continue
        prices[name] *= 1 + rng.gauss(0, 0.03)
        row.append(f"{prices[name]:.2f}")
        if prices[name] < 60:
            listed[name] = False
    rows.append(row)
write("invented-universe.csv", ["date"] + names, rows)
