├── tesouro ├── direto │ ├── __init__.py │ ├── importer.py │ ├── calculator.py │ ├── reporter.py │ └── client.py ├── __init__.py ├── templates │ ├── email.html │ └── brokerage.html ├── dates.py └── holidays.csv ├── requirements-test.txt ├── MANIFEST.in ├── tox.ini ├── .travis.yml ├── tests ├── conftest.py └── tesouro │ └── test_dates.py ├── .gitignore ├── LICENSE ├── setup.py └── README.rst /tesouro/direto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tesouro/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = '0.1.0' 3 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | requests 2 | mock 3 | pytest 4 | jinja2 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include tesouro/holidays.csv 2 | recursive-include tesouro/templates * 3 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27 3 | 4 | [testenv] 5 | deps = -rrequirements-test.txt 6 | commands = py.test ./tests 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - "2.7" 5 | install: 6 | - "pip install tox --use-mirrors" 7 | script: 8 | - "tox" 9 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | # Add module to the path 6 | base = os.path.abspath(os.path.dirname(__file__)) 7 | sys.path.insert(0, (os.path.join(base, '..'))) 8 | -------------------------------------------------------------------------------- /tesouro/templates/email.html: -------------------------------------------------------------------------------- 1 | 2 | {% for brokerage, titles in new.iteritems() %} 3 | {% set newt = titles %} 4 | {% if old is defined and old[brokerage] is defined %} 5 | {% set oldt = old[brokerage] %} 6 | {% else %} 7 | {% set oldt = undefined %} 8 | {% endif %} 9 | 10 | 11 |

{{ brokerage }}

