"""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 OptionsGreeks(Scene):
    def construct(self):
        beat(self, "Right direction. Still a losing trade?", "Long call at expiry: strike $100, premium $6 per unit.")
        a,labels = axes([80,120,10],[-10,25,5],"Expiry price","Payoff / profit ($)")
        payoff = a.plot(lambda s:max(s-100,0),x_range=[80,120],use_smoothing=False,color=BLUE)
        self.add(a,labels)
        self.play(Create(payoff),run_time=5)
        self.add(text("Payoff = max(S − 100, 0)",32,BLUE).move_to([1,2.3,0]))
        self.wait(6)
        self.play(FadeIn(Dot(a.c2p(104,4),color=GOLD)),run_time=2)
        end(self)

        beat(self, "Premium shifts payoff into profit", "Expiry values per unit, before fees; multiply by contract size.")
        a,labels = axes([80,120,10],[-10,25,5],"Expiry price","Profit ($)")
        curve = a.plot(lambda s:max(s-100,0),x_range=[80,120],use_smoothing=False,color=BLUE)
        self.add(a,labels,curve)
        self.play(Transform(curve,a.plot(lambda s:max(s-100,0)-6,x_range=[80,120],use_smoothing=False,color=TEAL)),run_time=4)
        note = text("Call: 104 → −$2; break even 106",30,TEAL).move_to([0,2.3,0])
        marker = Dot(a.c2p(106,0),color=GOLD)
        self.add(note, marker)
        self.wait(6)
        put = a.plot(lambda s:max(100-s,0)-6,x_range=[80,120],use_smoothing=False,color=CORAL)
        self.play(Transform(curve,put),Transform(note,text("Put: break even 94",30,CORAL).move_to(note)),run_time=4)
        self.play(marker.animate.move_to(a.c2p(94,0)),run_time=2)
        end(self)

        beat(self, "Delta is slope. Gamma is changing slope.", "European call model: K=100, r=0, σ=30%, T=0.5 years, no dividends.")
        a,labels = axes([70,130,10],[0,35,10],"Underlying price","Option value ($)")
        curve = a.plot(lambda s:call_value(s,.5),x_range=[70,130],color=TEAL)
        s = ValueTracker(90)
        def tangent():
            x=s.get_value()
            delta=(call_value(x+.01,.5)-call_value(x-.01,.5))/.02
            return Line(a.c2p(x-6,call_value(x,.5)-6*delta),a.c2p(x+6,call_value(x,.5)+6*delta),color=GOLD)
        line=always_redraw(tangent)
        dot=always_redraw(lambda:Dot(a.c2p(s.get_value(),call_value(s.get_value(),.5)),color=GOLD))
        self.add(a,labels,curve,line,dot)
        self.add(text("Δ = dV/dS       Γ = d²V/dS²",32,GOLD).move_to([1,2.3,0]))
        self.wait(6)
        self.play(s.animate.set_value(115),run_time=10)
        end(self)

        beat(self, "Direction is only one input", "Model illustration, not a forecast. Long options can lose the entire premium.")
        a,labels=axes([70,130,10],[0,35,10],"Underlying price","Option value ($)")
        t=ValueTracker(.5)
        curve=always_redraw(lambda:a.plot(lambda s:call_value(s,t.get_value()),x_range=[70,130],color=TEAL))
        expiry=a.plot(lambda s:max(s-100,0),x_range=[70,130],color=BLUE)
        counter=always_redraw(lambda:text(f"Time remaining: {t.get_value()*365:.0f} days",29,GOLD).move_to([2,2.05,0]))
        self.add(a,labels,expiry,curve,counter)
        self.wait(4)
        self.play(t.animate.set_value(0),run_time=12,rate_func=linear)
        self.play(FadeIn(text("Price + time + implied volatility",30).move_to([0,2.65,0])),run_time=2)
        end(self)
