science:social:economics:finance:option-simulator

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
science:social:economics:finance:option-simulator [2025/10/04 07:24] falsycatscience:social:economics:finance:option-simulator [2025/10/04 15:56] (current) falsycat
Line 1: Line 1:
-====== Option Price Simulator ======+====== Option PnL Simulator ====== 
 + 
 +[[black-scholes-equation]]に基づいて、オプションの損益をシミュレーションするためのツールです。 
 +テキストエリアに建玉を入力してCalcボタンを押すと、縦軸を原資産価格、横軸を満期までの日数とした損益のヒートマップを生成することができます。 
 +建玉の入力記法については[[#how-to-use]]を参照してください。
  
 <html> <html>
-<div id="heatmap" style="height: 1000px; positionrelative"></div>+<style>#heatmap svg { height: auto; }</style> 
 + 
 +<div id="heatmap_param"> 
 +  <div><textarea class="script" style="height: 16rem">SC@22000#5$130 
 +LC@21750#5$250x2 
 +SC@21500#5$390</textarea></div> 
 +  <div style="width40rem"> 
 +    <table> 
 +      <tr><td>IV</td><td><input class="iv" type="number" value="0.2" /></td><td>1=100%</td></tr> 
 +      <tr><td>Risk-Free rate</td><td><input class="risk-free-rate" type="number" value="0" /></td><td>1=100%</td></tr> 
 +    </table> 
 +    <button>Calc</button> 
 +  </div> 
 +</div> 
 + 
 +<div id="heatmap"></div>
  
 <script> <script>
 document.addEventListener("DOMContentLoaded", ()=>{ document.addEventListener("DOMContentLoaded", ()=>{
  
-// 1. scriptタグを作成 +// error function 
-const script document.createElement('script'); +const erf = (x)=>{ 
-script.src "https://cdn.plot.ly/plotly-3.1.0.min.js"+  const p  =  0.3275911
-script.async true;+  const a1 =  0.254829592; 
 +  const a2 -0.284496736; 
 +  const a3 =  1.421413741; 
 +  const a4 = -1.453152027; 
 +  const a5 =  1.061405429
 +  const t = 1 /(1 + p * Math.abs(x)); 
 +  const y 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x); 
 +  return (x > 0) ? y : -y; 
 +};
  
-// 2読み込み完了時処理 +// Normal Distribution CDF 
-script.onload = () => { +const normCDF = (x)=>(1 + erf(x / Math.sqrt(2))) / 2; 
-    console.log('Plotly.js loaded!');+ 
 +// formula to calculate option price 
 +const blackScholes = (S, K, T, r, sigma, type = 'call')=>
 +  // S : 現在株価 
 +  // K : 権利行使価格 
 +  // T : 満期までの時間(年単位) 
 +  // r : 無リスク金利(年率) 
 +  // sigma : ボラティリティ(年率) 
 +  // type : 'call' または 'put' 
 +  const d1 = (Math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * Math.sqrt(T)); 
 +  const d2 d1 - sigma * Math.sqrt(T)
 + 
 +  if (type === 'call') { 
 +      return T>0? S * normCDF(d1) - K * Math.exp(-r * T) * normCDF(d2): Math.max(S-K, 0); 
 +  } else if (type === 'put') { 
 +      return T>0? K * Math.exp(-r * T) * normCDF(-d2) - S * normCDF(-d1): Math.max(K-S, 0); 
 +  } else { 
 +      throw new Error("invalid type: "+type); 
 +  } 
 +}; 
 + 
 +// simulation function 
 +const simulate = (pos, r)=>{ 
 +  const priceMin = Math.min(...pos.map(x => x.strike))*0.8; 
 +  const priceMax = Math.max(...pos.map(x => x.strike))*1.2; 
 +  const priceStep = (priceMax - priceMin)/100; 
 +  let prices = []; 
 +  for (let S = priceMin; S <= priceMax; S+=priceStep) 
 +    prices.push(S); 
 +  } 
 +   
 +  const dayMax = Math.max(...pos.map(x => x.open_at)); 
 +  let days = []; 
 +  for (let T = 0; T <= dayMax; ++T) { 
 +    days.push(T); 
 +  } 
 + 
 +  let data = []; 
 +  for (const S of prices) { 
 +    let row = []; 
 +    for (const T of days) { 
 +      let pnl = 0; 
 +      for (const p of pos) { 
 +        if (p.open_at >= T) { 
 +            const open_vol = p.open_price; 
 +            const curr_vol = blackScholes(S, p.strike, T/252, r, p.iv, p.type); 
 +            pnl += p.count*(p.side === "short"? 
 +              open_vol - curr_vol: curr_vol - open_vol); 
 +        } 
 +      } 
 +      row.push(pnl); 
 +    } 
 +    data.push(row); 
 +  } 
 +  return [days, prices, data]; 
 +}; 
 + 
 +// parse 
 +const parseScript = (script, iv)=>{ 
 +  const tokens = script.split(/\s+/); 
 +   
 +  let pos = []; 
 +  for (const tok of tokens) { 
 +    const es = tok.split(/(?=[@$#\^x])/); 
 +     
 +    const p = {}; 
 +    if (es[0] == "LC") { 
 +      p.side = "long"; 
 +      p.type = "call"; 
 +    } else if (es[0] == "LP") { 
 +      p.side = "long"; 
 +      p.type = "put"; 
 +    } else if (es[0] == "SC") { 
 +      p.side = "short"; 
 +      p.type = "call"; 
 +    } else if (es[0] == "SP") { 
 +      p.side = "short"; 
 +      p.type = "put"; 
 +    } else { 
 +      throw "fuck"; 
 +    } 
 +     
 +    const get_num = (x)=>+es.filter(s => s.startsWith(x)).at(-1)?.substring(1); 
 +    p.strike     = get_num("@"); 
 +    p.open_price = get_num("$"); 
 +    p.open_at    = get_num("#"); 
 +    p.iv         = get_num("^") || iv; 
 +    p.count      = get_num("x") || 1; 
 +     
 +    const valid = p.strike && p.open_price && p.open_at; 
 +    if (!validthrow "fuck";
          
-    // ここでPlotlyを使ったヒートマップ描画などが可能 +    pos.push(p)
-    const data = [{ +  
-        z: [[1,2],[3,4]], +  return pos;
-        type: 'heatmap' +
-    }]+
-    const layout = { +
-      height: 400, +
-      width: 500 +
-    }; +
-    Plotly.newPlot('heatmap', data, layout, {responsive: true});+
 }; };
  
-// 3. bodyに追加してロード開始+// re-paint plot 
 +const update = ()=>{ 
 +  const params = document.querySelector("#heatmap_param"); 
 +  const script = params.querySelector(".script").value; 
 +  const iv = +params.querySelector(".iv").value; 
 +  const riskfree_rate = +params.querySelector(".risk-free-rate").value; 
 +   
 +  const pos = parseScript(script, iv); 
 +  const [x, y, z] = simulate(pos, riskfree_rate); 
 +   
 +  Plotly.newPlot('heatmap', [{ 
 +    x: x, 
 +    y: y, 
 +    z: z, 
 +    zmid: 0, 
 +    colorscale: [ 
 +      [0.0, 'blue'], 
 +      [0.49, 'lightblue'], 
 +      [0.5, 'black'], 
 +      [0.51, 'pink'], 
 +      [1.0, 'red'], 
 +    ], 
 +    type: 'heatmap', 
 +     
 +    hovertemplate: 'Days Left: %{x}<br>Underlying Price: %{y}<br>Option PnL: %{z}<extra></extra>', 
 +  }], { 
 +    title: {text: "Option PnL simulation"}, 
 +    xaxis: { 
 +      title: {text: "Days Left"}, 
 +      autorange: "reversed", 
 +      fixedrange: true, 
 +    }, 
 +    yaxis: { 
 +      title: {text: "Underlying Price"}, 
 +      fixedrange: true, 
 +    }, 
 +    dragmode: false, 
 +  }); 
 +}; 
 + 
 +// ---- main routine ---- 
 +const script = document.createElement('script'); 
 +script.src = "https://cdn.plot.ly/plotly-3.1.0.min.js"; 
 +script.async = true; 
 +script.onload = ()=>{ 
 +  document.querySelector("#heatmap_param button").addEventListener("click", update); 
 +  update(); 
 +};
 document.body.appendChild(script); document.body.appendChild(script);
  
Line 34: Line 191:
 </script> </script>
 </html> </html>
 +
 +===== How To Use =====
 +
 +単体の建玉は以下の記法で記述できます。
 +単体の建玉記述を1行ずつ記述することで、複数の建玉を記述することができます。
 +
 +<code>
 +(S/L)(C/P)@strike#open_at$open_price
 +  S/L: Short or Long
 +  C/P: Call or Put
 +  strike    : strike price
 +  open_at   : days left at open
 +  open_price: price at open
 +</code>
 +
 +==== 例 ====
 +
 +オプション戦略については[[option-strategy]]を参照。
 +
 +=== Short Iron Butterfly ===
 +
 +<code>
 +SC@21000#30$990
 +LC@20000#30$590
 +LP@20000#30$620
 +SP@19000#30$120
 +</code>
  • Last modified: 18 hours ago
  • by falsycat