Python Example Code

Posted by Jeffrey W on February 14, 2012 (03:42PM)

Hi all,

Has anyone been able to use python to connect to the TK API?  I know on the docs there is some example code for Ruby.

Here's what I have so far

#oauth2 can be installed using "pip install oauth2"
import oauth2 as oauth

consumer = oauth.Consumer(key="", secret = "")
request_token_url = "http://developers.tradeking.com/oauth/request_token"
client = oauth.Client(consumer)
resp, content = client.request(request_token_url, "GET")

But I don't know how to create the calls to get quotes, options, etc, or when the oauth token and oauth access token get used

Posted by Jeffrey W on February 14, 2012 (08:21PM)

Update:

so I found where the token goes

consumer = oauth.Consumer(key=consumer_key, secret = consumer_secret)
token = oauth.Token(key=oauth_token, secret=oauth_token_secret)
client = oauth.Client(consumer, token = token)

But I can't seem to make calls to get quotes

quote_url = "https://api.tradeking.com/v1/market/quotes.xml?symbols=AAPL"
resp, content = client.request(quote_url, "GET")


File "/home/jeffrey/python-envs/my-env/lib/python2.6/site-packages/httplib2/__init__.py", line 914, in connect
    raise SSLHandshakeError(e)
httplib2.SSLHandshakeError: [Errno 1] _ssl.c:490: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

Posted by M1Sports20 on February 14, 2012 (10:42PM)

I had the same problem with the certificate when using MONO.  I had to make some calls to override the validation of the certificate within the webrequest using MONO.  I am guessing you would have to do the same thing with python

Posted by Just Another Lousy Programmer on February 20, 2012 (01:08AM)

Hey Jeffry -

I don't blame you for being confused, it took quite a while for me to figure out how to retrieve TK information with Python. I have done this, but I have to warn you, this code is UGLY. It does the job, but I have made no attempt to clean it up at all. Eventually I plan to make it pythonic, but the code that I'm posting here is just what I hacked together to get the basics working. That said, this should work for you:

import sys
import string
from restkit import OAuthFilter, request    # http reading
import oauth2                                # oauth2 framework
import simplejson as json                    # json reader
import dateutil.parser as dateparse            # date processing
from operator import itemgetter                # list processing
import csv                                    # csv file processing

############################# tkquery.py #############################
# Routines for retrieving stock quotes from TradeKing

# urls for various queries
# balances': 'https://api.tradeking.com/v1/accounts/38547111/balances.json'
# history': 'https://api.tradeking.com/v1/accounts/38547111/history.json'
# holdings': 'https://api.tradeking.com/v1/accounts/38547111/holdings.json'
# orders': 'https://api.tradeking.com/v1/accounts/38547111/orders.json'
# chain': 'https://api.tradeking.com/v1/market/chain.json'
# stockquote': 'https://api.tradeking.com/v1/market/quotes.json'
# optionquote': 'https://api.tradeking.com/v1/market/quotes.json'
# watchlists': 'https://api.tradeking.com/v1/accounts/38547111/watchlist.json'
#
# urls that will be needed if we want to authenticate other people
# request access : 'http://developers.tradeking.com/oauth/request_token'
# access token : 'http://developers.tradeking.com/oauth/access_token'
# authorization : 'http://developers.tradeking.com/oauth/authorize'

# details of some of the JSON fields
# factor
#  contract size for an option
# sectype
#  CS = common stock
#  BOND = bonds
#  OPT = options
# accounttype gives long or short
#  3 or 5 = short
#  1 = long
#  2 = long margin
# purchase price
#  for holdings, its average price per share

# this whole thing needs to be abstracted to a class

def tk_query(queryurl, returntype='json'):
    # key and secret granted by service provider for this consumer application
    CONSUMER_KEY =       'CONSUMERKEY'
    CONSUMER_SECRET =    'CONSUMERSECRET'
    OAUTH_TOKEN_KEY =    'OAUTHTOKENKEY'
    OAUTH_TOKEN_SECRET = 'OAUTHTOKENSECRET'
    MEMBER_NUMBER = MEMBERNUMBER

    # set up an OAuth Consumer
    myconsumer = oauth2.Consumer(key=CONSUMER_KEY, secret=CONSUMER_SECRET)
    # manually update the access token/secret.
    mytoken = oauth2.Token(key=OAUTH_TOKEN_KEY, secret=OAUTH_TOKEN_SECRET)
    # make an oauth request
    auth = OAuthFilter('*', consumer=myconsumer, token=mytoken,
    method = oauth2.SignatureMethod_HMAC_SHA1())
    # get the response and return it
    queryresp = request(queryurl, 'GET', filters=[auth])
    if returntype == 'json':
        queryresult = json.loads(queryresp.body_string())
    else:
        queryresult = queryresp.body_string()
    return queryresult

# LIST OF STOCKS
# modified so that stocks are ALWAYS returned in a list!
#  located in query['response']['quotes']['instrumentquote']
def query_quote(s='F', returntype='json'):
    query = tk_query(
        ''.join(['https://api.tradeking.com/v1/market/quotes.json',
        '?symbols=', s, '&delayed=true']), returntype)
    if query_error(query) is None:
        query = reformat_quote_query(query)
        return query
    else:
        return IOError

# LIST OF OPTIONS
def query_chain(s='F', qexpiration='ALL', qrange='ALL_THE_MONEY',
                returntype='json'):
    return tk_query(''.join(['https://api.tradeking.com/v1/market/chains.json',
                    '?underlying=', s, '&type=PUT&expiration=',
                    qexpiration, '&range=', qrange]),
        returntype)

# SINGLE OPTION
def query_oquote(qunderlying='F',qexpiration='2011-06-24',
    qtype='CALL',qstrike='280.0'):
  return tk_query(''.join(['https://api.tradeking.com/v1/market/quotes.json',
    '?underlying=',qunderlying,'&expiration=',qexpiration,'&type=',qtype,
    '&strike=',qstrike,'&delayed=true']))

# WATCHLIST
def query_watchlist():
    return tk_query('https://api.tradeking.com/v1/watchlists.json')

# CURRENT BALANCES
def query_balance(returntype='json'):
    return tk_query('https://api.tradeking.com/v1/accounts/38547111/balances.json',
                    returntype)

# TRADING HISTORY
def query_history(qrange='all', qtransactions='trade', returntype='json'):
    return tk_query(''.join(['https://api.tradeking.com/v1/accounts/38547111/history.json?range=',
                    qrange,'&transactions=',
                    qtransactions]),returntype)

# CURRENT HOLDINGS
def query_holdings(returntype='json'):
    return tk_query('https://api.tradeking.com/v1/accounts/38547111/holdings.json',
                    returntype)

# OPEN ORDERS
def query_orders(returntype='json'):
    return tk_query('https://api.tradeking.com/v1/accounts/38547111/orders.json',
                    returntype)

# if there is an error in the tkread, we'll get a return dictionary
#  containing: query{'response':{'type':'Error'}}
def query_error(query):
    if 'type' in query['response']:
        return IOError
    else:
        pass

Posted by anonanonanon on May 19, 2012 (05:13PM)

Does anyone have Python sample code for completing a POST OAuth request to TradeKing?  I can perform GETs without trouble, but on the POST APIs such as "watchlists" or "accounts/:id/orders/preview" I always end up with an invalid signature.

I've tried this (seemingly abandoned) httplib2 based library:
https://github.com/simplegeo/python-oauth2

And this maintained fork of that library that is instead Requests based:
https://github.com/maraujop/requests-oauth


And also the oauth functionality built into restkit as per Just Another Lousy Programmer's sample code above.



All three of those libraries end up getting their POST based request signatures rejected by TradeKing.  I'm baffled.  I've actually spent time investigating the library code of python-oauth2 and requests-oauth and reading the RFC and messing around with this tool:
http://hueniverse.com/oauth/guide/authentication/ and I still can't get it to work.


Out of the box, python-oauth2 has a bug in handling POST requests where it's impossible to to force the library to use the "Authorization:" header (see class Client def request) around these lines
       if is_form_encoded:
            body = req.to_postdata()
        elif method == "GET":
            uri = req.to_url()
        else:
            headers.update(req.to_header(realm=realm))
Anyway, I patched that bug in my local copy of the library to populate the Authorize header but to no avail, my requests are still rejected with an invalid sig.  This is not a library I can recommend.


requests-oauth is seemingly better written/more python idiomatic and fixes the auth problem (just pass "header_auth=True" to OAuthHook), however I still get my sig rejected with that lib too, argh!


With both requests-oauth and python-oauth2 I've tried dumping the outgoing request and comparing all the sig/hash/key to the expected values to the debug tool on hueniverse.com and my outgoing values all appear correct.


Also to fix Jeffrery W's SSL error with python-oauth2 just do this:
import certifi
client = oauth.Client(consumer, token)
client.ca_certs = certifi.where()
but again, you'll still only be able to do GETs with python-oauth2 out of the box.

Posted by anonanonanon on May 20, 2012 (04:28PM)

Figured it out!  And since it's a #1 pet peeve of mine when people figure things out and then don't share, here's the solution.


Don't send a "realm" key/value pair in the Authorization header.  It seems to me TradeKing's implementation of OAuth is broken.  Check out the relevant  RFC section: http://tools.ietf.org/html/rfc5849#section-3.5.1

>
> "realm" parameter MAY be added and interpreted
> per http://tools.ietf.org/html/rfc2617#section-1.2
>

TradeKing's implementation appears to use "realm" as part of the checksum for the signature and, well, it just doesn't work out.


I figured this out by by getting a working POST request from the Java Demo https://developers.tradeking.com/documentation/java capturing it, and then comparing it to a captured request from my Python code.  That was the major difference, the scribe oauth library https://github.com/fernandezpablo85/scribe-java doesn't send a "realm" in the Authorization: header line whereas the Python libraries (requests-oauth, python-oauth2) do.

To fix this, git clone git://github.com/maraujop/requests-oauth.git and then in class OAuthHook def authorization_header patch it like so:
-         authorization_headers = 'OAuth realm="",'
+        authorization_headers = 'OAuth '

And then use requests normally e.g.:

def requestRequests(url, method, params={}, body=''):
    CONSUMER_KEY = 'foo'
    CONSUMER_SECRET = 'bar'
    TOKEN_KEY = 'baz'
    TOKEN_SECRET = 'qux'
    oauth_hook = OAuthHook(TOKEN_KEY, TOKEN_SECRET, CONSUMER_KEY, CONSUMER_SECRET, header_auth=True)
    client = requests.session(hooks={'pre_request': oauth_hook})
    url += '?'+urlencode(params)
    if 'GET' == method:
        r = client.get(url)
    elif 'POST' == method:
        r = client.post(url, data=body)
    return r.content

I'm assuming you have to do something very similar with restkit, but I didn't bother looking into patching it after I tried it the first time and it didn't work out of the box.


Or git clone git://github.com/simplegeo/python-oauth2.git and then in class Request def to_header and patch it like so:
-         auth_header = 'OAuth realm="%s"' % realm
+        auth_header = 'OAuth '

And then you need to implement your own functional version of class Client def request, mine ended up looking like this:

def requestHttp(url, method, params={}, body=''):
    CONSUMER_KEY = 'foo'
    CONSUMER_SECRET = 'bar'
    TOKEN_KEY = 'baz'
    TOKEN_SECRET = 'qux'
    consumer = oauth.Consumer(CONSUMER_KEY, CONSUMER_SECRET)
    token = oauth.Token(TOKEN_KEY, TOKEN_SECRET)
    client = oauth.Client(consumer, token)
    signature_method = oauth.SignatureMethod_HMAC_SHA1()
    url += '?'+urlencode(params)
    req = oauth.Request.from_consumer_and_token(consumer,
        token=token, http_method=method, http_url=url,
        parameters=params, body=body, is_form_encoded=False)
    req.sign_request(signature_method, consumer, token)
    headers = {}
    if 'GET' == method:
        url = req.to_url()
    elif 'POST' == method:
        headers.update(req.to_header())
    h = httplib2.Http()
    h.ca_certs = certifi.where()
    return h.request(url, method=method, body=body,
        headers=headers, redirections=httplib2.DEFAULT_MAX_REDIRECTS,
        connection_type=None)


