Answer To: Can you do the first two parts according to instructions?
Swapnil answered on Feb 22 2021
74525/python/.github/classroom/autograding.json
{
"tests": [
{
"name": "test_partthree",
"setup": "sudo -H pip3 install pytest numpy",
"run": "pytest test_ps5.py::test_partthree",
"input": "",
"output": "",
"comparison": "included",
"timeout": 5,
"points": 25
},
{
"name": "test_partone",
"setup": "sudo -H pip3 install pytest numpy",
"run": "pytest test_ps5.py::test_partone",
"input": "",
"output": "",
"comparison": "included",
"timeout": 2,
"points": 20
},
{
"name": "test_parttwob",
"setup": "sudo -H pip3 install pytest numpy",
"run": "pytest test_ps5.py::test_parttwob",
"input": "",
"output": "",
"comparison": "included",
"timeout": 5,
"points": 25
},
{
"name": "test_parttwoa",
"setup": "sudo -H pip3 install pytest numpy",
"run": "pytest test_ps5.py::test_parttwoa",
"input": "",
"output": "",
"comparison": "included",
"timeout": 5,
"points": 20
}
]
}
74525/python/.github/workflows/classroom.yml
name: GitHub Classroom Workflow
on: [push]
jobs:
build:
name: Autograding
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: education/autograding@v1
74525/python/.replit
language = "python3"
74525/python/__pycache__/curr.cpython-37.pyc
74525/python/__pycache__/curr_parser.cpython-37.pyc
74525/python/__pycache__/triggers.cpython-37.pyc
74525/python/curr.py
import numpy as np
class Currency:
def __init__(self, code, total_days):
self.code = code
self.BUY = np.zeros(total_days)
self.SELL = np.zeros(total_days)
def get_data_fragment(self, idx_1, idx_2, param):
data = self.__getattribute__(param)
return data[idx_1:idx_2]
def calc_change(self, idx_1, idx_2, prop):
"""
:param idx_1: first date index
:param idx_2: second date index
:param prop: property to be measured
:return:
"""
try:
value = 100*(idx_2 - idx_1)/idx_1
except ZeroDivisionError:
value=0
return value
def calc_mean(self,idx_1, idx_2, prop):
"""
Calculates mean of a property given date indices
:param idx_1: first date index
:param idx_2: second date index
:param prop: property to be measured
:return:
"""
value = np.mean(c.get_data_fragment(idx_1, idx_2, prop))
return value
def calc_volatility(self, idx_1, idx_2,prop):
value = np.var(c.get_data_fragment(idx_1, idx_2, prop))
return value
def calc_max(self, idx_1, idx_2, prop):
"""
Desc
:param idx_1: first date index
:param idx_2: second date index
:param prop: property to be measured
:return: float
"""
value = np.max(c.get_data_fragment(idx_1, idx_2, prop))
return value
def calc_min(self, idx_1, idx_2, prop):
"""
Desc
:param idx_1: first date index
:param idx_2: second date index
:param prop: property to be measured
:return: float
"""
value = np.min(c.get_data_fragment(idx_1, idx_2, prop))
return value
if __name__ == "__main__":
# DON'T MODIFY THIS!
total_days = 10
c = Currency("EUR", total_days)
np.random.seed(42)
c.BUY = 5.0*np.ones(total_days) + np.random.rand(total_days)
c.SELL = c.BUY + 0.1
print("Change: ", c.calc_change(0, 7, "BUY"))
print("Vol: ", c.calc_volatility(2, 9, "SELL"))
print("Max: ",c.calc_max(4, 9, "SELL"))
print("Mean: ",c.calc_mean(4, 9, "SELL"))
print("Min: ",c.calc_min(0, 3, "BUY"))
74525/python/curr_database.py
from curr import Currency
import datetime
from curr_parser import get_data
from triggers import LowTrigger,HighTrigger,AndTrigger,OrTrigger,NotTrigger
import numpy as np
class CurrencyDatabase:
def __init__(self, start_date, end_date):
self.start_date = self.conv2date(start_date)
self.end_date = self.conv2date(end_date)
self.market_open = np.zeros(self.date_cnt)
self.currency_list = ['USD', 'AUD', 'DKK', 'EUR', 'GBP', 'CHF', 'SEK', 'CAD', 'KWD',
'NOK', 'SAR', 'JPY', 'BGN', 'RON', 'RUB', 'IRR', 'CNY', 'PKR']
self.trigger_list = []
self.analysis_list = []
self.comp_trigger_list = []
self.comp_analysis_list = []
self.db = {}
for curr in self.currency_list:
self.db[curr] = Currency(total_days=self.date_cnt, code=curr)
# Get data from TCMB
for i in range(self.date_cnt):
date = self.start_date + datetime.timedelta(days=i)
data_dict = get_data(date)
# Check the dictionary is emtpy
if bool(data_dict):
db[curr]= data_dict[i]
# Market is open. TODO
self.market_open[i] = 1
return db[curr]
else:
db[curr] = date_dict[i-1]
# Market is closed. TODO
print("Database init completed.")
print(f"Database interval: {self.start_date} - {self.end_date}")
print(f"Fetched {self.date_cnt} days. Market is open {np.sum(self.market_open)} days.")
print(f"There are {len(self.currency_list)} currencies.")
print("---")
@property
def date_cnt(self,start_date,end_date):
if start_date == end_date:
val = 1
else:
val = (end_date-start_date).days
return val
def conv2date(self, date):
date = datetime(date)
return date
def idx2date(self, idx):
date = datetime()
return date
def date2idx(self, target_date):
idx = DatetimeIndex(target_date)
assert idx >= 0, f'Date is out of range!'
assert self.date_cnt >= idx, "Date is out of range!"
return idx
def set_triggers(self, trigger_list_path):
with open(trigger_list_path, "r") as f:
for line in f:
# Read the line, parse as a list
arg = line.rstrip().split()
# Extract list
trig_cls = arg[0] # Trigger class
if trig_cls == "LOW":
date_start_str = arg[4]
date_end_str = arg[5]
date_start_idx = self.date2idx(date_start_str.split('/'))
date_end_idx = self.date2idx(date_end_str.split('/'))
trigger = LowTrigger(func = arg[1], prop = arg[2], threshold = float(arg[3]),
date_start_idx=date_start_idx, date_end_idx=date_end_idx,
date_start_str = date_start_str, date_end_str = date_end_str)
elif trig_cls == "HIGH":
date_start_str = arg[4]
date_end_str = arg[5]
date_start_idx = self.date2idx(date_start_str.split('/'))
date_end_idx = self.date2idx(date_end_str.split('/'))
trigger = HighTrigger(func = arg[1], prop = arg[2], threshold = float(arg[3]),
date_start_idx=date_start_idx, date_end_idx=date_end_idx,
date_start_str = date_start_str, date_end_str = date_end_str)
elif trig_cls == "NOT":
trig_idx = int(arg[1])
trigger = NotTrigger(self.trigger_list[trig_idx])
elif trig_cls == "AND":
trig_idx_1 = int(arg[1])
trig_idx_2 = int(arg[2])
trigger = AndTrigger(self.trigger_list[trig_idx_1], self.trigger_list[trig_idx_2])
elif trig_cls == "OR":
trig_idx_1 = int(arg[1])
trig_idx_2 = int(arg[2])
trigger = OrTrigger(self.trigger_list[trig_idx_1], self.trigger_list[trig_idx_2])
print(f'Initialized trigger: {trigger}')
self.trigger_list.append(trigger)
print("Initializing triggers complete!")
print("---")
def run_triggers(self):
"""
Runs triggers on a specific date interval.
If start date is not set, it scans from the first day.
If end date is not set, it scans to the end.
If nothing has been set, it scans entire dataset.
:param start_date: tuple ()
:param end_date: tuple ()
:return:
"""
for trig in self.trigger_list:
print(f'Evaluating trigger: {trig}')
analysis = []
for curr_code in self.currency_list:
curr = self.db[curr_code]
result = trig.evaluate(curr)
if result:
analysis.append(curr.code)
self.analysis_list.append(analysis)
print("Running triggers complete!")
def analyze(self, trigger_list_path = False, debug = False):
self.set_triggers(trigger_list_path=trigger_list_path)
self.run_triggers()
print(f'============Writing Report============')
for idx, list in enumerate(self.analysis_list):
if list:
print(f"{idx + 1} - {self.trigger_list[idx]} is fired for: {list}")
elif not list and debug:
print(f"{idx + 1} - {self.trigger_list[idx]} is not fired!")
print(f'==========End of the Report==========')
74525/python/curr_parser.py
import xml.etree.ElementTree as ET
import urllib
from urllib.request import urlopen
import datetime
def get_data(date):
"""
:param date: datetime object
:return: data dictionary of currency values.
"""
# Prepare the URL
url = 'https://www.tcmb.gov.tr/kurlar/'+ datetime.datetime.strftime(date,"%Y%m")+'/'+ datetime.datetime.strftime(date,"%d%m%Y")+'.xml'
# Container for the date
data_dict = {}
try:
tree = ET.parse(urlopen(url))
root = tree.getroot()
for currency in root.findall('Currency'):
code = currency.get('CurrencyCode')
# Skip it since this is not a currency.
if code == "XDR":
continue
unit=currency.find('Unit').text
buying=currency.find('ForexBuying').text
selling=currency.find('ForexSelling').text
# Prepare a dictionary to store parsed information.
cur_dict = {'buying': buying, 'selling': selling}
# Save the currency dictionary to data dictionary.
data_dict[code] = cur_dict
except urllib.error.HTTPError as err:
if err.code == 404:
'whatever'
else:
raise
return data_dict
if __name__ == "__main__":
date = datetime.datetime(2020, 9, 14)
cur_dict = get_data(date)
print(cur_dict)
date = datetime.datetime(2020, 1, 1)
cur_dict = get_data(date)
74525/python/Instructions.md
# TCMB Currency Database
# Deadline: 17/01/2021 - 11:00pm
Required reading: Section 4.5 & 4.6 from your textbook.
## Part 0: Overview
In this assignment, you will implement a small currency database for the Central Bank of Turkey (TCMB), which holds TRY (Turkish Lira) foreign exchange rates
for a short interval. In this database, you will implement alarm mechanisms to alert the currency analysts if a specific threshold is violated,
for example, when EUR/TRY is lower than 5.2.
You will complete your implementation in three parts.
In the first part, you will code a parser, text-processor for a webpage in XML format.This parser is going to fetch the data stored
in the [Central Bank of Turkey webpage](https://www.tcmb.gov.tr/kurlar/201806/29062018.xml).This link returns daily currency rates based on URL.
The first segment of the link is the year-month format (2018 and 06).
The second segment of the link is in the day-month-year format.
You will extract foreign exchange (forex) buying and selling data from this tabular data with the code inside "curr_parser.py".
In the second part, you will implement a currency class (in currency.py) that holds this parsed exchange data. Each currency will be an instance of this class,
and you will keep this data in an indexed format. Then, you will aggregate these instances inside a master-class, called `CurrencyDatabase`.
This class will implement the high-level operations such as indexing, running triggers, writing reports, etc.
In the final part, you will implement the alarm mechanism for this database, namely, triggers. You will process trigger definitions from the "triggers.txt"
file provided. In each line of this file, there is a trigger configuration. You will iterate over the database and trigger an alarm if a specific
threshold is violated.
We provide you a skeleton code to complete all of these parts in multiple files.
## Part I: (Text Processing) Building the Parser
1. In the "curr_parser.py" file, we have a `get_data` function, which expects a `datetime` object, parses an XML file, and
returns a dictionary of currency values.
The following definitions and sources may be useful for you in this part:
* To work with dates as objects, you will need a module called `datetime`. This class date contains the year, month, day, hour, minute, second,
and microsecond,
and implements powerful functions. For more information, please check the [Python documentation for the `datetime` module]
(https://docs.python.org/3/library/datetime.html).
* Extensible Markup Language (XML) is a markup language that defines a set of rules for encoding documents in a human-readable and machine-readable format.
TCMB stores money data on the web in this format.
First, construct the URL as a string. For example, for June 29th of 2019, the url should be as follows:
> https://www.tcmb.gov.tr/kurlar/201806/29062018.xml
For the date-related part, you need to use the class properties of the `datetime` such as `month`.
**Hint**: Check the `strftime` method, which returns a custom string representing the date.
2. The page is in a both human-readable and machine-readable format. We can see the source of the page by typing into a browser
> view-source:https://www.tcmb.gov.tr/kurlar/201806/29062018.xml
or
by going to the website
> https://www.tcmb.gov.tr/kurlar/201806/29062018.xml
and doing right-click and then choosing the "view source".
This way, we can see the data stored on this webpage. This page structure is called an *element-tree*. A tree is a data structure with a root value and subtrees
of children with a parent node. The root-node of this XML is:
```
...
```
and one of the children subtrees is a currency tree, which holds daily exchange rate information:
```
1
ABD DOLARI
US DOLLAR
4.5607
4.5690
4.5575
4.5758
```
All subtrees -except one which you should not worry about- are in the same format. All we need to do is to iterate over each subtree and extract information
for currency exchange. From each subtree, we need to extract the attribute `CurrencyCode` and the information stored in the leaves including `Unit`, `ForexBuying`,
and `ForexSelling`. `ForexBuying` and `ForexSelling` define the conversion rates used for money trading as explained next with an example.
For example, for the `CurrencyCode` USD, if the `ForexSelling` is equal to 4.5690, this means by selling 1 USD, the bank will receive 4.5690 TRY in return.
Similarly, if the `ForexBuying` is equal to 4.5607, this means in order to buy 1 USD, the bank needs to spend 4.5607 TRY. `Unit` is used to scale down values
into readable ranges for some currencies. The value is typically 1, then we do not need to do anything but if it is greater than 1, for example 100 for some
currencies, then we need to multiply both `ForexSelling` and `ForexBuying` by the `Unit`.
Please follow the instructions to extract this information as follows:
To extract the value of an attribute like `CrossOrder` or `CurrencyCode`, you can use `get` function of the tree object. For example, `currency.get('CrossOrder')`
returns "0". We do not need to know its implementation in order to use this function. This is called *abstraction*.
To extract the information stored inside the leaves, you can call the `find` function of the tree object. This returns an XML-Unit object instance, which has a
class property called `text` that is a string. Here, you are accessing a property of a class instance. For example, `currency.find('CurrencyName').text` returns
a string "US DOLLAR". You need to cast it appropriately if you are processing numeric data.
To complete this section, please fill in the TODO part 2 in "curr_parser.py".
3. (Exception handling) As you might imagine, not all pages are available all the time, for example when the exchange market is closed. Use exception handling
to catch these cases, and show a meaningful message, e.g. with the URL or date in consideration as shown in the example below.
You can test your implementation by running "curr_parser.py". We provide a small script as a tester. If you get the same output with the following,
you are good to go!
```
{'USD': {'buying': 7.4689, 'selling': 7.4823}, 'AUD': {'buying': 5.4207, 'selling': 5.4561}, 'DKK': {'buying': 1.1887, 'selling': 1.1946},
'EUR': {'buying': 8.858, 'selling': 8.8739}, 'GBP': {'buying': 9.5837, 'selling': 9.6336}, 'CHF': {'buying': 8.2078, 'selling': 8.2605},
'SEK': {'buying': 0.84827, 'selling': 0.85705}, 'CAD': {'buying': 5.6628, 'selling': 5.6883}, 'KWD': {'buying': 24.29, 'selling': 24.6079},
'NOK': {'buying': 0.82709, 'selling': 0.83265}, 'SAR': {'buying': 1.9912, 'selling': 1.9948}, 'JPY': {'buying': 702.9200000000001, 'selling': 707.58},
'BGN': {'buying': 4.5035, 'selling': 4.5624}, 'RON': {'buying': 1.8134, 'selling': 1.8372}, 'RUB': {'buying': 0.09894, 'selling': 0.10023},
'IRR': {'buying': 1.7680000000000002, 'selling': 1.791}, 'CNY': {'buying': 1.0882, 'selling': 1.1024}, 'PKR': {'buying': 0.0447, 'selling': 0.04528},
'QAR': {'buying': 2.0392, 'selling': 2.0659}}
HTTP Error 404: Not Found - https://www.tcmb.gov.tr/kurlar/202001/01012020.xml
```
## Part IIa: (OOP) Currency Class
We will start by implementing a `Currency` class for our database. This class will hold our currency exchange data and also provide some analysis functions
such as calculating the mean, the minimum, the maximum, the change rate, and the volatility over a given interval. We will use `NumPy`,
which is a fundamental package for scientific computing in Python.
You can construct a `Currency` object with two parameters only: `code` and `total_days`. `code` stands for `CurrencyCode` in your parsed XML file,
a short notation for currencies (e.g., EUR, USD, JPY). `total_days` stands for the number of days that our currency data is fetched. For example,
`Currency("EUR", 10)` creates a currency object for Euro for 10 days data. Initially, your data is set to 0; that's what `np.zeros(total_days)` does.
You will implement five numeric calculation functions: `mean`, `min`, `max`, `volatility`, and `change`. Each of them expects two indices; start date index,
end date index, and a property to measure (`BUY` or `SELL`). For your convenience, we provide `get_data_fragment` function to extract currency data over an
interval for you.
* Mean is computed by averaging all of the values inside a data...