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:52] – 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=" | ||
+ | <div id=" | ||
+ | < | ||
+ | LC@21750# | ||
+ | SC@21500# | ||
+ | <div style=" | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <div id=" | ||
< | < | ||
document.addEventListener(" | document.addEventListener(" | ||
+ | // error function | ||
const erf = (x)=>{ | const erf = (x)=>{ | ||
const p = 0.3275911; | const p = 0.3275911; | ||
Line 24: | Line 42: | ||
const normCDF = (x)=>(1 + erf(x / Math.sqrt(2))) / 2; | const normCDF = (x)=>(1 + erf(x / Math.sqrt(2))) / 2; | ||
+ | // formula to calculate option price | ||
const blackScholes = (S, K, T, r, sigma, type = ' | const blackScholes = (S, K, T, r, sigma, type = ' | ||
// S : 現在の株価 | // S : 現在の株価 | ||
Line 35: | Line 54: | ||
if (type === ' | if (type === ' | ||
- | return S * normCDF(d1) - K * Math.exp(-r * T) * normCDF(d2); | + | return |
} else if (type === ' | } else if (type === ' | ||
- | return K * Math.exp(-r * T) * normCDF(-d2) - S * normCDF(-d1); | + | return |
} else { | } else { | ||
throw new Error(" | throw new Error(" | ||
Line 44: | Line 63: | ||
// simulation function | // simulation function | ||
- | const simulate = ()=>{ | + | const simulate = (pos, r)=>{ |
- | const priceMin | + | const priceMin = Math.min(...pos.map(x => x.strike))*0.8; |
- | const priceMax | + | const priceMax = Math.max(...pos.map(x => x.strike))*1.2; |
- | const priceStep = 50; | + | const priceStep = (priceMax - priceMin)/ |
- | | + | |
- | | + | |
- | const r = 0.0; | + | prices.push(S); |
- | | + | } |
+ | | ||
+ | const dayMax | ||
+ | | ||
+ | for (let T = 0; T <= dayMax; ++T) { | ||
+ | days.push(T); | ||
+ | } | ||
- | let ret = []; | + | let data = []; |
- | for (let p = priceMin; p <= priceMax; p+=priceStep) { | + | for (const S of prices) { |
let row = []; | let row = []; | ||
- | for (let d = dayMax; d > 0; --d) { | + | for (const T of days) { |
- | | + | |
+ | for (const p of pos) { | ||
+ | | ||
+ | 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 === " | ||
+ | open_vol - curr_vol: curr_vol - open_vol); | ||
+ | } | ||
+ | } | ||
+ | row.push(pnl); | ||
} | } | ||
- | | + | |
} | } | ||
- | return | + | return |
}; | }; | ||
- | // 1. scriptタグを作成 | + | // 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 " | ||
+ | |||
+ | pos.push(p); | ||
+ | } | ||
+ | return pos; | ||
+ | }; | ||
+ | |||
+ | // re-paint plot | ||
+ | const update = ()=>{ | ||
+ | const params = document.querySelector("# | ||
+ | const script | ||
+ | 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(' | const script = document.createElement(' | ||
script.src = " | script.src = " | ||
script.async = true; | script.async = true; | ||
- | + | script.onload = ()=>{ | |
- | // 2. 読み込み完了時の処理 | + | |
- | script.onload = () => { | + | |
- | // ここでPlotlyを使ったヒートマップ描画などが可能 | + | |
- | Plotly.newPlot(' | + | |
- | z: simulate(), | + | |
- | type: ' | + | |
- | }]); | + | |
}; | }; | ||
- | |||
- | // 3. bodyに追加してロード開始 | ||
document.body.appendChild(script); | document.body.appendChild(script); | ||
Line 84: | 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# | ||
+ | </ |