Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
science:social:economics:finance:option-simulator [2025/10/04 07:11] – falsycat | science:social:economics:finance:option-simulator [2025/10/04 15:56] (current) – falsycat | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Option | + | ====== Option |
+ | |||
+ | [[black-scholes-equation]]に基づいて、オプションの損益をシミュレーションするためのツールです。 | ||
+ | テキストエリアに建玉を入力してCalcボタンを押すと、縦軸を原資産価格、横軸を満期までの日数とした損益のヒートマップを生成することができます。 | ||
+ | 建玉の入力記法については[[# | ||
< | < | ||
+ | < | ||
+ | |||
+ | <div id=" | ||
+ | < | ||
+ | LC@21750# | ||
+ | SC@21500# | ||
+ | <div style=" | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | |||
<div id=" | <div id=" | ||
Line 7: | Line 26: | ||
document.addEventListener(" | document.addEventListener(" | ||
- | // 1. scriptタグを作成 | + | // error function |
- | const script | + | const erf = (x)=>{ |
- | script.src = " | + | 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; |
- | | + | |
+ | // formula to calculate option price | ||
+ | const blackScholes = (S, K, T, r, sigma, type = ' | ||
+ | // S : 現在の株価 | ||
+ | // K : 権利行使価格 | ||
+ | // T : 満期までの時間(年単位) | ||
+ | // r : 無リスク金利(年率) | ||
+ | // sigma : ボラティリティ(年率) | ||
+ | // type : ' | ||
+ | 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 === ' | ||
+ | return T>0? S * normCDF(d1) - K * Math.exp(-r * T) * normCDF(d2): | ||
+ | } else if (type === ' | ||
+ | return T>0? K * Math.exp(-r * T) * normCDF(-d2) - S * normCDF(-d1): | ||
+ | } else { | ||
+ | throw new Error(" | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | // 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)/ | ||
+ | let prices = []; | ||
+ | for (let S = priceMin; S <= priceMax; S+=priceStep) | ||
+ | | ||
+ | } | ||
+ | |||
+ | 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, | ||
+ | pnl += p.count*(p.side === " | ||
+ | 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(/ | ||
+ | |||
+ | let pos = []; | ||
+ | for (const tok of tokens) { | ||
+ | const es = tok.split(/ | ||
+ | |||
+ | const p = {}; | ||
+ | if (es[0] == " | ||
+ | p.side = " | ||
+ | p.type = " | ||
+ | } else if (es[0] == " | ||
+ | p.side = " | ||
+ | p.type = " | ||
+ | } else if (es[0] == " | ||
+ | p.side = " | ||
+ | p.type = " | ||
+ | } else if (es[0] == " | ||
+ | p.side = " | ||
+ | p.type = " | ||
+ | } else { | ||
+ | throw " | ||
+ | } | ||
+ | |||
+ | const get_num = (x)=> | ||
+ | p.strike | ||
+ | p.open_price = get_num(" | ||
+ | p.open_at | ||
+ | p.iv = get_num(" | ||
+ | p.count | ||
+ | |||
+ | const valid = p.strike && p.open_price && p.open_at; | ||
+ | if (!valid) throw " | ||
| | ||
- | | + | |
- | const data = [{ | + | } |
- | z: [[1, | + | |
- | type: ' | + | |
- | | + | |
- | | + | |
}; | }; | ||
- | // 3. bodyに追加してロード開始 | + | // re-paint plot |
+ | const update = ()=>{ | ||
+ | const params = document.querySelector("# | ||
+ | const script = params.querySelector(" | ||
+ | const iv = +params.querySelector(" | ||
+ | const riskfree_rate = +params.querySelector(" | ||
+ | |||
+ | const pos = parseScript(script, | ||
+ | const [x, y, z] = simulate(pos, | ||
+ | |||
+ | Plotly.newPlot(' | ||
+ | x: x, | ||
+ | y: y, | ||
+ | z: z, | ||
+ | zmid: 0, | ||
+ | colorscale: [ | ||
+ | [0.0, ' | ||
+ | [0.49, ' | ||
+ | [0.5, ' | ||
+ | [0.51, ' | ||
+ | [1.0, ' | ||
+ | ], | ||
+ | type: ' | ||
+ | |||
+ | hovertemplate: | ||
+ | }], { | ||
+ | title: {text: " | ||
+ | xaxis: { | ||
+ | title: {text: "Days Left" | ||
+ | autorange: " | ||
+ | fixedrange: true, | ||
+ | }, | ||
+ | yaxis: { | ||
+ | title: {text: " | ||
+ | fixedrange: true, | ||
+ | }, | ||
+ | dragmode: false, | ||
+ | }); | ||
+ | }; | ||
+ | |||
+ | // ---- main routine ---- | ||
+ | const script = document.createElement(' | ||
+ | script.src = " | ||
+ | script.async = true; | ||
+ | script.onload = ()=>{ | ||
+ | document.querySelector("# | ||
+ | update(); | ||
+ | }; | ||
document.body.appendChild(script); | document.body.appendChild(script); | ||
Line 30: | Line 191: | ||
</ | </ | ||
</ | </ | ||
+ | |||
+ | ===== How To Use ===== | ||
+ | |||
+ | 単体の建玉は以下の記法で記述できます。 | ||
+ | 単体の建玉記述を1行ずつ記述することで、複数の建玉を記述することができます。 | ||
+ | |||
+ | < | ||
+ | (S/ | ||
+ | S/L: Short or Long | ||
+ | C/P: Call or Put | ||
+ | strike | ||
+ | open_at | ||
+ | open_price: price at open | ||
+ | </ | ||
+ | |||
+ | ==== 例 ==== | ||
+ | |||
+ | オプション戦略については[[option-strategy]]を参照。 | ||
+ | |||
+ | === Short Iron Butterfly === | ||
+ | |||
+ | < | ||
+ | SC@21000# | ||
+ | LC@20000# | ||
+ | LP@20000# | ||
+ | SP@19000# | ||
+ | </ |