12 | 13 | {% include "brokerage.html" %} 14 | {% endfor %} 15 | -------------------------------------------------------------------------------- /tesouro/direto/importer.py: -------------------------------------------------------------------------------- 1 | 2 | import requests 3 | from lxml import html as lxml_html 4 | 5 | 6 | def clear_text(text): 7 | return text.replace('R$', '').replace('.', '').replace(',', '.').strip() 8 | 9 | 10 | base = 'http://www.tesouro.fazenda.gov.br/' 11 | taxas = base + 'tesouro-direto-precos-e-taxas-dos-titulos' 12 | index = {} 13 | 14 | html = requests.get(taxas).content 15 | 16 | rows = lxml_html.fromstring(html).xpath('//tr[@class="camposTesouroDireto"]') 17 | for row in rows: 18 | # Get data from row: 19 | columns = [ 20 | 'title', 'due_date', 21 | 'tax_aa_buy', 'tax_aa_sell', 22 | 'daily_value_buy', 'daily_value_sell' 23 | ] 24 | values = map(lambda x: clear_text(x.text), row.xpath('td')) 25 | data = dict(zip(columns, values)) 26 | 27 | title = data['title'] 28 | del data['title'] 29 | 30 | index[title] = data 31 | 32 | 33 | from pprint import pprint 34 | pprint(index) 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # config file 60 | config.yml 61 | 62 | # data file 63 | data.json 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vinicius K. Ruoso 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tesouro/direto/calculator.py: -------------------------------------------------------------------------------- 1 | 2 | # jaa1 = 0.1116 3 | jaa = 0.1116 4 | jaa = 0.10 5 | 6 | days = [ 7 | 121, 8 | 247, 9 | 370, 10 | 500, 11 | 622, 12 | 753, 13 | 875, 14 | 1003, 15 | 1127, 16 | 1254 17 | ] 18 | 19 | price = 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(121/252))) 20 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(247/252))) 21 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(370/252))) 22 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(500/252))) 23 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(622/252))) 24 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(753/252))) 25 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(875/252))) 26 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(1003/252))) 27 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(1127/252))) 28 | price += 1000 * ((1.10**0.5)) * (1/((1+jaa)**(1254/252))) 29 | 30 | print price 31 | 32 | price = 0 33 | for day in days[:-1]: 34 | price += 1000 * ((1.10**0.5)-1) * (1/((1+jaa)**(day/252))) 35 | price += 1000 * ((1.10**0.5)) * (1/((1+jaa)**(days[-1]/252))) 36 | 37 | print price 38 | 39 | 40 | jsm = (1+jaa)**0.5 - 1 41 | cupom = 1000 * jsm 42 | 43 | print cupom 44 | -------------------------------------------------------------------------------- /tests/tesouro/test_dates.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import pytest 3 | 4 | from tesouro import dates 5 | 6 | 7 | class TestDates(object): 8 | 9 | @pytest.mark.parametrize("cupom_date,bank_days", [ 10 | (datetime.date(2012, 7, 1), 121), 11 | (datetime.date(2013, 1, 1), 247), 12 | (datetime.date(2013, 7, 1), 370), 13 | (datetime.date(2014, 1, 1), 500), 14 | (datetime.date(2014, 7, 1), 622), 15 | (datetime.date(2015, 1, 1), 753), 16 | (datetime.date(2015, 7, 1), 875), 17 | (datetime.date(2016, 1, 1), 1003), 18 | (datetime.date(2016, 7, 1), 1127), 19 | (datetime.date(2017, 1, 1), 1254), 20 | ]) 21 | def test_dates(self, cupom_date, bank_days): 22 | """Tests bank days implementation based on TD source. 23 | 24 | This test is based on the date table of the following document: 25 | http://www.tesouro.fazenda.gov.br/documents/10180/410323/NTN-F_novidades.pdf 26 | 27 | This can also be simulated by Excel. The document above tells you how. 28 | """ 29 | buy_date = datetime.date(2012, 1, 6) 30 | assert dates.brazilian_bank_days(buy_date, cupom_date) == bank_days 31 | -------------------------------------------------------------------------------- /tesouro/dates.py: -------------------------------------------------------------------------------- 1 | """Module to allow financial dates calculations.""" 2 | 3 | import csv 4 | import datetime 5 | import os 6 | import time 7 | 8 | __all__ = ['brazilian_bank_days'] 9 | 10 | # The holidays data 11 | holidays = [] 12 | 13 | 14 | def load_brazilian_holidays(): 15 | """Loads the Brazilian holidays dates. 16 | 17 | We are using a CSV file with bare dates in it. The format is m/d/y. 18 | To update this list with newer information, check the Anbima website. 19 | The following URL may help: http://www.anbima.com.br/feriados/feriados.asp 20 | """ 21 | path = os.path.join(os.path.dirname(__file__), 'holidays.csv') 22 | with open(path, 'r') as f: 23 | reader = csv.reader(f) 24 | reader.next() # skip header 25 | for line in reader: 26 | assert len(line) == 2 27 | date = time.strptime(''.join(line), '%m/%d/%Y') 28 | date = datetime.date(date.tm_year, date.tm_mon, date.tm_mday) 29 | holidays.append(date) 30 | 31 | 32 | def brazilian_bank_days(low, high): 33 | """Calculates the number if bank days between two dates. 34 | 35 | The interval includes the lower date, and excludes the high date. 36 | For instance, supposing all days between 3/1/2015 and 5/1/2015 are not 37 | weekends and holidays, this function should return 2. 38 | 39 | This is based on Brazilian national holiday calendar. We are discounting 40 | the weekends regardless of its holiday status. 41 | """ 42 | if not holidays: 43 | load_brazilian_holidays() 44 | 45 | cnt = 0 46 | date = low 47 | delta = datetime.timedelta(days=1) 48 | while date < high: 49 | if date.weekday() not in [5, 6] and date not in holidays: 50 | cnt = cnt + 1 51 | date = date + delta 52 | return cnt 53 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from setuptools import setup 3 | from setuptools.command.test import test as TestCommand 4 | import os 5 | import sys 6 | 7 | import tesouro 8 | 9 | 10 | class Tox(TestCommand): 11 | user_options = [('tox-args=', 'a', "Arguments to pass to tox")] 12 | 13 | def initialize_options(self): 14 | TestCommand.initialize_options(self) 15 | self.tox_args = None 16 | 17 | def finalize_options(self): 18 | TestCommand.finalize_options(self) 19 | self.test_args = [] 20 | self.test_suite = True 21 | 22 | def run_tests(self): 23 | # import here, cause outside the eggs aren't loaded 24 | import tox 25 | import shlex 26 | args = self.tox_args 27 | if args: 28 | args = shlex.split(self.tox_args) 29 | errno = tox.cmdline(args=args) 30 | sys.exit(errno) 31 | 32 | 33 | curdir = os.path.dirname(os.path.abspath(__file__)) 34 | with open(os.path.join(curdir, 'README.rst')) as readme: 35 | long_description = readme.read() 36 | 37 | setup( 38 | # Basic info 39 | name='tesouro-direto', 40 | version=tesouro.__version__, 41 | url='http://github.com/vkruoso/tesouro-direto', 42 | license='MIT License', 43 | author='Vinicius K. Ruoso', 44 | author_email='vinicius.ruoso@gmail.com', 45 | description="Tools to manage 'Tesouro Direto' information.", 46 | long_description=long_description, 47 | 48 | # Details 49 | packages=['tesouro', 'tesouro.direto'], 50 | include_package_data=True, 51 | platforms='any', 52 | install_requires=[ 53 | 'requests', 54 | 'jinja2' 55 | ], 56 | 57 | # Testing 58 | tests_require=['tox'], 59 | cmdclass={'test': Tox}, 60 | 61 | # Scripts 62 | entry_points={ 63 | }, 64 | 65 | # Information 66 | classifiers=[ 67 | 'Programming Language :: Python', 68 | 'Development Status :: 4 - Beta', 69 | 'Natural Language :: English', 70 | 'Intended Audience :: Developers', 71 | 'Intended Audience :: End Users/Desktop', 72 | 'Intended Audience :: Financial and Insurance Industry', 73 | 'License :: OSI Approved :: MIT License', 74 | 'Operating System :: OS Independent', 75 | 'Topic :: Utilities', 76 | 'Topic :: Office/Business :: Financial :: Investment', 77 | 'Topic :: Software Development :: Libraries :: Python Modules' 78 | ], 79 | ) 80 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | tesouro-direto 2 | ============== 3 | 4 | |pypi| |travis| |license| 5 | 6 | Set of tools to allow automated information recovery from your 7 | "Tesouro Direto" account. 8 | 9 | .. contents:: 10 | :local: 11 | 12 | .. |pypi| image:: https://img.shields.io/pypi/v/tesouro-direto.svg?style=flat-square 13 | :target: https://pypi.python.org/pypi/tesouro-direto 14 | 15 | .. |travis| image:: https://img.shields.io/travis/vkruoso/tesouro-direto.svg?style=flat-square 16 | :target: https://travis-ci.org/vkruoso/tesouro-direto 17 | :alt: Build Status 18 | 19 | .. |license| image:: https://img.shields.io/dub/l/vibe-d.svg?style=flat-square 20 | 21 | 22 | Installation 23 | ------------ 24 | 25 | To install the tool the easiest way is to use pip:: 26 | 27 | pip install tesouro-direto 28 | 29 | 30 | Configuration 31 | ------------- 32 | 33 | Here is a sample configuration file: 34 | 35 | .. code-block:: YAML 36 | 37 | # The credentials to access the tesourodireto.bmfbovespa.com.br website. 38 | # This is necessary to the tool to get your information and send it to you. 39 | bmfbovespa: 40 | cpf: "00000000000" 41 | password: "secret" 42 | 43 | # SMTP settings for email sending. If port is not specified, the default 44 | # value is 25. Provide the username and password if necessary. 45 | smtp: 46 | server: "mail.mydomain.com" 47 | port: 587 48 | username: "user" 49 | password: "secret" 50 | 51 | from: "me@mydomain.com" 52 | to: "you@yourdomain.com" 53 | 54 | 55 | Available tools 56 | --------------- 57 | 58 | The main goal of this module is to generate an email that keeps you updated 59 | regarding your titles. It also aims to provide some numbers that you can 60 | use to have a better idea of how your money is working for you. 61 | 62 | 63 | Email Report 64 | ++++++++++++ 65 | 66 | The email report allows you to have very in depth view of your titles. It 67 | lists them based on the brokerage and them on the titles you have. 68 | 69 | Besides that it will provide the following information: 70 | 71 | * Summaries of all your titles; 72 | * Calculations about your title current situation. 73 | 74 | Here is a sample email screenshot: 75 | (todo) 76 | 77 | 78 | Configuring Crontab 79 | ^^^^^^^^^^^^^^^^^^^ 80 | 81 | You can configure `crontab` to call the program above. This way you can have 82 | a automated email at the time and periodicity that you like. 83 | 84 | 85 | Work Days Calculation 86 | ^^^^^^^^^^^^^^^^^^^^^ 87 | 88 | There is a simple module that can calculate the amount of work days between 89 | two dates based on Brazilian national holidays. You can use it in your 90 | program if you like by importing the module. 91 | -------------------------------------------------------------------------------- /tesouro/direto/reporter.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import argparse 4 | import json 5 | import re 6 | import smtplib 7 | import yaml 8 | 9 | from datetime import date 10 | from email.mime.text import MIMEText 11 | from jinja2 import Environment, PackageLoader 12 | 13 | from tesouro.direto.client import TDClient 14 | 15 | 16 | images = { 17 | u'brasil plural': 'http://www.brasilplural.com/Site/Content/Img/logo.png', 18 | u'clear': 'https://www.clear.com.br/site/Content/styles/img/im_lg_h.png', 19 | u'cm capital': 'http://bancodata.com.br/assets/img/logos/cm-capital-markets_m.jpg', # noqa: E501 20 | u'easy': 'https://www.easynvest.com.br/Pictures/Menu/logo-menu-desktop@2x.png', # noqa: E501 21 | u'itaú': 'https://www.itau.com.br/_arquivosestaticos/Itau/defaultTheme/img/logo-itau.png', # noqa: E501 22 | u'modal': 'http://bancodata.com.br/assets/img/logos/bco-modal-sa_m.jpg', 23 | u'rico': 'https://www.rico.com.vc/Util/Image/cabecalho/logo-rico-main.png', 24 | } 25 | 26 | 27 | def format(number): 28 | if isinstance(number, basestring): 29 | number = float(re.findall("\d+\.\d+", number.replace(',', '.'))[0]) 30 | return '%.2f' % number 31 | 32 | 33 | def get_image(brokerage): 34 | for name, image in images.iteritems(): 35 | if name in brokerage.lower(): 36 | return image 37 | 38 | 39 | def diff_color(new, old, invert=False): 40 | colors = { 41 | True: 'green', 42 | False: 'red', 43 | } 44 | positive = new > old 45 | diff = '%s%.2f' % ('+' if positive else '', new-old) 46 | color = colors[positive ^ invert] 47 | return '(%s)' % (color, diff) 48 | 49 | 50 | def get_old_detail(oldt, title, new): 51 | if title in oldt and 'details' in oldt[title]: 52 | for order in oldt[title]['details']: 53 | if (new['date'] == order['date'] and 54 | new['total_titles'] == order['total_titles'] and 55 | new['buy_unit'] == order['buy_unit']): 56 | # this uniquely identify a title 57 | return order 58 | return None 59 | 60 | 61 | def diff(field, new, old, invert=False): 62 | assert field in new 63 | new = new[field] 64 | 65 | # Check if old value is valid, or return new value 66 | if old is None or field not in old: 67 | return format(new) 68 | old = old[field] 69 | 70 | # Compare both values to determine some difference 71 | if isinstance(new, float) and isinstance(old, float) and new != old: 72 | return '%.2f %s' % (new, diff_color(new, old, invert)) 73 | return format(new) 74 | 75 | 76 | class Email(object): 77 | 78 | def __init__(self, config): 79 | self.config = config 80 | 81 | def send_diff(self, old, new): 82 | environment = Environment(loader=PackageLoader('tesouro', 'templates')) 83 | environment.filters['diff'] = diff 84 | environment.filters['format'] = format 85 | environment.filters['get_image'] = get_image 86 | environment.filters['get_old_detail'] = get_old_detail 87 | template = environment.get_template('email.html') 88 | 89 | text = template.render(new=new, old=old, images=images) 90 | 91 | # Create message container 92 | msg = MIMEText(text, 'html', 'utf-8') 93 | msg['Subject'] = "Atualizações Tesouro Direto" 94 | msg['From'] = self.config['from'] 95 | msg['To'] = self.config['to'] 96 | 97 | # Send the message via SMTP server 98 | port = self.config.get('port', 23) 99 | s = smtplib.SMTP(self.config['server'], port) 100 | s.ehlo() 101 | s.starttls() 102 | s.login(self.config['username'], self.config['password']) 103 | s.sendmail(msg['From'], msg['To'], msg.as_string()) 104 | s.quit() 105 | 106 | 107 | class Reporter(object): 108 | 109 | def run_cli(self): 110 | # Parse arguments (config file location) 111 | parser = argparse.ArgumentParser() 112 | parser.add_argument("-c", "--config", default="config.yml", 113 | help="path to configuration file") 114 | parser.add_argument("-d", "--data-file", default="data.json", 115 | help="the data file") 116 | args = parser.parse_args() 117 | 118 | # Load configuration 119 | with open(args.config, 'r') as f: 120 | config = yaml.safe_load(f) 121 | 122 | self.report(config, args.data_file) 123 | 124 | def report(self, config, datafile): 125 | # Get data 126 | prev = self._get_current_data(datafile) 127 | data = self._get_new_data(config['bmfbovespa']) 128 | 129 | # Build email 130 | email = Email(config['smtp']) 131 | email.send_diff(prev, data) 132 | 133 | # Save new data to disk 134 | self._save_data(data, datafile) 135 | 136 | def _get_current_data(self, datafile): 137 | """Returns the current saved data.""" 138 | try: 139 | with open(datafile, 'r') as f: 140 | data = f.read() 141 | return json.loads(data) 142 | except IOError: 143 | return None 144 | 145 | def _get_new_data(self, config): 146 | # Build client and login 147 | client = TDClient() 148 | client.login(cpf=config['cpf'], password=config['password']) 149 | 150 | # Get titles and their details 151 | info = client.get_titles(date.today().month, date.today().year) 152 | for brokerage, titles in info.iteritems(): 153 | for name, data in titles.iteritems(): 154 | data['details'] = client.get_title_details(name, data) 155 | 156 | # Logout and return 157 | client.logout() 158 | return info 159 | 160 | def _save_data(self, titles, datafile): 161 | with open(datafile, 'w') as f: 162 | json.dump(titles, f) 163 | 164 | 165 | if __name__ == '__main__': 166 | Reporter().run_cli() 167 | -------------------------------------------------------------------------------- /tesouro/templates/brokerage.html: -------------------------------------------------------------------------------- 1 | {% macro print_diff(new, old, field, invert=False) -%} 2 | {{ field|diff(new, old, invert) }} 3 | {%- endmacro %} 4 | 5 | {% macro format(num) -%} 6 | {{ num|format }} 7 | {%- endmacro %} 8 | 9 |

