Skip to content

Instantly share code, notes, and snippets.

@va2577
Created May 5, 2018 07:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save va2577/997fc9c073071408b7e5c2088718967e to your computer and use it in GitHub Desktop.
Save va2577/997fc9c073071408b7e5c2088718967e to your computer and use it in GitHub Desktop.
口座残高の推移のシミュレーション
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>simulation</title>
<style>
input#simulate {
width: 100%;
}
input.error {
background-color: red;
}
span#message {
color: red;
}
</style>
</head>
<body>
<div>
<form>
<table>
<tr>
<th>定率</th>
<td><input type="text" id="fr" placeholder="0.02" value="0.02"></td>
</tr>
<tr>
<th>口座残高</th>
<td><input type="text" id="balance" placeholder="1000000" value="1000000"></td>
</tr>
<tr>
<th>勝率</th>
<td><input type="text" id="wr" placeholder="0.30" value="0.30"></td>
</tr>
<tr>
<th>平均利益</th>
<td><input type="text" id="ap" placeholder="3" value="3"></td>
</tr>
<tr>
<th>平均損失</th>
<td><input type="text" id="al" placeholder="1" value="1"></td>
</tr>
<tr>
<th>機会</th>
<td><input type="text" id="opportunity" placeholder="10" value="10"></td>
</tr>
<tr>
<th>試行回数</th>
<td><input type="text" id="count" placeholder="100" value="100"></td>
</tr>
<tr>
<td colspan="2"><input type="button" id="simulate" value="シミュレート"></td>
</tr>
</table>
</form>
<span id="message"></span>
</div>
<div height="320" width="640">
<canvas id="canvas"></canvas>
</div>
<div>
<table>
<tr>
<th>口座残高</th>
<td><span id="rb">0</span></td>
</tr>
<tr>
<th>勝率</th>
<td><span id="rwr">0</span></td>
</tr>
<tr>
<th>期待値</th>
<td><span id="rev">0</span></td>
</tr>
<tr>
<th>平均月利</th>
<td><span id="rm">0</span></td>
</tr>
<tr>
<th>平均年利</th>
<td><span id="ra">0</span></td>
</tr>
</table>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<script>
const local = {
chart: null,
comma: v => v.toLocaleString(),
validate: ids => {
document.getElementById('message').textContent = '';
const entered = (a, c) => {
const e = document.getElementById(c);
const v = e.value;
const isValid = !(v.length === 0 || isNaN(v));
e.className = isValid ? '' : 'error';
return a && isValid;
};
if (!ids.reduce(entered, true)) {
document.getElementById('message').textContent = 'パラメーターを数字で入力してください。';
return false;
}
if (Number(document.getElementById('count').value) > 2000) {
document.getElementById('message').textContent = '試行回数は 2000 以内で入力してください。';
return false;
}
return true;
},
parameters: ids => {
return ids.reduce((a, c) => {
a[c] = Number(document.getElementById(c).value);
return a;
}, {});
},
rrr: (ap, al) => ap / al,
wl: wr => Math.random() < wr ? 1 : -1,
pl: (fr, balance, wl, rrr) => Math.round(fr * balance * (wl === 1 ? rrr : -1), 0),
last: array => array[array.length - 1],
balance: (balance, pl) => balance + pl,
wr: wls => local.precisionRound(wls.reduce((a, c) => a + (c === 1 ? 1 : 0), 0) / wls.length, 2),
precisionRound: (number, precision) => {
const factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
},
ev: (wr, rrr) => local.precisionRound((wr * rrr / 1) - (1 - wr * (1 / 1)), 2),
pr: (balance, opportunity, monthly, annual) => {
const times = monthly ? opportunity : annual ? opportunity * 12 : 0;
// if (balance.length - 1 < times) return 0;
const b = [];
for (let i = 0; i < Math.ceil(balance.length / times); i++) {
b.push(balance.slice(i * times, (i + 1) * times + 1));
}
const prs = b.filter(x => x.length > 1)
.map(x => (local.last(x) - x[0]) / x[0]);
const apr = local.precisionRound(prs.reduce((a, c) => a + c, 0) / prs.length, 2);
return apr;
},
simulate: () => {
const ids = ['fr', 'balance', 'wr', 'ap', 'al', 'opportunity', 'count'];
if (!local.validate(ids)) return {};
const params = local.parameters(ids);
const result = {};
result.rrr = local.rrr(params.ap, params.al);
result.wl = [];
result.pl = [];
result.balance = [params.balance];
for (let i = 0; i < params.count; i++) {
result.wl.push(local.wl(params.wr));
result.pl.push(local.pl(params.fr, local.last(result.balance), local.last(result.wl), result.rrr));
result.balance.push(local.balance(local.last(result.balance), local.last(result.pl)));
}
result.wr = local.wr(result.wl);
result.ev = local.ev(result.wr, result.rrr);
result.monthly = local.pr(result.balance, params.opportunity, true, false);
result.annual = local.pr(result.balance, params.opportunity, false, true);
return result;
},
plot: () => {
const result = local.simulate();
if (!result.hasOwnProperty('balance')) return;
local.chart.data.datasets[0].data = result.balance;
local.chart.data.labels = result.balance.map((c, i, a) => i);
local.chart.update();
document.getElementById('rb').textContent = local.last(result.balance).toLocaleString();
document.getElementById('rwr').textContent = result.wr;
document.getElementById('rev').textContent = result.ev;
document.getElementById('rm').textContent = result.monthly.toLocaleString();
document.getElementById('ra').textContent = result.annual.toLocaleString();
}
};
document.getElementById('simulate').addEventListener('click', () => {
local.plot();
});
window.addEventListener('load', () => {
const ctx = document.getElementById('canvas').getContext('2d');
const data = {
datasets: [{
data: [],
fill: false,
label: '口座残高'
}],
labels: []
};
const options = {
scales: {
yAxes: [{
ticks: {
userCallback: local.comma
}
}]
},
tooltips: {
callbacks: {
label: (tooltipItem, data) => data.datasets[tooltipItem.datasetIndex].label + ': ' + local.comma(tooltipItem.yLabel)
}
}
};
local.chart = new Chart(ctx, {
data: data,
options: options,
type: 'line'
});
local.plot();
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment