· 8 years ago · Apr 21, 2018, 05:46 PM
1class DateError(Exception):
2 pass
3class TransactionError(Exception):
4 pass
5
6def compareDates(s1,s2):
7 """
8 Function compareDates: It takes as input two strings 's1', 's2' which are dates in the form YYYY-MM-DD and it returns a Boolean value: False if 's1' is later than 's2' and True if 's1' is earlier than 's2'. It also returns True if the two dates are the same.
9 Input: Two strings 's1', 's2' describing dates in the form YYYY-MM-DD.
10 Output: A Boolean value; True/False.
11 """
12 if int(s1[:4])<int(s2[:4]):
13 return True
14 elif int(s1[:4])>int(s2[:4]):
15 return False
16 if int(s1[5:7])<int(s2[5:7]):
17 return True
18 elif int(s1[5:7])>int(s2[5:7]):
19 return False
20 if int(s1[-2:])<int(s2[-2:]):
21 return True
22 else:
23 return False
24def normaliseDate(s):
25 """
26 Function normaliseDate(s) which takes as input a string s and returns a string which describes the date in the form YYYY-MM-DD. The arguments MM, DD are not neccessarilly two digit numbers. For example, the month January can be described by '1' instead of '01'.
27 Input: A string which describes a date. This string can have any of the following forms: YYYY.MM.DD, DD.MM.YYYY, YYYY/MM/DD, DD/MM/YYYY, YYYY-MM-DD, DD-MM-YYYY
28 Output: The same date written in the form YYYY-MM-DD, where MM, DD are two digit numbers. The function will raise an error in the following cases:
29 1)the year entered is not between 2000 and 2018
30 2)the day is not between 1 and 31
31 3)the month is not between 1 and 12. .
32 """
33 for x in s:
34 if x not in "0123456789":
35 sep=x
36 break
37 lst=s.split(sep)
38 MM=lst[1]
39 if int(lst[0])//2000>0 and int(lst[0])//2019==0:
40 YY=lst[0]
41 DD=lst[2]
42 elif int(lst[2])//2000>0 and int(lst[2])//2019==0:
43 YY=lst[2]
44 DD=lst[0]
45 else:
46 raise DateError
47 if len(MM)!=2:
48 MM='0'+MM
49 if len(DD)!=2:
50 DD='0'+DD
51 dd=int(DD)
52 mm=int(MM)
53 if not (dd>0 and dd<=31):
54 raise DateError
55 if not (mm>0 and mm<=12):
56 raise DateError
57 return YY+'-'+MM+'-'+DD
58stocks=dict()
59portfolio=dict()
60transactions=[]
61def loadStock(symbol):
62 """
63 Function loadStock(symbol): It takes as input a string (symbol) and inputs all the historic financial data from the corresponding csv file (from the file symbol.csv in the subdirectory stockdata). First, it creates a dictionary with keys the dates given inside the file and values some lists with 4 floating point numbers representing (in the given order): the open, high, low, and closing price of the company described by 'symbol' in this specific date. Then, it loads this dictionary in the dictionary 'stocks' with key the string 'symbol' (which is the company's abbreviation). The function raises a FileNotFoundError exception in case no file with the given name exists. It also raises a ValueError exception in case some of the entries in the rows of the CSV file have a wrong format (for example they contain letters and therefore cannot be converted to floats).
64
65 Input: A string 'symbol' which is the three letter abbreviation of the company whose data we wish to load in the dictionary stocks.
66
67 Output: -
68 """
69
70 dct=dict()
71 f=open("stockdata/" + symbol + ".csv",mode="rt",encoding="utf8")
72 f.readline() #we ignore the first line with the headers
73 for line in f:
74 row=line.split(",")
75 try:
76 row[0]=normaliseDate(row[0])
77 except DateError:
78 print("Warning! One of the dates in the CSV file {} has a wrong format: {}. Hence it was not loaded in the {} dictionary.".format(symbol,DateError,symbol))
79 continue
80 dct[row[0]]=[float(row[1]),float(row[2]),float(row[3]),float(row[4])]
81
82
83 f.close()
84 stocks[symbol]=dct
85
86#print(loadStock.__doc__)
87
88
89def loadPortfolio(fname="portfolio"):
90 """
91 Function loadPortfolio(fname): It takes as input a string, 'fname' and loads the data from the 'fname.csv'. The file contains the cash and stocks that we have at a given date. It empties the portfolio dictionary and then loads the data from the file into the dictionary. In addition, it loads all the historic financial data to the dictionary stocks about every company that we own shares. It raises a FileNotFoundError exception in case no file with the given name exists. It also raises a ValueError exception in case some of the entries in the file is wrong (for example the amount of shares for a company is a floating point number).
92
93 Input: A string corresponding to the name of the csv file in which our portfolio is saved.
94
95 Output: -
96 """
97 try:
98 portfolio.clear()
99 transactions.clear()
100 f=open(fname + ".csv",mode="rt",encoding="utf8")
101 date=f.readline()
102 date=date[:-1] #getting rid of '\n'
103 date=normaliseDate(date)
104 portfolio['date']=date
105 cash=f.readline()
106 portfolio['cash']=float(cash)
107 for line in f:
108 if line=="":
109 break
110 lst=line.split(",") #a list with two entries: the company abbreviation and the amount of shares
111 portfolio[lst[0]]=int(lst[1])
112 try:
113 loadStock(lst[0]) #we load the historic data of the company into the stocks dictionary
114 except FileNotFoundError:
115 print("Warning! Failed to load historic financial data about {}. \n Type of error: There are no records about this company. ".format(lst[0]))
116 continue
117 except ValueError:
118 print("Warning! Failed to load historic financial data about {}. \n Type of error: The CSV file {} has a wrong format.".format(lst[0],lst[0]+".csv"))
119 continue
120
121 except Exception:
122 print ("Warning! Failed to load historic financial data about {}.".format(lst[0]))
123 continue
124 f.close()
125 except DateError:
126 print("Failed to load the portfolio. The date has an invalid format. Please modify the file {} and try again.".format(fname))
127 except ValueError:
128 print("Failed to load the portfolio. The date should be in one of the following forms: YYYY.MM.DD, DD.MM.YYYY, YYYY/MM/DD, DD/MM/YYYY, YYYY-MM-DD, DD-MM-YYYY.")
129 except Exception:
130 print("Something went wrong. Try again!")
131
132
133def valuatePortfolio(date=None,verbose=False):
134 """
135 Function valuatePortfolio: It takes as input a string describing a date and a Boolean value (which is by default False). The function has to evaluate the portfolio at the given date and return its value. If the variable 'verbose' has the value True it has to print a table with all the information about the portfolio: Cash, Amount of shares we own for each company, Value per unit for each share at this specific date (we always work with the low value of the day), Total value of the shares we own for each company, and Total value of the portfolio. The function raises a DateError exception in two case: A)When the date given is earlier than the date of the portfolio, and B)When there are no records about this specific date.
136
137 Input: A string 'date' which describes a date in one of the following forms: YYYY.MM.DD, DD.MM.YYYY, YYYY/MM/DD, DD/MM/YYYY, YYYY-MM-DD, DD-MM-YYYY, and a variable 'verbose' which takes Boolean values.
138
139 Output: The value of the portfolio at this given date.
140
141 """
142 if date==None:
143 date=portfolio["date"]
144 else:
145 date=normaliseDate(date)
146 if compareDates(date,portfolio["date"]):
147 raise DateError("The given date is earlier than the day in the portfolio")
148 value=0
149 lst=[]
150 for key in portfolio:
151 if key=="date":
152 continue
153 elif key=="cash":
154 cash=portfolio[key]
155 value+=cash
156 else:
157 if date not in stocks[key]:
158 raise DateError("There are no records about this specific day. It may be the case that this day is Saturday/Sunday or a Bank Holiday.")
159 value+=portfolio[key]*stocks[key][date][2]
160 lst.append([key,portfolio[key],stocks[key][date][2]]) #I create a list of lists: Each list inside 'lst' has 3 entries: the name of the company, the amount of shares we have on this company, and the low price on this day. I do this for convenience reasons; I want to store the data so that I can construct the table easier.
161 if verbose==True:
162 print("Your portfolio on {}:".format(date))
163 print("[* share values based on the lowest price on {}]".format(date))
164 print(" Capital type"+10*" "+"|"+" Volume "+"| Val/Unit* |"+" Value in £*")
165 print(23*"-"+"+"+8*"-"+"+"+11*"-"+"+"+13*"-")
166 print(" Cash"+18*" "+"|"+6*" "+"1 |"+"{:>10.2f} |{:>12.2f} ".format(cash,cash))
167 for x in lst:
168 print(" Shares of {:<12}|".format(x[0])+"{:>7}".format(x[1])+" |"+"{:>10.2f} |{:>12.2f} ".format(x[2],x[1]*x[2]))
169 print(23*"-"+"+"+8*"-"+"+"+11*"-"+"+"+13*"-")
170 print(" TOTAL VALUE"+"{:>45.2F} ".format(value))
171 return value
172
173
174
175
176
177
178
179def addTransaction(trans,verbose=False):
180 """
181 Function addTransaction: This function takes as an input a dictionary 'trans' and a Boolean variable which is by default False. The dictionary 'trans' describes a transaction made. It has 3 keys: date (date of transaction), symbol (3-letter abbreviation of the company), and volume(amount of shares we buy/sell: <0 if we sell and >0 if we buy). The aim of the function is to load this transaction to the list 'transactions', as well as update the portfolio dictionary with the latest changes (change the date, update the cash, etc). If the function is called with verbose=True, the function prints a short text giving details of the transaction and our available cash. It also raises a DateError exception, if the date of the transaction is earlier than the date on the portfolio, and a ValueError if there are no data about this company in the dictionary stocks.
182 Input: A dictionary 'trans' and a Boolean variable 'verbose'.
183 Output: -
184 """
185 try:
186 trans["date"]=normaliseDate(trans["date"])
187 except DateError:
188 print("Failed to load the transaction. The date has an invalid format. Please modify the file {} and try again.".format(fname))
189 except ValueError:
190 print("Failed to load the transaction. The date should be in one of the following forms: YYYY.MM.DD, DD.MM.YYYY, YYYY/MM/DD, DD/MM/YYYY, YYYY-MM-DD, DD-MM-YYYY.")
191 else:
192 print(trans["date"])
193 print(portfolio["date"])
194 if compareDates(trans["date"],portfolio["date"]):
195 raise DateError
196 vol=trans["volume"]
197 symb=trans["symbol"]
198 if not symb in stocks:
199 raise ValueError("There are no data about the company {} in the dictionary stocks.".format(symb))
200 date=trans["date"]
201 if date not in stocks[symb]:
202 raise ValueError("You cannot complete the transaction at this specific date. It is probably a weekend or a bank holiday.")
203 lst=stocks[symb][date]
204 if vol<0:
205 if symb not in portfolio or abs(vol)>portfolio[symb]:
206 raise TransactionError("You cannot sell the required number of shares. Please check your portfolio.")
207 money=lst[2]*abs(vol)
208 portfolio[symb]+=vol
209 portfolio["cash"]+=money
210 sold_bought="Sold"
211 else:
212 if portfolio["cash"]<vol*lst[1]:
213 raise TransactionError("There are not enough cash to complete the transaction.")
214 if symb not in portfolio:
215 portfolio[symb]=0
216 portfolio[symb]+=vol
217 money=vol*lst[1]
218 portfolio["cash"]-=money
219 sold_bought="Bought"
220 if portfolio[symb]==0:
221 del portfolio[symb]
222
223 portfolio["date"]=date
224 transactions.append(trans)
225 if verbose==True:
226 if abs(vol)!=1:
227 print("> {}: {} {} shares of {} for a total of £{:.2f}.".format(date,sold_bought,abs(vol),symb,money))
228 print(" Available cash: £{:.2f}.".format(portfolio["cash"]))
229 else:
230 print("> {}: {} {} share of {} for a total of £{:.2f}.".format(date,sold_bought,abs(vol),symb,money))
231 print(" Available cash: £{:.2f}.".format(portfolio["cash"]))
232
233
234
235def savePortfolio(fname="portfolio"):
236 f=open(fname+"csv",mode="wt",encoding="utf8")
237 f.write(portfolio["date"]+"\n")
238 f.write(str(portfolio["cash"])+"\n")
239 for key in portfolio:
240 if key=="date" or key=="cash":
241 continue
242 f.write(key+","+str(portfolio[key])+"\n")
243 f.close()