So, there you have it.  Here's some final words of wisdom for all you service writers out there: be strict in what you emit and liberal in what you accept.

Posted by Jason Barry on May 23, 2012 (05:12PM)

I updated the other post.  You're dead on.  We fixed the oauth realm bug.  There will be another bug fix associated with formurlencoded POST bodies not hashing correctly going out as well.

The release is slated for next week's release window (Thursday night).  
Thanks for reporting this and tracking down the cause.  Nice work.

Thanks!
Jason

Posted by aaen on June 15, 2012 (03:12PM)

oauth2 example, which gets around the httplib2 SSL issue that's mentioned in a few posts using httplib2.Http(disable_ssl_certificate_validation=True)

import httplib2
import oauth2 as oauth
import time

url = "https://api.tradeking.com/v1/member/profile.json"

params = {
    'oauth_version': "1.0",
    'oauth_nonce': oauth.generate_nonce(),
    'oauth_timestamp': int(time.time())
}

token = oauth.Token(key="", secret="")
consumer = oauth.Consumer(key="", secret="")

params['oauth_token'] = token.key
params['oauth_consumer_key'] = consumer.key

req = oauth.Request(method="GET", url=url, parameters=params)

signature_method = oauth.SignatureMethod_HMAC_SHA1()
req.sign_request(signature_method, consumer, token)

h = httplib2.Http(disable_ssl_certificate_validation=True)
resp, content = h.request(url, method="GET", body='',headers=req.to_header())

Posted by aaen on June 15, 2012 (03:52PM)

oauth2 with SSL

Download the AddTrust External CA Root certificate (google "addtrust external ca root" to find where to download it)

convert to pem:
openssl x509 -in AddTrustExternalCARoot.crt -out AddTrustExternalCARoot.pem -outform PEM

replace httplib2.Http(disable_ssl_certificate_validation=True) in my prior post with:
httplib2.Http(ca_certs='/path/AddTrustExternalCARoot.pem')

Or add the certificate to httplib2's certificate store rather than referencing the pem file directly, in which case an oauth2 example like this should work (now that the realm bug is fixed):

import oauth2 as oauth

token = oauth.Token(key="", secret="")
consumer = oauth.Consumer(key="", secret="")

client = oauth.Client(consumer,token)

resp, content = client.request("https://api.tradeking.com/v1/member/profile.xml")

Posted by flashcrash on August 07, 2012 (03:22PM)

I'm still running into "signature_method_rejected" when I do POST requests. I've tried both preview orders and quotes calls.

All of my GET requests work, but only if I pass the oauth parameters in the URL rather than the header. For quotes calls, all other parameters (besides using GET and passing oauth params in the URL) are exactly the same between the calls that work and those that throw signature_method_rejected errors.

When I do a POST request, my Authorization header looks like this:

Authorization: OAuth realm="",oauth_nonce="7048205",oauth_timestamp="1344366588",oauth_consumer_key="MY_KEY",oauth_signature_method="HMAC-SHA1",oauth_version="1.0",oauth_token="MY_TOKEN",oauth_signature="Y5O23JigI9j5DxuMec9JyFMHN6A%3D"

I'm using this library: https://github.com/maraujop/requests-oauth without modifications.

Posted by tulsaengineer on October 27, 2012 (03:15PM)

Have the issues with realm been resolved?  I am getting the following error in objective C when I try to do an order preview:

Error

    AuthenticationFailure

    signature_invalid

All of my GET requests work fine.

Regards.

Posted by tulsaengineer on October 28, 2012 (04:31PM)

Please ignore my question.  I was able to fix the problem myself.  I had an error in the FIXML preview.  I verified that the realm worked fine.  Thanks.

Posted by anonanonanon on February 04, 2013 (05:05PM)

anonanonanon said: Figured it out!  


Figured it out, again!

TradeKing now also requires a Content-Type: header be passed along to avoid an invalid signature on POST requests.  The contents of the value are apparently ignored, but 'text/xml' works just fine.


You must Log In to post to this forum.

Not a member? Register Now to …

  • See what other traders are doing
  • Make your own trades public
  • Share your thoughts on a trade
  • Join or start a group
  • Connect with like-minded traders