diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index e0c1f65..a446b12 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -24,6 +24,13 @@
+
+
+
diff --git a/.metrik.enc b/.metrik.enc
new file mode 100644
index 0000000..57402e9
Binary files /dev/null and b/.metrik.enc differ
diff --git a/.travis.yml b/.travis.yml
index 4fc0c9e..902693e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,5 +10,8 @@ services:
before_install:
- export TZ=America/New_York
+ # Encrypted .metrik.enc file contains credentials for services that are behind OAuth, etc.
+ - openssl aes-256-cbc -K $encrypted_4cca49abdb96_key -iv $encrypted_4cca49abdb96_iv -in .metrik.enc -out ~\/.metrik -d
+
install: pip install -r requirements.txt
script: python setup.py test
\ No newline at end of file
diff --git a/metrik/__init__.py b/metrik/__init__.py
index 73d59c8..1e9ec6d 100644
--- a/metrik/__init__.py
+++ b/metrik/__init__.py
@@ -1,2 +1,2 @@
-__version__ = '0.2.3'
+__version__ = '0.3.0'
__release__ = __version__
\ No newline at end of file
diff --git a/metrik/tasks/tradeking.py b/metrik/tasks/tradeking.py
new file mode 100644
index 0000000..c3044d5
--- /dev/null
+++ b/metrik/tasks/tradeking.py
@@ -0,0 +1,82 @@
+import requests
+from requests_oauthlib import OAuth1Session
+from luigi.parameter import DateParameter, Parameter
+from datetime import timedelta
+from dateutil.parser import parse
+import logging
+
+from metrik.tasks.base import MongoCreateTask, MongoRateLimit
+from metrik.conf import get_config
+
+
+class TradekingApi(object):
+
+ format_json = '.json'
+ format_xml = '.xml'
+ root_url = 'https://api.tradeking.com/v1/'
+
+ def __init__(self):
+ config = get_config()
+ self.session = OAuth1Session(
+ client_key=config.get('tradeking', 'consumer_key'),
+ client_secret=config.get('tradeking', 'consumer_secret'),
+ resource_owner_key=config.get('tradeking', 'oauth_token'),
+ resource_owner_secret=config.get('tradeking', 'oauth_token_secret')
+ )
+
+ def api_request(self, url, params=None, format=format_json, **kwargs):
+ full_url = self.root_url + url + format
+ return self.session.get(full_url, params=params, **kwargs)
+
+
+class Tradeking1mTimesales(MongoCreateTask):
+ # While this is marked as an idempotent task, it only goes back about
+ # a week or so. Be careful.
+ present = DateParameter()
+ symbol = Parameter()
+
+ def acquire_lock(self, service, limit, interval, max_tries=5, backoff=.5):
+ return super(Tradeking1mTimesales, self).acquire_lock(
+ 'tradeking', 60, timedelta(minutes=1)
+ )
+
+ @staticmethod
+ def retrieve_data(present, symbol):
+ ratelimit = MongoRateLimit()
+ tradeking = TradekingApi()
+ did_acquire = ratelimit.acquire_lock(
+ service='tradeking',
+ limit=60,
+ interval=timedelta(minutes=1)
+ )
+ if did_acquire:
+ json_data = tradeking.api_request('market/timesales', {
+ 'symbols': symbol,
+ 'interval': '1min',
+ 'startdate': present.strftime('%Y-%m-%d'),
+ 'enddate': present.strftime('%Y-%m-%d')
+ }).json()
+
+ quotes = json_data['response']['quotes']['quote']
+ def format_quote(quote):
+ return {
+ 'last': float(quote['last']),
+ 'lo': float(quote['lo']),
+ 'vl': int(quote['vl']),
+ 'datetime': parse(quote['datetime']),
+ 'incr_vl': int(quote['incr_vl']),
+ 'hi': float(quote['hi']),
+ 'timestamp': parse(quote['timestamp']),
+ 'date': parse(quote['date']).date(),
+ 'opn': float(quote['opn'])
+ }
+ quotes_typed = [format_quote(q) for q in quotes]
+
+ return {
+ 'symbol': symbol,
+ 'quotes': quotes_typed
+ }
+ else:
+ logging.error('Unable to acquire lock for Tradeking ticker {}'
+ .format(symbol))
+ return {}
diff --git a/setup.py b/setup.py
index 6881353..21a5820 100644
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,8 @@ setup(
],
tests_require=[
'pytest',
- 'pytest-catchlog'
+ 'pytest-catchlog',
+ 'pandas-datareader'
],
entry_points={
'console_scripts': [
diff --git a/test/tasks/test_tradeking.py b/test/tasks/test_tradeking.py
new file mode 100644
index 0000000..c6499c1
--- /dev/null
+++ b/test/tasks/test_tradeking.py
@@ -0,0 +1,37 @@
+from unittest import TestCase
+from datetime import datetime
+from pandas_datareader.data import get_data_yahoo
+from numpy.testing import assert_allclose
+import pytest
+
+from metrik.tasks.tradeking import Tradeking1mTimesales
+from metrik.trading_days import TradingDay
+
+
+@pytest.mark.parametrize('ticker', [
+ 'AAPL', 'GOOG', 'SPY', 'REGN', 'SWHC', 'BAC', 'NVCR'
+])
+def test_returns_verifiable(ticker):
+ # Test that the quotes line up with data off of Yahoo
+ now = datetime.now()
+ prior_day = now - TradingDay(1)
+
+ quotes = Tradeking1mTimesales.retrieve_data(prior_day, ticker)
+
+ yahoo_ohlc = map(tuple, get_data_yahoo(ticker, prior_day, prior_day)[
+ ['Open', 'High', 'Low', 'Close']
+ ].values)[0]
+
+ open = high = close = 0
+ low = 999999
+ for index, quote in enumerate(quotes['quotes']):
+ if index == 0:
+ open = quote['opn']
+ if index == len(quotes['quotes']) - 1:
+ close = quote['last']
+ high = max(high, quote['hi'])
+ low = min(low, quote['lo'])
+
+ tradeking_ohlc = (open, high, low, close)
+
+ assert_allclose(tradeking_ohlc, yahoo_ohlc, rtol=1e-3)