Skip to content

Instantly share code, notes, and snippets.

@tedmiston
Last active June 10, 2016 03:03
Show Gist options
  • Save tedmiston/760736be62920421a864 to your computer and use it in GitHub Desktop.
Save tedmiston/760736be62920421a864 to your computer and use it in GitHub Desktop.
High-yield checking analysis
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# High-yield checking calculator\n",
"\n",
"The goal of this program is to decide how much money to invest in a high-yield checking account with tiered interest rates.\n",
"\n",
"For this project, I'm sharing my Jupyter Notebook (née IPython Notebook). I think being able to view some sample results alongside the code in a browser is a lot more valuable than just the code itself.\n",
"\n",
"You can download a copy and upload it (for free) to the awesome [tmpnb.org](http://tmpnb.org), where you can tweak the amounts and make it your own."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Setup\n",
"\n",
"But first some input params."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# 0.25% APY on balances < $2500, and 1.00% APY on any balance above (uncapped)\n",
"\n",
"APY = {\n",
" 'LOW': .0025,\n",
" 'HIGH': .01,\n",
"}\n",
"\n",
"THRESHOLD = 2499.99 # the last penny at the lower rate"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Computing interest\n",
"\n",
"No surprises here.\n",
"\n",
"Note that I choose not to switch to cents (as integers) here even though it would be more technically correct for two reasons:\n",
"\n",
"1. I'm only printing output to screen, not doing further math or storing it.\n",
"2. I'm rounding to the nearest dollar anyway."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import sys\n",
"\n",
"def interest(amount):\n",
" \"\"\"Compute interest for a high-yield checking account (USD).\"\"\"\n",
" invalid_type = not any([isinstance(amount, int),\n",
" isinstance(amount, float)])\n",
" invalid_amount = amount < 0 or amount > sys.maxint\n",
" if invalid_type or invalid_amount:\n",
" return None\n",
"\n",
" low_amount = THRESHOLD if amount > THRESHOLD else amount\n",
" low_interest = low_amount * APY['LOW']\n",
"\n",
" high_amount = amount - low_amount\n",
" high_interest = high_amount * APY['HIGH']\n",
"\n",
" return int(round(low_interest + high_interest, 0))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Aside: Generating a list of halved numbers\n",
"\n",
"*This part can be skipped if you're only interested in the problem at hand.*\n",
"\n",
"I wrote a one-liner utility function to quickly find the point where change becomes small after halving an amount repeatedly. Kind of inspired by git-bisect.\n",
"\n",
"For example, divide the number `1000` in half, `5` times:\n",
"\n",
" bisect(1000, 5) == [1000, 500, 250, 166, 125]"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def bisect(value, repetitions):\n",
" \"\"\"Divide value in half a number of repetitions.\"\"\"\n",
" return [value] + [value/(2*i) for i in xrange(1, repetitions)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Add a snazzy print function:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from IPython.display import Markdown, display\n",
"\n",
"def build_markdown_table(headers, body):\n",
" s = ' | '.join(headers) + '\\n'\n",
" s += ' | '.join(['---'] * len(headers)) + '\\n'\n",
"\n",
" for row in body:\n",
" s += ' | '.join(row) + '\\n'\n",
"\n",
" return s\n",
"\n",
"def print_table(amount, reverse=False):\n",
" headers = ['Deposit', 'Interest']\n",
" body = []\n",
" for amount in sorted(amounts, reverse=reverse):\n",
" balance_dollars = '\\${}'.format(amount)\n",
" interest_dollars = '\\${}'.format(interest(amount))\n",
" body.append([balance_dollars, interest_dollars])\n",
" \n",
" s = build_markdown_table(headers, body)\n",
" display(Markdown(s))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Show me some numbers"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/markdown": [
"Deposit | Interest\n",
"--- | ---\n",
"\\$100000 | \\$981\n",
"\\$50000 | \\$481\n",
"\\$25000 | \\$231\n",
"\\$16666 | \\$148\n",
"\\$12500 | \\$106\n",
"\\$10000 | \\$81\n",
"\\$8333 | \\$65\n",
"\\$7142 | \\$53\n",
"\\$6250 | \\$44\n",
"\\$5555 | \\$37\n",
"\\$5000 | \\$31\n",
"\\$4545 | \\$27\n",
"\\$4166 | \\$23\n",
"\\$3846 | \\$20\n",
"\\$3571 | \\$17\n",
"\\$3333 | \\$15\n",
"\\$3125 | \\$13\n",
"\\$2941 | \\$11\n",
"\\$2777 | \\$9\n",
"\\$2631 | \\$8\n",
"\\$2500 | \\$6\n",
"\\$2380 | \\$6\n",
"\\$2272 | \\$6\n",
"\\$2173 | \\$5\n",
"\\$2083 | \\$5\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"amounts = bisect(100000, 25)\n",
"print_table(amounts, reverse=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What I think is interesting is how quickly returns drop off at tiered interest rates.\n",
"\n",
"For example, let's look at the difference between investing \\$100 and \\$3000:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/markdown": [
"Deposit | Interest\n",
"--- | ---\n",
"\\$100 | \\$0\n",
"\\$200 | \\$1\n",
"\\$300 | \\$1\n",
"\\$400 | \\$1\n",
"\\$500 | \\$1\n",
"\\$600 | \\$2\n",
"\\$700 | \\$2\n",
"\\$800 | \\$2\n",
"\\$900 | \\$2\n",
"\\$1000 | \\$3\n",
"\\$1100 | \\$3\n",
"\\$1200 | \\$3\n",
"\\$1300 | \\$3\n",
"\\$1400 | \\$4\n",
"\\$1500 | \\$4\n",
"\\$1600 | \\$4\n",
"\\$1700 | \\$4\n",
"\\$1800 | \\$5\n",
"\\$1900 | \\$5\n",
"\\$2000 | \\$5\n",
"\\$2100 | \\$5\n",
"\\$2200 | \\$6\n",
"\\$2300 | \\$6\n",
"\\$2400 | \\$6\n",
"\\$2500 | \\$6\n",
"\\$2600 | \\$7\n",
"\\$2700 | \\$8\n",
"\\$2800 | \\$9\n",
"\\$2900 | \\$10\n",
"\\$3000 | \\$11\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"amounts = range(100, 3000+1, 100)\n",
"print_table(amounts)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$10 is the price of a nice lunch these days. It's not even really worth your time.\n",
"\n",
"*Of course*, this is why banks bury them in fine print. Luckily you know better ;).\n",
"\n",
"Now, let's dig into that high-yield spectrum."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/markdown": [
"Deposit | Interest\n",
"--- | ---\n",
"\\$2500 | \\$6\n",
"\\$3000 | \\$11\n",
"\\$3500 | \\$16\n",
"\\$4000 | \\$21\n",
"\\$4500 | \\$26\n",
"\\$5000 | \\$31\n",
"\\$5500 | \\$36\n",
"\\$6000 | \\$41\n",
"\\$6500 | \\$46\n",
"\\$7000 | \\$51\n",
"\\$7500 | \\$56\n",
"\\$8000 | \\$61\n",
"\\$8500 | \\$66\n",
"\\$9000 | \\$71\n",
"\\$9500 | \\$76\n",
"\\$10000 | \\$81\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"amounts = range(2500, 10000+1, 500)\n",
"print_table(amounts)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/markdown": [
"Deposit | Interest\n",
"--- | ---\n",
"\\$10000 | \\$81\n",
"\\$11000 | \\$91\n",
"\\$12000 | \\$101\n",
"\\$13000 | \\$111\n",
"\\$14000 | \\$121\n",
"\\$15000 | \\$131\n",
"\\$16000 | \\$141\n",
"\\$17000 | \\$151\n",
"\\$18000 | \\$161\n",
"\\$19000 | \\$171\n",
"\\$20000 | \\$181\n",
"\\$21000 | \\$191\n",
"\\$22000 | \\$201\n",
"\\$23000 | \\$211\n",
"\\$24000 | \\$221\n",
"\\$25000 | \\$231\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"amounts = range(10000, 25000+1, 1000)\n",
"print_table(amounts)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Test all the things"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Some \"micro unit tests\" pytest-style because unittest doesn't work elegantly\n",
"# in IPython Notebooks.\n",
"\n",
"# error cases\n",
"assert interest(-1) is None\n",
"assert interest('cat') is None\n",
"assert interest([]) is None\n",
"assert interest(['x', 'y']) is None\n",
"assert interest(None) is None\n",
"\n",
"# normal and edge cases\n",
"assert interest(100) == interest(100.0) == interest(100.00)\n",
"assert interest(0) == 0\n",
"assert interest(1) == 0\n",
"assert interest(10) == 0\n",
"assert interest(100) == 0\n",
"assert interest(200) == 1\n",
"assert interest(2499.99) == 6\n",
"assert interest(2500) == 6\n",
"assert interest(5000) == 31"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Future work\n",
"\n",
"In the future, I may add support for capped balance accounts, minimum balances, and the ability to give a true total minus account fees (the account I had in mind while creating this charges none)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.11"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment