From 2186762c36041edf58b1afb2c3e911bf09d4bcab Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 24 Jul 2017 19:48:50 -0500 Subject: [PATCH] python client for od --- .gitignore | 134 +++++++++++++++++++++ .vscode/launch.json | 204 ++++++++++++++++++++++++++++++++ README.md | 33 ++++++ ondemand/__init__.py | 3 + ondemand/ondemand_client.py | 230 ++++++++++++++++++++++++++++++++++++ setup.py | 14 +++ test.py | 92 +++++++++++++++ 7 files changed, 710 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 README.md create mode 100644 ondemand/__init__.py create mode 100644 ondemand/ondemand_client.py create mode 100644 setup.py create mode 100644 test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7bc2f68 --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/ + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5b2c335 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,204 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "${workspaceRoot}", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "PySpark", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "osx": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit" + }, + "windows": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit.cmd" + }, + "linux": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit" + }, + "program": "${file}", + "cwd": "${workspaceRoot}", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Python Module", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "module": "module.name", + "cwd": "${workspaceRoot}", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Integrated Terminal/Console", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "", + "console": "integratedTerminal", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit" + ] + }, + { + "name": "External Terminal/Console", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "", + "console": "externalTerminal", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit" + ] + }, + { + "name": "Django", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/manage.py", + "cwd": "${workspaceRoot}", + "args": [ + "runserver", + "--noreload" + ], + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput", + "DjangoDebugging" + ] + }, + { + "name": "Flask", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "pythonPath": "${config:python.pythonPath}", + "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter", + "cwd": "${workspaceRoot}", + "env": { + "FLASK_APP": "${workspaceRoot}/quickstart/app.py" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Flask (old)", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/run.py", + "cwd": "${workspaceRoot}", + "args": [], + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Pyramid", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "cwd": "${workspaceRoot}", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "args": [ + "${workspaceRoot}/development.ini" + ], + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput", + "Pyramid" + ] + }, + { + "name": "Watson", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/console.py", + "cwd": "${workspaceRoot}", + "args": [ + "dev", + "runserver", + "--noreload=True" + ], + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Attach (Remote Debug)", + "type": "python", + "request": "attach", + "localRoot": "${workspaceRoot}", + "remoteRoot": "${workspaceRoot}", + "port": 3000, + "secret": "my_secret", + "host": "localhost" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3520c8e --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +## Python client for Barchart OnDemand + +Get access to market data and the OnDemand APIs in just a few lines of code. + +### Installation + +``` +python setup.py install +``` + +### Usage + +```python +import ondemand + +od = ondemand.OnDemandClient(api_key='CHANGE_ME') + +# get quote data for Apple and Microsoft +quotes = od.quote('AAPL,MSFT')['results'] + +for q in quotes: + print('Symbol: %s, Last Price: %s' % (q['symbol'], q['lastPrice'])) + +# get 1 minutes bars for Apple +resp = od.history('AAPL', 'minutes', maxRecords=50, interval=1) + +# generic request by API name +resp = od.get('getQuote', symbols='AAPL,EXC', fields='bid,ask') +``` + +### Version + +- 1.0 - 7/27/2017 -- init \ No newline at end of file diff --git a/ondemand/__init__.py b/ondemand/__init__.py new file mode 100644 index 0000000..2e7b8e5 --- /dev/null +++ b/ondemand/__init__.py @@ -0,0 +1,3 @@ +from .ondemand_client import OnDemandClient, OnDemandError + +VERSION = '1.0.0' diff --git a/ondemand/ondemand_client.py b/ondemand/ondemand_client.py new file mode 100644 index 0000000..0f91926 --- /dev/null +++ b/ondemand/ondemand_client.py @@ -0,0 +1,230 @@ +""" +Simple Python client for Barchart OnDemand + +https://www.barchartondemand.com/api + +""" + +import requests + + +class OnDemandError(RuntimeError): + pass + + +class OnDemandClient(object): + """ + Example usage:: + + import ondemand + + od = ondemand.OnDemandClient(api_key='CHANGE_ME') + + od.debug = True # turn on debug logging + + quotes = od.quote('AAPL,NEM')['results'] + print(quotes) + """ + + debug = False + + def __init__(self, api_key=None, end_point='https://ondemand.websol.barchart.com/'): + self.endpoint = end_point + self.api_key = api_key + print('Barchart OnDemand Client: ' + self.endpoint) + + def _do_call(self, url, params): + if not isinstance(params, dict): + params = dict() + + if self.api_key: + params['apikey'] = self.api_key + + headers = dict() + headers['X-OnDemand-Client'] = 'bc_python' + + if self.debug: + print('do call with params: %s, url: %s' % (params, url)) + + resp = requests.get(url, params=params, timeout=60, headers=headers) + + if self.debug: + print('resp code: %s, resp text: %s' % (resp.status_code, resp.text)) + + if resp.status_code != 200: + raise OnDemandError('Request Failed: %s. Text: %s' % (resp.status_code, resp.text)) + + try: + result = resp.json() + except Exception as e: + raise OnDemandError( + 'Failed to parse JSON response %s. Resp Code: %s. Text: %s' % (e, resp.status_code, resp.text)) + finally: + resp.connection.close() + + return result + + def quote(self, symbols, fields=''): + params = dict(symbols=symbols, fields=fields) + return self._do_call(self.endpoint + 'getQuote.json', params) + + def quote_eod(self, symbols, exchange): + params = dict(symbols=symbols, exchange=exchange) + return self._do_call(self.endpoint + 'getQuoteEod.json', params) + + def profile(self, symbols, fields=''): + params = dict(symbols=symbols, fields=fields) + return self._do_call(self.endpoint + 'getProfile.json', params) + + def equities_by_exchange(self, exchange, fields=''): + params = dict(exchange=exchange, fields=fields) + return self._do_call(self.endpoint + 'getEquitiesByExchange.json', params) + + def futures_by_exchange(self, exchange, **kwargs): + params = dict(exchange=exchange) + kwargs.update(params) + return self._do_call(self.endpoint + 'getFuturesByExchange.json', params) + + def futures_options(self, root, **kwargs): + params = dict(root=root) + kwargs.update(params) + return self._do_call(self.endpoint + 'getFuturesOptions.json', params) + + def special_options(self, root, **kwargs): + params = dict(root=root) + kwargs.update(params) + return self._do_call(self.endpoint + 'getSpecialOptions.json', params) + + def equity_options(self, underlying_symbols, **kwargs): + params = dict(underlying_symbols=underlying_symbols) + kwargs.update(params) + return self._do_call(self.endpoint + 'getEquityOptions.json', params) + + def equity_options_intraday(self, underlying_symbols, **kwargs): + params = dict(underlying_symbols=underlying_symbols) + kwargs.update(params) + return self._do_call(self.endpoint + 'getEquityOptionsIntraday.json', params) + + def equity_options_history(self, symbol, **kwargs): + params = dict(symbol=symbol) + kwargs.update(params) + return self._do_call(self.endpoint + 'getEquityOptionsHistory.json', kwargs) + + def forex_forward_curves(self, symbols, **kwargs): + params = dict(symbols=symbols) + kwargs.update(params) + return self._do_call(self.endpoint + 'getForexForwardCurves.json', kwargs) + + def history(self, symbol, historical_type, **kwargs): + params = dict(symbol=symbol, type=historical_type) + kwargs.update(params) + return self._do_call(self.endpoint + 'getHistory.json', kwargs) + + def financial_highlights(self, symbols, fields=''): + params = dict(fields=fields, symbols=symbols) + return self._do_call(self.endpoint + 'getFinancialHighlights.json', params) + + def financial_ratios(self, symbols, fields=''): + params = dict(fields=fields, symbols=symbols) + return self._do_call(self.endpoint + 'getFinancialRatios.json', params) + + def cash_flow(self, symbols, **kwargs): + params = dict(symbols=symbols) + kwargs.update(params) + return self._do_call(self.endpoint + 'getCashFlow.json', kwargs) + + def ratings(self, symbols, fields=''): + params = dict(fields=fields, symbols=symbols) + return self._do_call(self.endpoint + 'getRatings.json', params) + + def index_members(self, symbol, fields=''): + params = dict(fields=fields, symbol=symbol) + return self._do_call(self.endpoint + 'getIndexMembers.json', params) + + def income_statements(self, symbols, frequency, **kwargs): + params = dict(symbols=symbols, frequency=frequency) + kwargs.update(params) + return self._do_call(self.endpoint + 'getIncomeStatements.json', kwargs) + + def competitors(self, symbol, **kwargs): + params = dict(symbol=symbol) + kwargs.update(params) + return self._do_call(self.endpoint + 'getCompetitors.json', kwargs) + + def insiders(self, symbol, insider_type, **kwargs): + params = dict(symbol=symbol, type=insider_type) + kwargs.update(params) + return self._do_call(self.endpoint + 'getInsiders.json', kwargs) + + def balance_sheets(self, symbols, frequency, **kwargs): + params = dict(symbols=symbols, frequency=frequency) + kwargs.update(params) + return self._do_call(self.endpoint + 'getBalanceSheets.json', kwargs) + + def corporate_actions(self, symbols, **kwargs): + params = dict(symbols=symbols) + kwargs.update(params) + return self._do_call(self.endpoint + 'getCorporateActions.json', params) + + def earnings_estimates(self, symbols, **kwargs): + params = dict(symbols=symbols) + kwargs.update(params) + return self._do_call(self.endpoint + 'getEarningsEstimates.json', params) + + def chart(self, symbols, **kwargs): + params = dict(symbols=symbols) + kwargs.update(params) + return self._do_call(self.endpoint + 'getChart.json', kwargs) + + def technicals(self, symbols, **kwargs): + params = dict(symbols=symbols) + kwargs.update(params) + return self._do_call(self.endpoint + 'getTechnicals.json', kwargs) + + def leaders(self, asset_type, **kwargs): + params = dict(assetType=asset_type) + kwargs.update(params) + return self._do_call(self.endpoint + 'getLeaders.json', kwargs) + + def highs_lows(self, asset_type, **kwargs): + params = dict(assetType=asset_type) + kwargs.update(params) + return self._do_call(self.endpoint + 'getHighsLows.json', kwargs) + + def sectors(self, sector_period, **kwargs): + params = dict(sectorPeriod=sector_period) + kwargs.update(params) + return self._do_call(self.endpoint + 'getSectors.json', kwargs) + + def news(self, sources, **kwargs): + params = dict(sources=sources) + kwargs.update(params) + return self._do_call(self.endpoint + 'getNews.json', kwargs) + + def news_sources(self, **kwargs): + return self._do_call(self.endpoint + 'getNewsSources.json', kwargs) + + def news_categories(self, **kwargs): + return self._do_call(self.endpoint + 'getNewsCategories.json', kwargs) + + def sec_filings(self, symbols, filing_type, **kwargs): + params = dict(symbols=symbols, filingType=filing_type) + kwargs.update(params) + return self._do_call(self.endpoint + 'getSECFilings.json', kwargs) + + def weather(self, **kwargs): + return self._do_call(self.endpoint + 'getWeather.json', kwargs) + + def usda_grain_prices(self, **kwargs): + return self._do_call(self.endpoint + 'getUSDAGrainPrices.json', kwargs) + + def etf_details(self, symbols, **kwargs): + kwargs.update(dict(symbols=symbols)) + return self._do_call(self.endpoint + 'getETFDetails.json', kwargs) + + def etf_constituents(self, symbol, **kwargs): + kwargs.update(dict(symbol=symbol)) + return self._do_call(self.endpoint + 'getETFConstituents.json', kwargs) + + def get(self, api_name, **kwargs): + return self._do_call(self.endpoint + api_name + '.json', kwargs) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..51378a5 --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup + +setup( + name='ondemand', + version='1.0.0', + description='Simple client for Barchart OnDemand REST APIs', + author="Mike Ehrenberg", + author_email="solutions@barchart.com", + url='https://www.barchartondemand.com/api', + install_requires=[ + 'requests>=2.3.0' + ], + license='LICENSE.txt', + packages=['ondemand']) diff --git a/test.py b/test.py new file mode 100644 index 0000000..5c40089 --- /dev/null +++ b/test.py @@ -0,0 +1,92 @@ +""" +python test.py my-api-key +""" + +import ondemand +import sys + +od = ondemand.OnDemandClient(api_key=sys.argv[1]) +od.debug = True + +# Get a Quote +resp = od.quote('AAPL', 'bid,ask') +print('') +print(resp) +print('') + +for q in resp['results']: + print('Symbol: %s, Last Price: %s' % (q['symbol'], q['lastPrice'])) + +# Get Historical Data +resp = od.history('AAPL', 'minutes', maxRecords=50, interval=1) +# print('') +# print('getHistory', resp) +# print('') + +resp = od.profile('AAPL', fields='qtrOneEarnings,qtrTwoEarnings') +print('') +print('getProfile', resp) +print('') + +resp = od.financial_highlights('AAPL', fields='lastQtrEPS') +print('') +print('getFinancialHighlights', resp) +print('') + +resp = od.financial_ratios('AAPL') +print('') +print('getFinancialRatios', resp) +print('') + +resp = od.income_statements('AAPL', 'Quarter', rawData=1) +print('') +print('getIncomeStatements', resp) + +resp = od.competitors('AAPL', fields='fiftyTwoWkLowDate', maxRecords=1) +print('') +print('getCompetitors', resp) +print('') + +resp = od.ratings('AAPL', 'strongBuy') +print('') +print('getRatings', resp) +print('') + +resp = od.index_members('$SPX') +print('') +print('getIndexMembers', resp) +print('') + +resp = od.cash_flow('AAPL,GOOG', reportPeriod='12M', numberOfYears=2) +print('') +print('getCashFlow', resp) +print('') + +# resp = od.futures_options('ES') +# print('') +# print('getFuturesOptions', resp) +# print('') + +# resp = od.equity_options('AAPL', optionType='Monthly') +# print('') +# print('getEquityOptions', resp) +# print('') + +resp = od.usda_grain_prices(commodityTypes='SWW') +print('') +print('getUSDAGrainPrices', resp) +print('') + +resp = od.chart('AAPL', height=400, width=400, type='LINE') +print('') +print('getChart', resp) +print('') + +resp = od.get('getQuote', symbols='AAPL,EXC', fields='bid,ask') +print('') +print('generic get', resp) +print('') + +resp = od.insiders('AAPL', 'H') +print('') +print('getInsiders', resp)