Skip to content

Commit

Permalink
Creates QuantConnect ApiPython project
Browse files Browse the repository at this point in the history
This new project includes the source and tools to develop the quantconnect python package that provides interaction via API with quantconnect.com.
  • Loading branch information
AlexCatarino committed Feb 9, 2018
1 parent fc25c3a commit 7c5c45d
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 59 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -257,3 +257,5 @@ Charts/
# QuantConnect plugin files
QuantConnectProjects.xml
Launcher/Plugins/*
/ApiPython/dist
/ApiPython/quantconnect.egg-info
1 change: 0 additions & 1 deletion Api/QuantConnect.Api.csproj
Expand Up @@ -131,7 +131,6 @@
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
<None Include="Api.py" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Expand Down
1 change: 1 addition & 0 deletions ApiPython/MANIFEST.in
@@ -0,0 +1 @@
include LICENSE
52 changes: 52 additions & 0 deletions ApiPython/QuantConnect.ApiPython.pyproj
@@ -0,0 +1,52 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>1c47f4db-2aff-4fae-9142-b33be654a516</ProjectGuid>
<ProjectHome>
</ProjectHome>
<StartupFile>tests\test_api.py</StartupFile>
<SearchPath>
</SearchPath>
<WorkingDirectory>.</WorkingDirectory>
<OutputPath>.</OutputPath>
<Name>QuantConnect.ApiPython</Name>
<RootNamespace>ApiPython</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<ItemGroup>
<Compile Include="quantconnect\api.py" />
<Compile Include="quantconnect\__init__.py" />
<Compile Include="setup.py" />
<Compile Include="tests\test_api.py" />
<Compile Include="tests\__init__.py">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="tests\" />
<Folder Include="quantconnect\" />
</ItemGroup>
<ItemGroup>
<Content Include="MANIFEST.in" />
<Content Include="README.rst">
<SubType>Code</SubType>
</Content>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
<!-- Uncomment the CoreCompile target to enable the Build command in
Visual Studio and specify your pre- and post-build commands in
the BeforeBuild and AfterBuild targets below. -->
<!--<Target Name="CoreCompile" />-->
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
</Project>
31 changes: 31 additions & 0 deletions ApiPython/README.rst
@@ -0,0 +1,31 @@
QuantConnect.com Interaction via API (python edition)
=========

What is it
------------------

**quantconnect** is a Python package providing interaction via API with `QuantConnect.com <https://www.quantconnect.com>`_.

Installation Instructions
------------------

This package can be installed with pip:

>>> pip install quantconnect -U

Enter Python’s interpreter and type the following commands:

>>> from quantconnect.api import Api
>>> api = Api(your-user-id, your-token)
>>> p = api.list_projects()
>>> print(len(p['projects']))

For your user id and token, please visit `your account page <https://www.quantconnect.com/account>`_.

Create the package
------------------

Edit setup.py to set the desired package version. Then, create the distribution and upload it with `twine <https://pypi.python.org/pypi/twine>`_.:

>>> python setup.py sdist
>>> twine upload dist/*
69 changes: 69 additions & 0 deletions ApiPython/quantconnect/__init__.py
@@ -0,0 +1,69 @@
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from hashlib import sha256
from logging import exception
from requests import Request, Session
from time import time

def create_secure_hash(timestamp, token):
"""Generate a secure hash for the authorization headers.
Returns:
Time based hash of user token and timestamp."""
hash_data = sha256()
hash_data.update('{0}:{1}'.format(token, timestamp).encode('utf-8'))
return hash_data.hexdigest()


class ApiConnection:
"""API Connection and Hash Manager
Attributes:
client(str): Authorized client to use for requests.
userId(int/str): User Id number from QuantConnect.com account. Found at www.quantconnect.com/account.
token(str): Access token for the QuantConnect account. Found at www.quantconnect.com/account.
"""
def __init__(self, userId, token):
self.client = 'https://www.quantconnect.com/api/v2/'
self.userId = str(userId)
self.token = token
if len(self.userId) * len(self.token) == 0:
exception('Cannot use empty string for userId or token. Found yours at www.quantconnect.com/account')


def connected(self):
"""Return true if connected successfully."""
request = Request('GET', 'authenticate')
result = self.try_request(request)
return result['success']

def try_request(self, request):
"""Place a secure request and get back an object of type T.
Args:
request: Result object of the request
Returns:
result: request response
"""
timestamp = int(time())
hash = create_secure_hash(timestamp, self.token)
request.auth = (self.userId, hash)
request.headers.update({'Timestamp': str(timestamp)})
request.url = self.client + request.url

try:
session = Session()
response = session.send(request.prepare())
session.close()
return response.json()
except:
exception('Failed to make REST request to {0}'.format(request.url))
return { 'success': False }
56 changes: 5 additions & 51 deletions Api/Api.py → ApiPython/quantconnect/api.py
Expand Up @@ -12,29 +12,17 @@
# limitations under the License.

from datetime import datetime
from hashlib import sha256
from json import dumps
from logging import exception
from requests import Session, Request
from time import time, mktime

def create_secure_hash(timestamp, token):
"""Generate a secure hash for the authorization headers.
Returns:
Time based hash of user token and timestamp."""
hash_data = sha256()
hash_data.update("{0}:{1}".format(token, timestamp).encode('utf-8'))
return hash_data.hexdigest()
from requests import Request
from time import mktime
from quantconnect import ApiConnection

class Api:
"""QuantConnect.com Interaction Via API.
Attributes:
userId(int): User Id number from QuantConnect.com account. Found at www.quantconnect.com/account.
userId(int/str): User Id number from QuantConnect.com account. Found at www.quantconnect.com/account.
token(str): Access token for the QuantConnect account. Found at www.quantconnect.com/account.
timestamp(int): UTC timestamp. Timestamps older than 1800 seconds will not work.
client(str): Authorized client to use for requests.
"""

def __init__(self, userId, token):
self.api_connection = ApiConnection(userId, token)

Expand Down Expand Up @@ -467,38 +455,4 @@ def download_data(self, symbol, securityType, market, resolution, date, fileName
with open(fileName + '.zip', "wb") as code:
code.write(request.content)

return link['success']


class ApiConnection:
client = "https://www.quantconnect.com/api/v2/"

def __init__(self, userId, token):
self.userId = userId
self.token = token

def connected(self):
"""Return true if connected successfully."""
request = Request('GET', "authenticate")
result = self.try_request(request)
return result['success']

def try_request(self, request):
"""Place a secure request and get back an object of type T.
Args:
request: Result object of the request
Returns:
result: request response
"""
timestamp = int(time())
hash = create_secure_hash(timestamp, self.token)
request.auth = (self.userId, hash)
request.headers.update({"Timestamp": str(timestamp)})
request.url = self.client + request.url

try:
response = Session().send(request.prepare())
return response.json()
except:
exception("Failed to make REST request to {0}".format(request.url))
return { 'success': False }
return link['success']
35 changes: 35 additions & 0 deletions ApiPython/setup.py
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from setuptools import setup, find_packages

# https://github.com/QuantConnect/Lean/blob/master/LICENSE
with open('../LICENSE') as f:
license = f.read()

with open('README.rst') as f:
readme = f.read()

setup(
name='quantconnect',
version='0.1',
description = 'QuantConnect API',
long_description=readme,
author = 'QuantConnect Python Team',
author_email = 'support@quantconnect.com',
url='https://www.quantconnect.com/',
license=license,
packages = find_packages(exclude=('tests', 'docs')),
install_requires=['requests']
)
12 changes: 12 additions & 0 deletions ApiPython/tests/__init__.py
@@ -0,0 +1,12 @@
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
13 changes: 7 additions & 6 deletions Tests/API/ApiTests.py → ApiPython/tests/test_api.py
Expand Up @@ -19,8 +19,9 @@
class TestApi(unittest.TestCase):

def setUp(self):
self.userId = "1"
self.testToken = "ec87b337ac970da4cbea648f24f1c851"
# Please add your credentials. Found at https://www.quantconnect.com/account
self.userId = ""
self.testToken = ""
self.api = Api(self.userId, self.testToken)

def test_Projects_CanBeCreatedAndDeleted_Successfully(self):
Expand Down Expand Up @@ -65,8 +66,8 @@ def test_ApiWillAuthenticate_InvalidCredentials_Unsuccessfully(self):
def test_CRUD_ProjectFiles_Successfully(self):
"""Test updating the files associated with a project"""

real_file_code = get_content('/../../Algorithm.Python/BasicTemplateAlgorithm.py')
second_real_file_code = get_content('/../../Algorithm.Python/BasicTemplateForexAlgorithm.py')
real_file_code = get_content('BasicTemplateAlgorithm.py')
second_real_file_code = get_content('BasicTemplateForexAlgorithm.py')

fakeFile = {"name":"Hello.py", "code": "Hello World!"}
realFile = {"name":"main.py", "code": real_file_code}
Expand Down Expand Up @@ -123,8 +124,8 @@ def test_CRUD_ProjectFiles_Successfully(self):
self.assertTrue(deleteProject['success'])

def get_content(file):
with open(os.getcwd() + file, 'r') as myfile:
return myfile.read()#.decode("utf-8-sig").encode("utf-8")
with open("../Algorithm.Python/" + file, 'r') as f:
return f.read()

if __name__ == '__main__':
unittest.main(argv=['first-arg-is-ignored'], exit=False)
6 changes: 6 additions & 0 deletions QuantConnect.Lean.sln
Expand Up @@ -56,6 +56,8 @@ Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "QuantConnect.Algorithm.Pyth
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantConnect.Algorithm.Framework", "Algorithm.Framework\QuantConnect.Algorithm.Framework.csproj", "{75981418-7246-4B91-B136-482728E02901}"
EndProject
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "QuantConnect.ApiPython", "ApiPython\QuantConnect.ApiPython.pyproj", "{1C47F4DB-2AFF-4FAE-9142-B33BE654A516}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -212,6 +214,10 @@ Global
{75981418-7246-4B91-B136-482728E02901}.Release|Any CPU.Build.0 = Release|Any CPU
{75981418-7246-4B91-B136-482728E02901}.Release|x64.ActiveCfg = Release|Any CPU
{75981418-7246-4B91-B136-482728E02901}.Release|x64.Build.0 = Release|Any CPU
{1C47F4DB-2AFF-4FAE-9142-B33BE654A516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C47F4DB-2AFF-4FAE-9142-B33BE654A516}.Debug|x64.ActiveCfg = Debug|Any CPU
{1C47F4DB-2AFF-4FAE-9142-B33BE654A516}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C47F4DB-2AFF-4FAE-9142-B33BE654A516}.Release|x64.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
1 change: 0 additions & 1 deletion Tests/QuantConnect.Tests.csproj
Expand Up @@ -468,7 +468,6 @@
<None Include="TestData\spy_with_keltner.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="API\ApiTests.py" />
</ItemGroup>
<ItemGroup>
<Folder Include="Adapters\" />
Expand Down

0 comments on commit 7c5c45d

Please sign in to comment.