Títulos

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for title, data in newt.iteritems() %} 28 | {% if oldt is defined %} 29 | {% set data_old = oldt[title] %} 30 | {% else %} 31 | {% set data_old = undefined %} 32 | {% endif %} 33 | 34 | 37 | 40 | 43 | 46 | 49 | 52 | 55 | 56 | {% endfor %} 57 | 58 |
TítuloVencimentoValor (R$)Quantidade
InvestidoBruto AtualLíquido AtualTotalBloqueada
35 | {{ title }} 36 | 38 | {{ data['due_date'] }} 39 | 41 | {{ print_diff(data, data_old, 'invested_value') }} 42 | 44 | {{ print_diff(data, data_old, 'current_gross_value') }} 45 | 47 | {{ print_diff(data, data_old, 'current_net_value') }} 48 | 50 | {{ print_diff(data, data_old, 'total_titles') }} 51 | 53 | {{ print_diff(data, data_old, 'bloqued_titles') }} 54 |
59 | 60 |

Analítico

61 | 62 | 63 | {% for title, data in newt.iteritems() %} 64 |

{{title}}

65 | 66 | 67 | 68 | 71 | 74 | 75 | 76 | 80 | 85 | 91 | 95 | 99 | 102 | 106 | 110 | 113 | 118 | 122 | 126 | 127 | 128 | 129 | 132 | 135 | 138 | 141 | 144 | 147 | 150 | 153 | 156 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | {% for detail in data.details %} 167 | {% set old_detail = oldt|get_old_detail(title, detail) %} 168 | 169 | 170 | 173 | 176 | 179 | 182 | 185 | 188 | 191 | 194 | 197 | 200 | 203 | 206 | 209 | 212 | 215 | 216 | {% endfor %} 217 | 218 | 219 |
69 | Dados do seu investimento 70 | 72 | Situação hoje (no caso de venda antecipada) 73 |
77 | Data da
78 | aplicação 79 |
81 | Qtd de
82 | títulos
83 | A 84 |
86 | Preço do
87 | título na
88 | aplicação (R$)
89 | B 90 |
92 | Valor
investido
(R$)
93 | A x B 94 |
96 | Rentabilidade
97 | contratada 98 |
100 | Rentabilidade
bruta acumulada 101 |
103 | Valor bruto
104 | C 105 |
107 | Tempo da
108 | aplicação 109 |
111 | Alíq I.R. 112 | 114 | Imposto 115 |
previsto (R$)
116 | D 117 |
119 | Taxa
devida (R$)
120 | E 121 |
123 | Valor
líquido
124 | C-D-E 125 |
130 | Acum
a.a 131 |
133 | Acum % 134 | 136 | (R$) 137 | 139 | Dias
Corridos 140 |
142 | (%) 143 | 145 | I.R. 146 | 148 | IOF 149 | 151 | BOV 152 | 154 | Cor 155 | 157 | (R$) 158 |
171 | {{ detail['date'] }} 172 | 174 | {{ format(detail.total_titles) }} 175 | 177 | {{ format(detail.buy_unit) }} 178 | 180 | {{ print_diff(detail, old_detail, 'invested_value') }} 181 | 183 | {{ detail['agreed_rate'] }} 184 | 186 | {{ print_diff(detail, old_detail, 'current_anual_rate') }} 187 | 189 | {{ print_diff(detail, old_detail, 'current_rate') }} 190 | 192 | {{ print_diff(detail, old_detail, 'gross_value') }} 193 | 195 | {{ detail['days'] }} 196 | 198 | {{ print_diff(detail, old_detail, 'ir_rate', True) }} 199 | 201 | {{ print_diff(detail, old_detail, 'ir_tax', True) }} 202 | 204 | {{ print_diff(detail, old_detail, 'iof_tax', True) }} 205 | 207 | {{ print_diff(detail, old_detail, 'bvmf_tax', True) }} 208 | 210 | {{ print_diff(detail, old_detail, 'custody_tax', True) }} 211 | 213 | {{ print_diff(detail, old_detail, 'net_value') }} 214 |
220 | 221 | {% endfor %} 222 | -------------------------------------------------------------------------------- /tesouro/direto/client.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import re 4 | import requests 5 | from datetime import datetime 6 | from lxml import html as lxml_html 7 | 8 | 9 | __all__ = ['TDClient'] 10 | 11 | 12 | def clear_text(text): 13 | text = text.replace('R$', '').replace('.', '').replace(',', '.') 14 | text = text.replace('\r', '').replace('\n', '').strip() 15 | try: 16 | return float(text) 17 | except ValueError: 18 | return text 19 | 20 | 21 | def calculate(title, data): 22 | if title.endswith('(LFT)'): 23 | return data['net_value'] - data['initial_value'] 24 | return None 25 | 26 | 27 | class TDClient(object): 28 | 29 | URL = 'https://tesourodireto.bmfbovespa.com.br/PortalInvestidor/' 30 | USER_AGENT = ( 31 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' 32 | '(KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36' 33 | ) 34 | 35 | def __init__(self): 36 | self.session = requests.Session() 37 | self.session.headers = { 38 | 'User-Agent': self.USER_AGENT, 39 | 'Referer': self.URL 40 | } 41 | 42 | # The logout URL 43 | self._logout_url = None 44 | 45 | def login(self, cpf, password): 46 | html = self.session.get(self.URL, verify=False, timeout=10).content 47 | info = lxml_html.fromstring(html) 48 | 49 | # Build post data 50 | post_data = self._build_base_post_data(info) 51 | post_data['ctl00$BodyContent$txtLogin'] = cpf 52 | post_data['ctl00$BodyContent$txtSenha'] = password 53 | post_data['ctl00$BodyContent$btnLogar'] = 'Entrar' 54 | 55 | # Submit login information 56 | resp = self.session.post(self.URL, post_data) 57 | if 'Usuario/Senha Invalido(a)!' in resp.content: 58 | raise AssertionError('Usuario/Senha Invalido(a)!') 59 | 60 | # Maintain the URL for logout 61 | self._logout_url = lxml_html.fromstring(resp.content).xpath( 62 | '//div[@id="user-logoff-desktop"]/p/a[last()]/@href')[0] 63 | 64 | def get_titles(self, month, year): 65 | # Get base statement page and build base post data 66 | statement = self.session.get(self.URL + 'extrato.aspx') 67 | statement = lxml_html.fromstring(statement.content) 68 | post_data = self._build_base_post_data(statement) 69 | 70 | # Add the query we are looking for 71 | post_data['ctl00$BodyContent$ddlMes'] = month 72 | post_data['ctl00$BodyContent$ddlAno'] = year 73 | post_data['ctl00$BodyContent$btnConsultar'] = 'Consultar' 74 | 75 | # Get data and parse the information 76 | statement = self.session.post(self.URL + 'extrato.aspx', post_data) 77 | statement = lxml_html.fromstring(statement.content) 78 | 79 | # The columns of the statement page 80 | columns = [ 81 | 'due_date', 'invested_value', 82 | 'current_gross_value', 'current_net_value', 83 | 'total_titles', 'bloqued_titles' 84 | ] 85 | keyre = re.compile("\('QS=(.*)'\)") 86 | 87 | # Find all brokerages available 88 | index = {} 89 | brokerages = statement.xpath('//p[@class="title doc"]') 90 | for brokerage in brokerages: 91 | name = brokerage.xpath('a/text()')[0] 92 | 93 | # Add an entry to this brokerage in the index 94 | data = {} 95 | index[name] = data 96 | 97 | # The data section that have information about all titles of 98 | # a brokerage is just above the paragraph with its title. 99 | section = brokerage.getparent() 100 | 101 | # Lets use the fact that the data we are looking for is in a 102 | # table line with 8 columns, so we can easily find it. 103 | rows = section.xpath('.//tr') 104 | for row in rows: 105 | tds = row.xpath('td') 106 | if len(tds) == 8: 107 | values = map(lambda x: clear_text(x.text_content()), tds[1:7]) 108 | table = dict(zip(columns, values)) 109 | 110 | # The first column is the title, that is inside a link 111 | title = clear_text(tds[0].xpath('a/text()')[0]) 112 | 113 | # The last column has the key to get data for the title 114 | value = tds[-1].xpath('a/@onclick')[0] 115 | table['key'] = keyre.search(value).group(1) 116 | 117 | # Consolidate the information 118 | data[title] = table 119 | return index 120 | 121 | def get_title_details(self, name, title): 122 | # Get data and parse the information 123 | resp = self.session.get( 124 | self.URL + 'extrato-analitico.aspx?QS=%s' % title['key'] 125 | ) 126 | details = lxml_html.fromstring(resp.content) 127 | 128 | # The columns of the details page 129 | columns = [ 130 | 'date', 'total_titles', 'buy_unit', 'invested_value', 131 | 'agreed_rate', 'current_anual_rate', 'graph', 'current_rate', 132 | 'gross_value', 'days', 'ir_rate', 'ir_tax', 'iof_tax', 133 | 'bvmf_tax', 'custody_tax', 'net_value' 134 | ] 135 | 136 | # Lets use the fact that the data we are looking for is in a table 137 | # line with 17 columns and class 'nowrap', so we can easily find it. 138 | index = [] 139 | rows = details.xpath('//tr[@class="nowrap"]') 140 | for row in rows: 141 | tds = row.xpath('td') 142 | if len(tds) == 16: 143 | values = map(lambda x: clear_text(x.text), tds) 144 | data = dict(zip(columns, values)) 145 | 146 | # Consolidate the information 147 | index.append(data) 148 | 149 | index.sort(cmp=self._date_cmp, key=self._date_key) 150 | return index 151 | 152 | def _date_key(self, value): 153 | return value['date'] 154 | 155 | def _date_cmp(self, a, b): 156 | date_a = datetime.strptime(a, '%d/%m/%Y') 157 | date_b = datetime.strptime(b, '%d/%m/%Y') 158 | if date_a == date_b: 159 | return 0 160 | if date_a < date_b: 161 | return -1 162 | return 1 163 | 164 | def logout(self): 165 | if self._logout_url: 166 | return self.session.get(self.URL + self._logout_url) 167 | return None 168 | 169 | def _build_base_post_data(self, current): 170 | """Generic post data builder with common fields in the forms.""" 171 | post_data = {} 172 | fields = [ 173 | '__VIEWSTATE', 174 | '__VIEWSTATEGENERATOR', 175 | '__EVENTVALIDATION', 176 | '__EVENTTARGET', 177 | '__EVENTARGUMENT', 178 | ('BodyContent_hdnCamposRequeridos', 179 | 'ctl00$BodyContent$hdnCamposRequeridos') 180 | ] 181 | for field in fields: 182 | # Get input id and name 183 | try: 184 | (id_, name_) = field 185 | except ValueError: 186 | id_ = name_ = field 187 | 188 | # Get value and set in the post data 189 | input_ = current.xpath('//input[@id="%s"]' % id_) 190 | if input_: 191 | value = input_[0].value 192 | post_data[name_] = value 193 | return post_data 194 | -------------------------------------------------------------------------------- /tesouro/holidays.csv: -------------------------------------------------------------------------------- 1 | date, 2 | 01/01/2001, 3 | 02/26/2001, 4 | 02/27/2001, 5 | 04/13/2001, 6 | 04/21/2001, 7 | 05/01/2001, 8 | 06/14/2001, 9 | 09/07/2001, 10 | 10/12/2001, 11 | 11/02/2001, 12 | 11/15/2001, 13 | 12/25/2001, 14 | 01/01/2002, 15 | 02/11/2002, 16 | 02/12/2002, 17 | 03/29/2002, 18 | 04/21/2002, 19 | 05/01/2002, 20 | 05/30/2002, 21 | 09/07/2002, 22 | 10/12/2002, 23 | 11/02/2002, 24 | 11/15/2002, 25 | 12/25/2002, 26 | 01/01/2003, 27 | 03/03/2003, 28 | 03/04/2003, 29 | 04/18/2003, 30 | 04/21/2003, 31 | 05/01/2003, 32 | 06/19/2003, 33 | 09/07/2003, 34 | 10/12/2003, 35 | 11/02/2003, 36 | 11/15/2003, 37 | 12/25/2003, 38 | 01/01/2004, 39 | 02/23/2004, 40 | 02/24/2004, 41 | 04/09/2004, 42 | 04/21/2004, 43 | 05/01/2004, 44 | 06/10/2004, 45 | 09/07/2004, 46 | 10/12/2004, 47 | 11/02/2004, 48 | 11/15/2004, 49 | 12/25/2004, 50 | 01/01/2005, 51 | 02/07/2005, 52 | 02/08/2005, 53 | 03/25/2005, 54 | 04/21/2005, 55 | 05/01/2005, 56 | 05/26/2005, 57 | 09/07/2005, 58 | 10/12/2005, 59 | 11/02/2005, 60 | 11/15/2005, 61 | 12/25/2005, 62 | 01/01/2006, 63 | 02/27/2006, 64 | 02/28/2006, 65 | 04/14/2006, 66 | 04/21/2006, 67 | 05/01/2006, 68 | 06/15/2006, 69 | 09/07/2006, 70 | 10/12/2006, 71 | 11/02/2006, 72 | 11/15/2006, 73 | 12/25/2006, 74 | 01/01/2007, 75 | 02/19/2007, 76 | 02/20/2007, 77 | 04/06/2007, 78 | 04/21/2007, 79 | 05/01/2007, 80 | 06/07/2007, 81 | 09/07/2007, 82 | 10/12/2007, 83 | 11/02/2007, 84 | 11/15/2007, 85 | 12/25/2007, 86 | 01/01/2008, 87 | 02/04/2008, 88 | 02/05/2008, 89 | 03/21/2008, 90 | 04/21/2008, 91 | 05/01/2008, 92 | 05/22/2008, 93 | 09/07/2008, 94 | 10/12/2008, 95 | 11/02/2008, 96 | 11/15/2008, 97 | 12/25/2008, 98 | 01/01/2009, 99 | 02/23/2009, 100 | 02/24/2009, 101 | 04/10/2009, 102 | 04/21/2009, 103 | 05/01/2009, 104 | 06/11/2009, 105 | 09/07/2009, 106 | 10/12/2009, 107 | 11/02/2009, 108 | 11/15/2009, 109 | 12/25/2009, 110 | 01/01/2010, 111 | 02/15/2010, 112 | 02/16/2010, 113 | 04/02/2010, 114 | 04/21/2010, 115 | 05/01/2010, 116 | 06/03/2010, 117 | 09/07/2010, 118 | 10/12/2010, 119 | 11/02/2010, 120 | 11/15/2010, 121 | 12/25/2010, 122 | 01/01/2011, 123 | 03/07/2011, 124 | 03/08/2011, 125 | 04/21/2011, 126 | 04/22/2011, 127 | 05/01/2011, 128 | 06/23/2011, 129 | 09/07/2011, 130 | 10/12/2011, 131 | 11/02/2011, 132 | 11/15/2011, 133 | 12/25/2011, 134 | 01/01/2012, 135 | 02/20/2012, 136 | 02/21/2012, 137 | 04/06/2012, 138 | 04/21/2012, 139 | 05/01/2012, 140 | 06/07/2012, 141 | 09/07/2012, 142 | 10/12/2012, 143 | 11/02/2012, 144 | 11/15/2012, 145 | 12/25/2012, 146 | 01/01/2013, 147 | 02/11/2013, 148 | 02/12/2013, 149 | 03/29/2013, 150 | 04/21/2013, 151 | 05/01/2013, 152 | 05/30/2013, 153 | 09/07/2013, 154 | 10/12/2013, 155 | 11/02/2013, 156 | 11/15/2013, 157 | 12/25/2013, 158 | 01/01/2014, 159 | 03/03/2014, 160 | 03/04/2014, 161 | 04/18/2014, 162 | 04/21/2014, 163 | 05/01/2014, 164 | 06/19/2014, 165 | 09/07/2014, 166 | 10/12/2014, 167 | 11/02/2014, 168 | 11/15/2014, 169 | 12/25/2014, 170 | 01/01/2015, 171 | 02/16/2015, 172 | 02/17/2015, 173 | 04/03/2015, 174 | 04/21/2015, 175 | 05/01/2015, 176 | 06/04/2015, 177 | 09/07/2015, 178 | 10/12/2015, 179 | 11/02/2015, 180 | 11/15/2015, 181 | 12/25/2015, 182 | 01/01/2016, 183 | 02/08/2016, 184 | 02/09/2016, 185 | 03/25/2016, 186 | 04/21/2016, 187 | 05/01/2016, 188 | 05/26/2016, 189 | 09/07/2016, 190 | 10/12/2016, 191 | 11/02/2016, 192 | 11/15/2016, 193 | 12/25/2016, 194 | 01/01/2017, 195 | 02/27/2017, 196 | 02/28/2017, 197 | 04/14/2017, 198 | 04/21/2017, 199 | 05/01/2017, 200 | 06/15/2017, 201 | 09/07/2017, 202 | 10/12/2017, 203 | 11/02/2017, 204 | 11/15/2017, 205 | 12/25/2017, 206 | 01/01/2018, 207 | 02/12/2018, 208 | 02/13/2018, 209 | 03/30/2018, 210 | 04/21/2018, 211 | 05/01/2018, 212 | 05/31/2018, 213 | 09/07/2018, 214 | 10/12/2018, 215 | 11/02/2018, 216 | 11/15/2018, 217 | 12/25/2018, 218 | 01/01/2019, 219 | 03/04/2019, 220 | 03/05/2019, 221 | 04/19/2019, 222 | 04/21/2019, 223 | 05/01/2019, 224 | 06/20/2019, 225 | 09/07/2019, 226 | 10/12/2019, 227 | 11/02/2019, 228 | 11/15/2019, 229 | 12/25/2019, 230 | 01/01/2020, 231 | 02/24/2020, 232 | 02/25/2020, 233 | 04/10/2020, 234 | 04/21/2020, 235 | 05/01/2020, 236 | 06/11/2020, 237 | 09/07/2020, 238 | 10/12/2020, 239 | 11/02/2020, 240 | 11/15/2020, 241 | 12/25/2020, 242 | 01/01/2021, 243 | 02/15/2021, 244 | 02/16/2021, 245 | 04/02/2021, 246 | 04/21/2021, 247 | 05/01/2021, 248 | 06/03/2021, 249 | 09/07/2021, 250 | 10/12/2021, 251 | 11/02/2021, 252 | 11/15/2021, 253 | 12/25/2021, 254 | 01/01/2022, 255 | 02/28/2022, 256 | 03/01/2022, 257 | 04/15/2022, 258 | 04/21/2022, 259 | 05/01/2022, 260 | 06/16/2022, 261 | 09/07/2022, 262 | 10/12/2022, 263 | 11/02/2022, 264 | 11/15/2022, 265 | 12/25/2022, 266 | 01/01/2023, 267 | 02/20/2023, 268 | 02/21/2023, 269 | 04/07/2023, 270 | 04/21/2023, 271 | 05/01/2023, 272 | 06/08/2023, 273 | 09/07/2023, 274 | 10/12/2023, 275 | 11/02/2023, 276 | 11/15/2023, 277 | 12/25/2023, 278 | 01/01/2024, 279 | 02/12/2024, 280 | 02/13/2024, 281 | 03/29/2024, 282 | 04/21/2024, 283 | 05/01/2024, 284 | 05/30/2024, 285 | 09/07/2024, 286 | 10/12/2024, 287 | 11/02/2024, 288 | 11/15/2024, 289 | 12/25/2024, 290 | 01/01/2025, 291 | 03/03/2025, 292 | 03/04/2025, 293 | 04/18/2025, 294 | 04/21/2025, 295 | 05/01/2025, 296 | 06/19/2025, 297 | 09/07/2025, 298 | 10/12/2025, 299 | 11/02/2025, 300 | 11/15/2025, 301 | 12/25/2025, 302 | 01/01/2026, 303 | 02/16/2026, 304 | 02/17/2026, 305 | 04/03/2026, 306 | 04/21/2026, 307 | 05/01/2026, 308 | 06/04/2026, 309 | 09/07/2026, 310 | 10/12/2026, 311 | 11/02/2026, 312 | 11/15/2026, 313 | 12/25/2026, 314 | 01/01/2027, 315 | 02/08/2027, 316 | 02/09/2027, 317 | 03/26/2027, 318 | 04/21/2027, 319 | 05/01/2027, 320 | 05/27/2027, 321 | 09/07/2027, 322 | 10/12/2027, 323 | 11/02/2027, 324 | 11/15/2027, 325 | 12/25/2027, 326 | 01/01/2028, 327 | 02/28/2028, 328 | 02/29/2028, 329 | 04/14/2028, 330 | 04/21/2028, 331 | 05/01/2028, 332 | 06/15/2028, 333 | 09/07/2028, 334 | 10/12/2028, 335 | 11/02/2028, 336 | 11/15/2028, 337 | 12/25/2028, 338 | 01/01/2029, 339 | 02/12/2029, 340 | 02/13/2029, 341 | 03/30/2029, 342 | 04/21/2029, 343 | 05/01/2029, 344 | 05/31/2029, 345 | 09/07/2029, 346 | 10/12/2029, 347 | 11/02/2029, 348 | 11/15/2029, 349 | 12/25/2029, 350 | 01/01/2030, 351 | 03/04/2030, 352 | 03/05/2030, 353 | 04/19/2030, 354 | 04/21/2030, 355 | 05/01/2030, 356 | 06/20/2030, 357 | 09/07/2030, 358 | 10/12/2030, 359 | 11/02/2030, 360 | 11/15/2030, 361 | 12/25/2030, 362 | 01/01/2031, 363 | 02/24/2031, 364 | 02/25/2031, 365 | 04/11/2031, 366 | 04/21/2031, 367 | 05/01/2031, 368 | 06/12/2031, 369 | 09/07/2031, 370 | 10/12/2031, 371 | 11/02/2031, 372 | 11/15/2031, 373 | 12/25/2031, 374 | 01/01/2032, 375 | 02/09/2032, 376 | 02/10/2032, 377 | 03/26/2032, 378 | 04/21/2032, 379 | 05/01/2032, 380 | 05/27/2032, 381 | 09/07/2032, 382 | 10/12/2032, 383 | 11/02/2032, 384 | 11/15/2032, 385 | 12/25/2032, 386 | 01/01/2033, 387 | 02/28/2033, 388 | 03/01/2033, 389 | 04/15/2033, 390 | 04/21/2033, 391 | 05/01/2033, 392 | 06/16/2033, 393 | 09/07/2033, 394 | 10/12/2033, 395 | 11/02/2033, 396 | 11/15/2033, 397 | 12/25/2033, 398 | 01/01/2034, 399 | 02/20/2034, 400 | 02/21/2034, 401 | 04/07/2034, 402 | 04/21/2034, 403 | 05/01/2034, 404 | 06/08/2034, 405 | 09/07/2034, 406 | 10/12/2034, 407 | 11/02/2034, 408 | 11/15/2034, 409 | 12/25/2034, 410 | 01/01/2035, 411 | 02/05/2035, 412 | 02/06/2035, 413 | 03/23/2035, 414 | 04/21/2035, 415 | 05/01/2035, 416 | 05/24/2035, 417 | 09/07/2035, 418 | 10/12/2035, 419 | 11/02/2035, 420 | 11/15/2035, 421 | 12/25/2035, 422 | 01/01/2036, 423 | 02/25/2036, 424 | 02/26/2036, 425 | 04/11/2036, 426 | 04/21/2036, 427 | 05/01/2036, 428 | 06/12/2036, 429 | 09/07/2036, 430 | 10/12/2036, 431 | 11/02/2036, 432 | 11/15/2036, 433 | 12/25/2036, 434 | 01/01/2037, 435 | 02/16/2037, 436 | 02/17/2037, 437 | 04/03/2037, 438 | 04/21/2037, 439 | 05/01/2037, 440 | 06/04/2037, 441 | 09/07/2037, 442 | 10/12/2037, 443 | 11/02/2037, 444 | 11/15/2037, 445 | 12/25/2037, 446 | 01/01/2038, 447 | 03/08/2038, 448 | 03/09/2038, 449 | 04/21/2038, 450 | 04/23/2038, 451 | 05/01/2038, 452 | 06/24/2038, 453 | 09/07/2038, 454 | 10/12/2038, 455 | 11/02/2038, 456 | 11/15/2038, 457 | 12/25/2038, 458 | 01/01/2039, 459 | 02/21/2039, 460 | 02/22/2039, 461 | 04/08/2039, 462 | 04/21/2039, 463 | 05/01/2039, 464 | 06/09/2039, 465 | 09/07/2039, 466 | 10/12/2039, 467 | 11/02/2039, 468 | 11/15/2039, 469 | 12/25/2039, 470 | 01/01/2040, 471 | 02/13/2040, 472 | 02/14/2040, 473 | 03/30/2040, 474 | 04/21/2040, 475 | 05/01/2040, 476 | 05/31/2040, 477 | 09/07/2040, 478 | 10/12/2040, 479 | 11/02/2040, 480 | 11/15/2040, 481 | 12/25/2040, 482 | 01/01/2041, 483 | 03/04/2041, 484 | 03/05/2041, 485 | 04/19/2041, 486 | 04/21/2041, 487 | 05/01/2041, 488 | 06/20/2041, 489 | 09/07/2041, 490 | 10/12/2041, 491 | 11/02/2041, 492 | 11/15/2041, 493 | 12/25/2041, 494 | 01/01/2042, 495 | 02/17/2042, 496 | 02/18/2042, 497 | 04/04/2042, 498 | 04/21/2042, 499 | 05/01/2042, 500 | 06/05/2042, 501 | 09/07/2042, 502 | 10/12/2042, 503 | 11/02/2042, 504 | 11/15/2042, 505 | 12/25/2042, 506 | 01/01/2043, 507 | 02/09/2043, 508 | 02/10/2043, 509 | 03/27/2043, 510 | 04/21/2043, 511 | 05/01/2043, 512 | 05/28/2043, 513 | 09/07/2043, 514 | 10/12/2043, 515 | 11/02/2043, 516 | 11/15/2043, 517 | 12/25/2043, 518 | 01/01/2044, 519 | 02/29/2044, 520 | 03/01/2044, 521 | 04/15/2044, 522 | 04/21/2044, 523 | 05/01/2044, 524 | 06/16/2044, 525 | 09/07/2044, 526 | 10/12/2044, 527 | 11/02/2044, 528 | 11/15/2044, 529 | 12/25/2044, 530 | 01/01/2045, 531 | 02/20/2045, 532 | 02/21/2045, 533 | 04/07/2045, 534 | 04/21/2045, 535 | 05/01/2045, 536 | 06/08/2045, 537 | 09/07/2045, 538 | 10/12/2045, 539 | 11/02/2045, 540 | 11/15/2045, 541 | 12/25/2045, 542 | 01/01/2046, 543 | 02/05/2046, 544 | 02/06/2046, 545 | 03/23/2046, 546 | 04/21/2046, 547 | 05/01/2046, 548 | 05/24/2046, 549 | 09/07/2046, 550 | 10/12/2046, 551 | 11/02/2046, 552 | 11/15/2046, 553 | 12/25/2046, 554 | 01/01/2047, 555 | 02/25/2047, 556 | 02/26/2047, 557 | 04/12/2047, 558 | 04/21/2047, 559 | 05/01/2047, 560 | 06/13/2047, 561 | 09/07/2047, 562 | 10/12/2047, 563 | 11/02/2047, 564 | 11/15/2047, 565 | 12/25/2047, 566 | 01/01/2048, 567 | 02/17/2048, 568 | 02/18/2048, 569 | 04/03/2048, 570 | 04/21/2048, 571 | 05/01/2048, 572 | 06/04/2048, 573 | 09/07/2048, 574 | 10/12/2048, 575 | 11/02/2048, 576 | 11/15/2048, 577 | 12/25/2048, 578 | 01/01/2049, 579 | 03/01/2049, 580 | 03/02/2049, 581 | 04/16/2049, 582 | 04/21/2049, 583 | 05/01/2049, 584 | 06/17/2049, 585 | 09/07/2049, 586 | 10/12/2049, 587 | 11/02/2049, 588 | 11/15/2049, 589 | 12/25/2049, 590 | 01/01/2050, 591 | 02/21/2050, 592 | 02/22/2050, 593 | 04/08/2050, 594 | 04/21/2050, 595 | 05/01/2050, 596 | 06/09/2050, 597 | 09/07/2050, 598 | 10/12/2050, 599 | 11/02/2050, 600 | 11/15/2050, 601 | 12/25/2050, 602 | 01/01/2051, 603 | 02/13/2051, 604 | 02/14/2051, 605 | 03/31/2051, 606 | 04/21/2051, 607 | 05/01/2051, 608 | 06/01/2051, 609 | 09/07/2051, 610 | 10/12/2051, 611 | 11/02/2051, 612 | 11/15/2051, 613 | 12/25/2051, 614 | 01/01/2052, 615 | 03/04/2052, 616 | 03/05/2052, 617 | 04/19/2052, 618 | 04/21/2052, 619 | 05/01/2052, 620 | 06/20/2052, 621 | 09/07/2052, 622 | 10/12/2052, 623 | 11/02/2052, 624 | 11/15/2052, 625 | 12/25/2052, 626 | 01/01/2053, 627 | 02/17/2053, 628 | 02/18/2053, 629 | 04/04/2053, 630 | 04/21/2053, 631 | 05/01/2053, 632 | 06/05/2053, 633 | 09/07/2053, 634 | 10/12/2053, 635 | 11/02/2053, 636 | 11/15/2053, 637 | 12/25/2053, 638 | 01/01/2054, 639 | 02/09/2054, 640 | 02/10/2054, 641 | 03/27/2054, 642 | 04/21/2054, 643 | 05/01/2054, 644 | 05/28/2054, 645 | 09/07/2054, 646 | 10/12/2054, 647 | 11/02/2054, 648 | 11/15/2054, 649 | 12/25/2054, 650 | 01/01/2055, 651 | 03/01/2055, 652 | 03/02/2055, 653 | 04/16/2055, 654 | 04/21/2055, 655 | 05/01/2055, 656 | 06/17/2055, 657 | 09/07/2055, 658 | 10/12/2055, 659 | 11/02/2055, 660 | 11/15/2055, 661 | 12/25/2055, 662 | 01/01/2056, 663 | 02/14/2056, 664 | 02/15/2056, 665 | 03/31/2056, 666 | 04/21/2056, 667 | 05/01/2056, 668 | 06/01/2056, 669 | 09/07/2056, 670 | 10/12/2056, 671 | 11/02/2056, 672 | 11/15/2056, 673 | 12/25/2056, 674 | 01/01/2057, 675 | 03/05/2057, 676 | 03/06/2057, 677 | 04/20/2057, 678 | 04/21/2057, 679 | 05/01/2057, 680 | 06/21/2057, 681 | 09/07/2057, 682 | 10/12/2057, 683 | 11/02/2057, 684 | 11/15/2057, 685 | 12/25/2057, 686 | 01/01/2058, 687 | 02/25/2058, 688 | 02/26/2058, 689 | 04/12/2058, 690 | 04/21/2058, 691 | 05/01/2058, 692 | 06/13/2058, 693 | 09/07/2058, 694 | 10/12/2058, 695 | 11/02/2058, 696 | 11/15/2058, 697 | 12/25/2058, 698 | 01/01/2059, 699 | 02/10/2059, 700 | 02/11/2059, 701 | 03/28/2059, 702 | 04/21/2059, 703 | 05/01/2059, 704 | 05/29/2059, 705 | 09/07/2059, 706 | 10/12/2059, 707 | 11/02/2059, 708 | 11/15/2059, 709 | 12/25/2059, 710 | 01/01/2060, 711 | 03/01/2060, 712 | 03/02/2060, 713 | 04/16/2060, 714 | 04/21/2060, 715 | 05/01/2060, 716 | 06/17/2060, 717 | 09/07/2060, 718 | 10/12/2060, 719 | 11/02/2060, 720 | 11/15/2060, 721 | 12/25/2060, 722 | 01/01/2061, 723 | 02/21/2061, 724 | 02/22/2061, 725 | 04/08/2061, 726 | 04/21/2061, 727 | 05/01/2061, 728 | 06/09/2061, 729 | 09/07/2061, 730 | 10/12/2061, 731 | 11/02/2061, 732 | 11/15/2061, 733 | 12/25/2061, 734 | 01/01/2062, 735 | 02/06/2062, 736 | 02/07/2062, 737 | 03/24/2062, 738 | 04/21/2062, 739 | 05/01/2062, 740 | 05/25/2062, 741 | 09/07/2062, 742 | 10/12/2062, 743 | 11/02/2062, 744 | 11/15/2062, 745 | 12/25/2062, 746 | 01/01/2063, 747 | 02/26/2063, 748 | 02/27/2063, 749 | 04/13/2063, 750 | 04/21/2063, 751 | 05/01/2063, 752 | 06/14/2063, 753 | 09/07/2063, 754 | 10/12/2063, 755 | 11/02/2063, 756 | 11/15/2063, 757 | 12/25/2063, 758 | 01/01/2064, 759 | 02/18/2064, 760 | 02/19/2064, 761 | 04/04/2064, 762 | 04/21/2064, 763 | 05/01/2064, 764 | 06/05/2064, 765 | 09/07/2064, 766 | 10/12/2064, 767 | 11/02/2064, 768 | 11/15/2064, 769 | 12/25/2064, 770 | 01/01/2065, 771 | 02/09/2065, 772 | 02/10/2065, 773 | 03/27/2065, 774 | 04/21/2065, 775 | 05/01/2065, 776 | 05/28/2065, 777 | 09/07/2065, 778 | 10/12/2065, 779 | 11/02/2065, 780 | 11/15/2065, 781 | 12/25/2065, 782 | 01/01/2066, 783 | 02/22/2066, 784 | 02/23/2066, 785 | 04/09/2066, 786 | 04/21/2066, 787 | 05/01/2066, 788 | 06/10/2066, 789 | 09/07/2066, 790 | 10/12/2066, 791 | 11/02/2066, 792 | 11/15/2066, 793 | 12/25/2066, 794 | 01/01/2067, 795 | 02/14/2067, 796 | 02/15/2067, 797 | 04/01/2067, 798 | 04/21/2067, 799 | 05/01/2067, 800 | 06/02/2067, 801 | 09/07/2067, 802 | 10/12/2067, 803 | 11/02/2067, 804 | 11/15/2067, 805 | 12/25/2067, 806 | 01/01/2068, 807 | 03/05/2068, 808 | 03/06/2068, 809 | 04/20/2068, 810 | 04/21/2068, 811 | 05/01/2068, 812 | 06/21/2068, 813 | 09/07/2068, 814 | 10/12/2068, 815 | 11/02/2068, 816 | 11/15/2068, 817 | 12/25/2068, 818 | 01/01/2069, 819 | 02/25/2069, 820 | 02/26/2069, 821 | 04/12/2069, 822 | 04/21/2069, 823 | 05/01/2069, 824 | 06/13/2069, 825 | 09/07/2069, 826 | 10/12/2069, 827 | 11/02/2069, 828 | 11/15/2069, 829 | 12/25/2069, 830 | 01/01/2070, 831 | 02/10/2070, 832 | 02/11/2070, 833 | 03/28/2070, 834 | 04/21/2070, 835 | 05/01/2070, 836 | 05/29/2070, 837 | 09/07/2070, 838 | 10/12/2070, 839 | 11/02/2070, 840 | 11/15/2070, 841 | 12/25/2070, 842 | 01/01/2071, 843 | 03/02/2071, 844 | 03/03/2071, 845 | 04/17/2071, 846 | 04/21/2071, 847 | 05/01/2071, 848 | 06/18/2071, 849 | 09/07/2071, 850 | 10/12/2071, 851 | 11/02/2071, 852 | 11/15/2071, 853 | 12/25/2071, 854 | 01/01/2072, 855 | 02/22/2072, 856 | 02/23/2072, 857 | 04/08/2072, 858 | 04/21/2072, 859 | 05/01/2072, 860 | 06/09/2072, 861 | 09/07/2072, 862 | 10/12/2072, 863 | 11/02/2072, 864 | 11/15/2072, 865 | 12/25/2072, 866 | 01/01/2073, 867 | 02/06/2073, 868 | 02/07/2073, 869 | 03/24/2073, 870 | 04/21/2073, 871 | 05/01/2073, 872 | 05/25/2073, 873 | 09/07/2073, 874 | 10/12/2073, 875 | 11/02/2073, 876 | 11/15/2073, 877 | 12/25/2073, 878 | 01/01/2074, 879 | 02/26/2074, 880 | 02/27/2074, 881 | 04/13/2074, 882 | 04/21/2074, 883 | 05/01/2074, 884 | 06/14/2074, 885 | 09/07/2074, 886 | 10/12/2074, 887 | 11/02/2074, 888 | 11/15/2074, 889 | 12/25/2074, 890 | 01/01/2075, 891 | 02/18/2075, 892 | 02/19/2075, 893 | 04/05/2075, 894 | 04/21/2075, 895 | 05/01/2075, 896 | 06/06/2075, 897 | 09/07/2075, 898 | 10/12/2075, 899 | 11/02/2075, 900 | 11/15/2075, 901 | 12/25/2075, 902 | 01/01/2076, 903 | 03/02/2076, 904 | 03/03/2076, 905 | 04/17/2076, 906 | 04/21/2076, 907 | 05/01/2076, 908 | 06/18/2076, 909 | 09/07/2076, 910 | 10/12/2076, 911 | 11/02/2076, 912 | 11/15/2076, 913 | 12/25/2076, 914 | 01/01/2077, 915 | 02/22/2077, 916 | 02/23/2077, 917 | 04/09/2077, 918 | 04/21/2077, 919 | 05/01/2077, 920 | 06/10/2077, 921 | 09/07/2077, 922 | 10/12/2077, 923 | 11/02/2077, 924 | 11/15/2077, 925 | 12/25/2077, 926 | 01/01/2078, 927 | 02/14/2078, 928 | 02/15/2078, 929 | 04/01/2078, 930 | 04/21/2078, 931 | 05/01/2078, 932 | 06/02/2078, 933 | 09/07/2078, 934 | 10/12/2078, 935 | 11/02/2078, 936 | 11/15/2078, 937 | 12/25/2078, 938 | --------------------------------------------------------------------------------