【Swift】投資の複利計算と単利計算、積立額の比較アプリ。複利運用の威力など。
2016/06/07
そういえば以前ドット避けのアプリを申請したと書きましたが、App Storeに並んでましたのでリンク。
avoidDot (App Storeのページに遷移します)
さて、かねてより主にインデックスファンドを使ってコツコツ積立投資をしているのですが、ポートフォリオ算定に当たっていつも参考にしていたのが、期待リターンと将来到達できる額でして、これによって将来どれくらい稼げるのかということを妄想してほくそ笑んだりしているわけです。
で、複利計算できるサイトをいろいろ見ていたところ、swiftで似たようなものを作ってみたいなと思って、iPhoneとかiPadのアプリで動くようなものを作成してみました。
(以下画像が小さくて何が書いてあるか読みにくいですが、クリックするとたぶん大きく表示されます。)
アプリを立ち上げると、
"年利(%)"
"元金(万円)"
"毎月積立額(万円)"
"ボーナス積立額(万円)"
"経過年数(年)"
を入力できるテキストフィールドが出てくるので、適当に数字を入力してリターンを押すと、以下が自動的に計算されて表示されます。
"合計投資額(万円)"
"達成額(単利)(万円)"
"達成額(複利)(万円)"
それに伴って、積立額が赤、単利運用でゲットできる合計額を緑、利子を再投資して複利運用した場合に達成できる額を青で表示するグラフも自動的に出てきます。

試しに、期待リターンが5%になる投資先に元値100万円を一括投資して、以降は毎月5万円、6月と12月のボーナス時に10万円ずつ購入するとした場合。10年後には積立額が900万円のところ、単利運用だと90万円の利子がプラスされるらしい。さらに複利だと400万円近くの不労所得が得られることになるらしいです。

20年後だと積立額1700万円に対して複利運用で3200万円、ほぼ倍に成るらしい。

30年後だとだんだんグラフの乖離がはんぱなくなってきますね。このグラフを見ると、もう単なる貯金とか単利運用なんかやってると効率が悪いので、毎月分配型の投信で利息を現金化してしまうよりも複利運用した方がよさそうに思えます。

