"""Trading mathematics: deterministic, self-contained Manim Community scenes.
All examples exclude costs unless explicitly stated. Four 24-second beats.
Teal = long/gain, coral = short/loss, gold = reference or collateral threshold.
"""
from manim import *
from math import erf, exp, log, sqrt

BG = "#0b1524"
INK = "#e7eef7"
TEAL = "#41dbc0"
CORAL = "#ff907e"
GOLD = "#f6c453"
BLUE = "#65aaff"

def text(value, size=32, color=INK):
    return Text(value, font="DejaVu Sans", font_size=size, color=color)

def beat(scene, title, caption):
    scene.clear()
    scene.camera.background_color = BG
    scene.beat_start = scene.time
    heading = text(title, 36).move_to([0, 3.25, 0])
    if heading.width > 12:
        heading.scale_to_fit_width(12)
    footer = text(caption, 23).move_to([0, -3.3, 0])
    if footer.width > 12:
        footer.scale_to_fit_width(12)
    scene.add(heading, footer)

def end(scene):
    scene.wait(max(0.1, 24 - (scene.time - scene.beat_start)))

def axes(xr, yr, xlabel, ylabel):
    a = Axes(x_range=xr, y_range=yr, x_length=10, y_length=4.1,
             tips=False, axis_config={"color": "#66778e", "include_ticks": True})
    a.move_to([0, -0.25, 0])
    labels = VGroup(text(xlabel, 23).move_to([0, -2.85, 0]),
                    text(ylabel, 23).move_to([-4.3, 2.1, 0]))
    # Numeric scales make the plotted claims independently checkable.
    for value in range(int(xr[0]), int(xr[1])+1, int(xr[2])):
        labels.add(text(str(value), 19).move_to([a.c2p(value, yr[0])[0], -2.5, 0]))
    for value in range(int(yr[0]), int(yr[1])+1, int(yr[2])):
        labels.add(text(str(value), 18).move_to([-5.55, a.c2p(xr[0], value)[1], 0]))
    return a, labels

def call_value(s, t, sigma=.3):
    if t <= 0:
        return max(s - 100, 0)
    d1 = (log(s / 100) + .5 * sigma * sigma * t) / (sigma * sqrt(t))
    d2 = d1 - sigma * sqrt(t)
    normal = lambda x: .5 * (1 + erf(x / sqrt(2)))
    return s * normal(d1) - 100 * normal(d2)

class ReturnsLeverage(Scene):
    def construct(self):
        beat(self, "Do +20% and −20% cancel?", "Same percentages. Different dollar bases.")
        value = ValueTracker(100)
        amount = always_redraw(lambda: text(f"$ {value.get_value():.0f}", 76,
                                TEAL if value.get_value() >= 100 else CORAL).move_to([0, .45, 0]))
        bar = always_redraw(lambda: Rectangle(width=value.get_value()/18, height=.55,
                                fill_color=TEAL, fill_opacity=.8, stroke_width=0).move_to([0, -1, 0]))
        self.add(amount, bar)
        self.wait(5)
        change = text("× 1.20", 38, TEAL).move_to([0, 1.8, 0])
        self.play(FadeIn(change), value.animate.set_value(120), run_time=4)
        self.wait(4)
        self.play(Transform(change, text("× 0.80", 38, CORAL).move_to(change)),
                  value.animate.set_value(96), run_time=4)
        end(self)

        beat(self, "Returns multiply; they do not add", "A 20% loss needs a 25% recovery.")
        equation = text("1.20 × 0.80 = 0.96", 46).move_to([0, 1.55, 0])
        self.play(Write(equation), run_time=2)
        boxes = VGroup(*[RoundedRectangle(width=2.2, height=1.4, corner_radius=.15,
                    color=c) for c in [INK, CORAL, TEAL]]).arrange(RIGHT, buff=1.5)
        nums = VGroup(*[text(v, 38).move_to(b) for v,b in zip(["100", "80", "100"], boxes)])
        arrows = VGroup(Arrow(boxes[0].get_right(), boxes[1].get_left(), color=CORAL),
                        Arrow(boxes[1].get_right(), boxes[2].get_left(), color=TEAL))
        rates = VGroup(text("−20%", 29, CORAL).next_to(arrows[0], DOWN),
                       text("+25%", 29, TEAL).next_to(arrows[1], DOWN))
        self.play(Create(boxes), FadeIn(nums), run_time=2)
        self.play(GrowArrow(arrows[0]), FadeIn(rates[0]), run_time=3)
        self.wait(3)
        self.play(GrowArrow(arrows[1]), FadeIn(rates[1]), run_time=3)
        self.play(FadeIn(text("Recovery = loss / (1 − loss)", 31).move_to([0,-2,0])), run_time=2)
        end(self)

        beat(self, "Leverage rotates the profit line", "Fixed exposure; returns measured against starting equity; no costs.")
        a, labels = axes([-10,10,5], [-50,50,25], "Price return (%)", "Equity return (%)")
        leverage = ValueTracker(1)
        graph = always_redraw(lambda: a.plot(lambda x: leverage.get_value()*x,
                                             x_range=[-10,10], color=TEAL))
        label = always_redraw(lambda: text(f"Leverage: {leverage.get_value():.0f}×", 32, GOLD).move_to([2.5,2.05,0]))
        self.add(a, labels, graph, label)
        self.wait(5)
        self.play(leverage.animate.set_value(5), run_time=6)
        self.play(FadeIn(Dot(a.c2p(2,10), color=GOLD)),
                  FadeIn(text("+2% price → +10% equity", 30).move_to([0,2.65,0])), run_time=2)
        end(self)

        beat(self, "Collateral can run out before the forecast", "Illustrative maintenance threshold. Actual venue rules differ.")
        a, labels = axes([-20,0,5], [0,1000,250], "Price return (%)", "Equity ($)")
        curve = a.plot(lambda x: 1000+50*x, x_range=[-20,0], color=TEAL)
        maintenance = DashedLine(a.c2p(-20,200),a.c2p(0,200),color=GOLD)
        move = ValueTracker(0)
        dot = always_redraw(lambda: Dot(a.c2p(move.get_value(),1000+50*move.get_value()),color=CORAL))
        self.add(a,labels,curve,dot,maintenance)
        self.add(text("$1,000 equity / $5,000 exposure",28).move_to([1.5,2.1,0]))
        self.wait(5)
        self.play(move.animate.set_value(-16),run_time=7)
        self.play(FadeIn(text("−16% → $200 remaining",31,GOLD).move_to([0,2.65,0])),run_time=2)
        end(self)
