Test coverage report: forex.models

# forex.models: 140 total statements, 93.0% covered

Generated: Thu 2016-02-04 14:24 AST

Source file: /home/kevin/Development/custom apps/valuehorizon-forex/forex/models.py

Stats: 120 executed, 9 missed, 11 excluded, 114 ignored

1. `from django.db import models`
2. `from django.db.models import Manager`
3. `from django.core.validators import MinValueValidator, ValidationError`
4. ``
5. `# Import misc packages`
6. `import numpy as np`
7. `from datetime import date, timedelta`
8. `from decimal import Decimal`
9. `from pandas import DataFrame, date_range`
10. ``
11. `PRICE_PRECISION = 4`
12. `DATEFRAME_START_DATE = date(2005,1,1)`
13. ``
14. ``
15. `class Currency(models.Model):`
16. `"""`
17. `Represents a currency according to ISO 4217 standards.`
18. `"""`
19. ``
20. `name = models.CharField(max_length=255)`
21. `symbol = models.CharField(max_length=10, unique=True)`
22. `ascii_symbol = models.CharField(max_length=20, null=True, blank=True)`
23. `num_code = models.IntegerField(null=True, blank=True)`
24. `digits = models.IntegerField(null=True, blank=True) # Digits after decimal (minor unit)`
25. `description = models.TextField(blank=True)`
26. ``
27. `# Cached Data`
28. `date_modified = models.DateTimeField(null=True, blank=True, editable=False, auto_now=True)`
29. `date_created = models.DateTimeField(null=True, blank=True, editable=False, auto_now_add=True)`
30. ``
31. `class Meta:`
32. `verbose_name_plural = 'Currencies'`
33. `verbose_name = 'Currency'`
34. `ordering = ['name', 'symbol']`
35. ``
36. `def __unicode__(self):`
37. `return u'%s, %s' % (unicode(self.name), unicode(self.symbol))`
38. ``
39. `def get_verbose_name(self):`
40. `"""`
41. `Fetch verbose name from _meta. This is useful if we want to do haystack`
42. `faceting on the model's objects.`
43. `"""`
44. `return self._meta.verbose_name`
45. ``
46. `def get_verbose_name_plural(self):`
47. `"""`
48. `Fetch plural verbose name from _meta. This is useful if we want to do haystack`
49. `faceting on the model's objects.`
50. `"""`
51. `return self._meta.verbose_name_plural`
52. ``
53. ``
54. `def generate_dataframe(self, start_date=None, end_date=None):`
55. `"""`
56. `"""`
57. `first_series_point = CurrencyPrice.objects.filter(currency=self)[0]`
58. `last_series_point = CurrencyPrice.objects.filter(currency=self).reverse()[0]`
59. `start_date = first_series_point.date if start_date == None else max(first_series_point.date, start_date)`
60. `temp_start_date = start_date - timedelta(days=3) # Add lag`
61. `end_date = last_series_point.date if end_date == None else min(last_series_point.date, end_date)`
62. ``
63. `currency_date = CurrencyPrice.objects.filter(currency=self, date__gte=temp_start_date, date__lte=end_date).values_list('date', 'ask_price', 'bid_price')`
64. `currency_data_array = np.core.records.fromrecords(currency_date, names=['DATE', "ASK", "BID"])`
65. `df = DataFrame.from_records(currency_data_array, index='DATE').astype(float)`
66. `df['MID'] = (df['ASK'] + df['BID']) / 2.0`
67. `df['CHANGE'] = df['MID'].pct_change()`
68. ``
69. `required_dates = date_range(start_date,end_date)`
70. `df = df.reindex(required_dates)`
71. `df = df.fillna(method='ffill')`
72. ``
73. `return df`
74. ``
75. ``
76. ``
77. `class CurrencyPriceManager(Manager):`
78. `""" Adds some added functionality """`
79. ``
80. `def generate_dataframe(self, symbols=None, date_index=None, price_type="mid"):`
81. `"""`
82. `Generate a dataframe consisting of the currency prices (specified by symbols)`
83. `from the start to end date`
84. `"""`
85. ``
86. `# Set defaults if necessary`
87. `if symbols == None:`
88. `symbols = list(Currency.objects.all().values_list('symbol', flat=True))`
89. `try:`
90. `start_date = date_index[0]`
91. `end_date = date_index[-1]`
92. `except:`
93. `start_date = DATEFRAME_START_DATE`
94. `end_date = date.today()`
95. `date_index = date_range(start_date, end_date)`
96. ``
97. `currency_price_data = CurrencyPrice.objects.filter(currency__symbol__in=symbols,`
98. `date__gte=date_index[0],`
99. `date__lte=date_index[-1]).values_list('date', 'currency__symbol', 'ask_price', 'bid_price')`
100. `try:`
101. `forex_data_array = np.core.records.fromrecords(currency_price_data, names=['date', 'symbol', 'ask_price', 'bid_price'])`
102. `except IndexError:`
103. `forex_data_array = np.core.records.fromrecords([(date(1900,1,1) , "", 0, 0)], names=['date', 'symbol', 'ask_price', 'bid_price'])`
104. `df = DataFrame.from_records(forex_data_array, index='date')`
105. ``
106. `df['date'] = df.index`
107. ``
108. `if price_type == "mid":`
109. `df['price'] = (df['ask_price'] + df['bid_price']) / 2`
110. `elif price_type == "ask_price":`
111. `df['price'] = df['ask_price']`
112. `elif price_type == "bid_price":`
113. `df['price'] = df['bid_price']`
114. `else:`
115. `raise ValueError("price_type must be on of 'ask', 'bid' or 'mid'")`
116. ``
117. `df = df.pivot(index='date', columns='symbol', values='price')`
118. `df = df.reindex(date_index)`
119. `df = df.fillna(method="ffill")`
120. `unlisted_symbols = list(set(symbols) - set(df.columns))`
121. `for unlisted_symbol in unlisted_symbols:`
122. `df[unlisted_symbol] = np.nan`
123. `df = df[symbols]`
124. ``
125. `return df`
126. ``
127. ``
128. `class CurrencyPrice(models.Model):`
129. `"""`
130. `Represents a currency price to US`
131. `"""`
132. ``
133. `currency = models.ForeignKey(Currency)`
134. `date = models.DateField()`
135. ``
136. `# Price Data per $1 of US`
137. `ask_price = models.DecimalField(max_digits=20, decimal_places=PRICE_PRECISION,`
138. `validators=[MinValueValidator(Decimal('0.00'))])`
139. `bid_price = models.DecimalField(max_digits=20, decimal_places=PRICE_PRECISION,`
140. `validators=[MinValueValidator(Decimal('0.00'))])`
141. ``
142. `# Cached Data`
143. `date_modified = models.DateTimeField(null=True, blank=True, editable=False, auto_now=True)`
144. `date_created = models.DateTimeField(null=True, blank=True, editable=False, auto_now_add=True)`
145. ``
146. `# Add custom managers`
147. `objects=CurrencyPriceManager()`
148. ``
149. `class Meta:`
150. `verbose_name_plural = 'Currency Prices'`
151. `verbose_name = 'Currency Price'`
152. `ordering = ['date', ]`
153. `unique_together=['date', 'currency']`
154. `get_latest_by = "date"`
155. ``
156. `def __unicode__(self):`
157. `return u'%s, %s' % (unicode(self.currency),`
158. `unicode(self.date),)`
159. ``
160. `def save(self, *args, **kwargs):`
161. `"""`
162. `Sanitation checks`
163. `"""`
164. `if self.ask_price < 0:`
165. `raise ValidationError("Ask price must be greater than zero")`
166. `if self.bid_price < 0:`
167. `raise ValidationError("Bid price must be greater than zero")`
168. `if self.ask_price < self.bid_price:`
169. `raise ValidationError("Ask price must be at least Bid price")`
170. ``
171. `super(CurrencyPrice, self).save(*args, **kwargs) # Call the "real" save() method.`
172. ``
173. `@property`
174. `def mid_price(self):`
175. `"""`
176. `Compute the mid point between the bid and ask prices`
177. `"""`
178. `return (self.ask_price + self.bid_price) / Decimal('2.0')`
179. ``
180. `@property`
181. `def spread(self):`
182. `"""`
183. `Compute the difference between bid and ask prices`
184. `"""`
185. `return (self.ask_price - self.bid_price)`
186. ``
187. `@property`
188. `def ask_price_us(self):`
189. `"""`
190. `Calculate the ask_price in USD. This is the inverse`
191. `of the ask price.`
192. `"""`
193. `if self.ask_price != 0:`
194. `return 1 / Decimal(str(self.ask_price))`
195. `else:`
196. `raise ZeroDivisionError('Ask price is zero')`
197. ``
198. `@property`
199. `def bid_price_us(self):`
200. `"""`
201. `Calculate the bid_price in USD. This is the inverse`
202. `of the bid price.`
203. `"""`
204. `if self.bid_price != 0:`
205. `return 1 / Decimal(str(self.bid_price))`
206. `else:`
207. `raise ZeroDivisionError('Bid price is zero')`
208. ``
209. ``
210. `def conversion_factor(from_symbol, to_symbol, date):`
211. `"""`
212. `Generates a multiplying factor used to convert tow currencies`
213. `"""`
214. ``
215. `if from_symbol == to_symbol:`
216. `return Decimal('1.0')`
217. ``
218. `from_currency = Currency.objects.get(symbol=from_symbol)`
219. `try:`
220. `from_currency_price = CurrencyPrice.objects.get(currency=from_currency, date=date).mid_price`
221. `except CurrencyPrice.DoesNotExist:`
222. `print "Cannot fetch prices for %s on %s" % (str(from_currency), str(date))`
223. `return None`
224. ``
225. `to_currency = Currency.objects.get(symbol=to_symbol)`
226. `try:`
227. `to_currency_price = CurrencyPrice.objects.get(currency=to_currency, date=date).mid_price`
228. `except CurrencyPrice.DoesNotExist:`
229. `print "Cannot fetch prices for %s on %s" % (str(to_currency), str(date))`
230. `return None`
231. ``
232. `return to_currency_price / from_currency_price`
233. ``
234. ``
235. `def convert_currency(from_symbol, to_symbol, value, date):`
236. `"""`
237. `Converts an amount of money from one currency to another on a specified date.`
238. `"""`
239. `if from_symbol == to_symbol:`
240. `return value`
241. ``
242. `factor = conversion_factor(from_symbol, to_symbol, date)`
243. ``
244. `if type(value) == float:`
245. `output = value * float(factor)`
246. `elif type(value) == Decimal:`
247. `output = Decimal(format(value * factor, '.%sf' % str(PRICE_PRECISION)))`
248. `elif type(value) in [np.float16, np.float32, np.float64, np.float128, np.float]:`
249. `output = float(value) * float(factor)`
250. `else:`
251. `output = None`
252. ``
253. `return output`
254. ``