40年後だと、もしなんの運用もしなかった場合の積立額が3300万円なのに、ただ毎月5万円とボーナス10万円を自動で買い付けてほったらかししているだけで1億円を超えて億り人になれるらしい。1億1千万円ですか。半端ない。
だいたい不労所得で8000万円くらい稼ぐ計算になるので、年単位に直すと8000万円割る40年ということで1年で200万円の不労所得を得ている計算になるのですかね。
サラリーマンの平均年収が400万円くらいらしいので、労働所得の半額が何もせずほったらかし運用してるだけでもらえるということになるのかな。
まあ毎月5万円とボーナス10万円を捻出するのはそんなに難しいことじゃないと思うし、20代のうちからなら40年の投資は可能だと思うので、早いうちから投資しておけばいい老後が送れそうですなあ。
で、以下コードなのですが、複利計算と単利計算の利子のつき方をどう設定するかで到達額に差が出てきてしまうのですが、
計算式を考えるのが面倒臭かったので、
単利の場合には、元本は毎年利息が出る。その年に投資した額には期待リターン通りの利息がかかる、前年以前に投資した額には一切利息が出ない、という感じにして、
複利の場合には、とりあえず投資額全部利息がかかってくるようにしてあります。
あと、for文の所、何年間運用したかの計算で、1年分実際とズレてるかもしれません。この辺面倒臭かったので全然検討してません。
だから同じ一年でも1月に投資した額と12月に投資した額で年末に同じ利息分もらえるみたいなありえない計算式になってますし、他のサイトで計算するよりも1年間くらい多めに出てない?という感じなるかもしれませんが、
まあ結果が現実的なものとそこまで大きくずれるわけではないと思うので、複利運用、単利運用、ただの積立の3つを比較することで複利運用に威力を直感的に理解できるという目的にはかなっていると思います。
なお、これを発展させてアプリ申請してApp Storeに並べようという意図はないので、レイアウトは適当です。
MacのシミュレータでiPhone6の画面で適正になるようにしているだけで、iPadとかで表示させると大きくずれると思います。
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
let nenriLabel: UILabel = UILabel()
let nenri: UITextField = UITextField()
let gankinLabel: UILabel = UILabel()
let gankin: UITextField = UITextField()
let maitsukiLabel: UILabel = UILabel()
let maitsuki: UITextField = UITextField()
let bonusLabel: UILabel = UILabel()
let bonus: UITextField = UITextField()
let keikaLabel: UILabel = UILabel()
let keika: UITextField = UITextField()
let ruikeiLabel: UILabel = UILabel()
let ruikei: UITextField = UITextField()
let tanriLabel: UILabel = UILabel()
let tanri: UITextField = UITextField()
let hukuriLabel: UILabel = UILabel()
let hukuri: UITextField = UITextField()
let line: UIBezierPath = UIBezierPath()
let line2: UIBezierPath = UIBezierPath()
let lineView: UIView = UIView()
let graphView: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
labelByoga(nenriLabel, i: 0)
labelByoga(gankinLabel, i: 1)
labelByoga(maitsukiLabel, i: 2)
labelByoga(bonusLabel, i: 3)
labelByoga(keikaLabel, i: 4)
labelByoga(ruikeiLabel, i: 5)
labelByoga(tanriLabel, i: 6)
labelByoga(hukuriLabel, i: 7)
nenriLabel.text = "年利(%)"
gankinLabel.text = "元金(万円)"
maitsukiLabel.text = "毎月積立額(万円)"
bonusLabel.text = "ボーナス積立額(万円)"
keikaLabel.text = "経過年数(年)"
ruikeiLabel.text = "合計投資額(万円)"
tanriLabel.text = "達成額(単利)(万円)"
hukuriLabel.text = "達成額(複利)(万円)"
byoga(nenri,i: 0)
byoga(gankin,i: 1)
byoga(maitsuki,i: 2)
byoga(bonus,i: 3)
byoga(keika,i: 4)
byoga(ruikei,i: 5)
byoga(tanri,i: 6)
byoga(hukuri,i: 7)
lineView.frame = CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
UIGraphicsBeginImageContext(CGSize(width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height))
line.moveToPoint(CGPoint(x: 0, y: 215))
line.addLineToPoint(CGPoint(x: UIScreen.mainScreen().bounds.width, y: 215))
line.stroke()
line2.moveToPoint(CGPoint(x: UIScreen.mainScreen().bounds.width-20, y: 600))
line2.addLineToPoint(CGPoint(x: 20, y: 600))
line2.addLineToPoint(CGPoint(x: 20, y: 400))
line2.stroke()
lineView.layer.contents = UIGraphicsGetImageFromCurrentImageContext().CGImage
UIGraphicsEndImageContext()
self.view.addSubview(lineView)
lineView.userInteractionEnabled = false
graphView.frame = CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
UIGraphicsBeginImageContext(CGSize(width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height))
graphView.userInteractionEnabled = false
}
func labelByoga(label: UILabel, i :CGFloat) -> () {
label.frame = CGRectMake(20, 20+40*i, 200, 40)
label.font = UIFont.systemFontOfSize(14)
self.view.addSubview(label)
}
func byoga(textField: UITextField, i: CGFloat) -> () {
textField.frame = CGRectMake(230, 20+40*i, 120, 30)
textField.borderStyle = UITextBorderStyle.RoundedRect
textField.backgroundColor = UIColor(red: 0, green: 0.5, blue: 0.5, alpha: 0.3)
textField.delegate = self
textField.tag = Int(i)+1
self.view.addSubview(textField)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
let a: Float = checkTextField(nenri)
let b: Float = checkTextField(gankin)
let c: Float = checkTextField(maitsuki)
let d: Float = checkTextField(bonus)
let e: Float = checkTextField(keika)
var f: Float = b
var g: Float = b
let ruikeiLine: UIBezierPath = UIBezierPath()
let tanriLine: UIBezierPath = UIBezierPath()
let hukuriLine: UIBezierPath = UIBezierPath()
let ruikeiValue = b+c*12*e+d*2*e
ruikei.text = "\(ruikeiValue)"
UIGraphicsBeginImageContext(CGSize(width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height))
for var i = 0; i < Int(e); i++ {
f = f+b*a*0.01+c*12*(1+a*0.01)+d*2*(1+a*0.01)
}
for var i = 0; i < Int(e); i++ {
g = g*(1+a*0.01)+b*a*0.01+c*12*(1+a*0.01)+d*2*(1+a*0.01)
}
tanri.text = "\(f)"
hukuri.text = "\(g)"
let jogen = g/200
let xGage = (Float(UIScreen.mainScreen().bounds.width)-40)/e
ruikeiLine.moveToPoint(CGPoint(x: 20, y: 600-CGFloat(b)/CGFloat(jogen)))
ruikeiLine.addLineToPoint(CGPoint(x: UIScreen.mainScreen().bounds.width-20, y: 600-CGFloat(ruikeiValue)/CGFloat(jogen)))
UIColor.redColor().setStroke()
ruikeiLine.stroke()
f = b
tanriLine.moveToPoint(CGPoint(x: 20, y: 600-CGFloat(b)/CGFloat(jogen)))
for var i = 0; i < Int(e); i++ {
f = f+b*a*0.01+c*12*(1+a*0.01)+d*2*(1+a*0.01)
tanriLine.addLineToPoint(CGPoint(x: CGFloat(20+xGage*Float(i+1)), y: 600-CGFloat(f)/CGFloat(jogen)))
}
UIColor.greenColor().setStroke()
tanriLine.stroke()
g = b
hukuriLine.moveToPoint(CGPoint(x: 20, y: 600-CGFloat(b)/CGFloat(jogen)))
for var i = 0; i < Int(e); i++ {
g = g*(1+a*0.01)+b*a*0.01+c*12*(1+a*0.01)+d*2*(1+a*0.01)
hukuriLine.addLineToPoint(CGPoint(x: CGFloat(20+xGage*Float(i+1)), y: 600-CGFloat(g)/CGFloat(jogen)))
}
UIColor.blueColor().setStroke()
hukuriLine.stroke()
graphView.layer.contents = UIGraphicsGetImageFromCurrentImageContext().CGImage
UIGraphicsEndImageContext()
self.view.addSubview(graphView)
print(textField.text)
return true
}
func drawGraph() {
lineView.frame = CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
UIGraphicsBeginImageContext(CGSize(width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height))
line.moveToPoint(CGPoint(x: 0, y: 215))
line.addLineToPoint(CGPoint(x: UIScreen.mainScreen().bounds.width, y: 215))
line.stroke()
lineView.layer.contents = UIGraphicsGetImageFromCurrentImageContext().CGImage
UIGraphicsEndImageContext()
self.view.addSubview(lineView)
lineView.userInteractionEnabled = false
}
func checkTextField(newTextField: UITextField) -> (Float) {
if newTextField.text == "" {
newTextField.text = "0"
}
return Float(newTextField.text!)!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}