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