Files
Tim Burke 552ceddc09 Stream downloaded data in more cases
Passing chunk_size=None to iter_content() works fine for chunk-encoded
responses, but still leaves us buffering the entire response when the
server just specifies a Content-Length.

Now, stream the response 256k at a time.

Why 256k? It's large enough that you won't be in a tight loop doing tiny
reads, but small enough that it shouldn't be any great memory concern.
If anyone finds that it's problematic, they can always monkey-patch
quantconnect.api.DOWNLOAD_CHUNK_SIZE.
2019-07-02 16:31:03 -07:00

479 lines
18 KiB
Python

# 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 datetime import datetime
from json import dumps
from requests import get as requests_get, Request
from time import mktime
from quantconnect import ApiConnection
DOWNLOAD_CHUNK_SIZE = 256 * 1024
class Api:
"""QuantConnect.com Interaction Via API.
Attributes:
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.api_connection = ApiConnection(userId, token)
def connected(self):
"""Check if Api is successfully connected with correct credentials"""
return self.api_connection.connected()
def read_project(self, projectId):
"""Get details about a single project.
Args:
projectId(int): Id of the project.
Returns:
ProjectResponse that contains information regarding the project.
"""
request = Request('GET', "projects/read", params = { "projectId": projectId })
return self.api_connection.try_request(request)
def list_projects(self):
"""List details of all projects
Returns:
ProjectResponse that contains information regarding the project.
"""
request = Request('GET', "projects/read")
return self.api_connection.try_request(request)
def create_project(self, name, language):
"""Create a project with the specified name and language via QuantConnect.com API
Args:
name(str): Project name
language(str): Programming language to use (Language must be C#, F# or Py).
Returns:
Project object from the API.
"""
request = Request('POST', "projects/create",
data=dumps({ "name": name, "language": language }))
return self.api_connection.try_request(request)
def add_project_file(self, projectId, name, content):
"""Add a file to a project.
Args:
projectId(int): The project to which the file should be added.
name(str): The name of the new file.
content(str): The content of the new file.
Returns:
ProjectResponse that contains information regarding the project.
"""
request = Request('POST', "files/create",
params =
{
"projectId" : projectId,
"name" : name,
"content" : content
})
return self.api_connection.try_request(request)
def update_project_filename(self, projectId, oldFileName, newFileName):
"""Update the name of a file
Args:
projectId(int): Project id to which the file belongs
oldFileName(str): The current name of the file
newFileName(str): The new name for the file
Returns:
Response indicating success
"""
request = Request('POST', "files/update",
params =
{
"projectId": projectId,
"name": oldFileName,
"newName": newFileName
})
return self.api_connection.try_request(request)
def update_project_file_content(self, projectId, fileName, newFileContents):
"""Update the contents of a file
Args:
projectId(int): Project id to which the file belongs
fileName(str): The name of the file that should be updated
newFileContents(str): The new contents of the file
Returns:
Response indicating success
"""
request = Request('POST', "files/update",
params =
{
"projectId": projectId,
"name": fileName,
"content": newFileContents
})
return self.api_connection.try_request(request)
def read_project_files(self, projectId):
"""Read all files in a project
Args:
projectId(int): Project id to which the file belongs
Returns:
ProjectFilesResponse that includes the information about all files in the project
"""
request = Request('GET', "files/read", params = { "projectId" : projectId })
return self.api_connection.try_request(request)
def read_project_file(self, projectId, fileName):
"""Read a file in a project
Args:
projectId(int): Project id to which the file belongs
fileName(str): The name of the file that should be updated
Returns:
ProjectFilesResponse that includes the file information
"""
request = Request('GET', "files/read",
params =
{
"projectId": projectId,
"name": fileName
})
return self.api_connection.try_request(request)
def delete_project_file(self, projectId, name):
"""Delete a file in a project
Args:
projectId(int): Project id to which the file belongs
name(str): The name of the file that should be deleted
Returns:
ProjectFilesResponse that includes the information about all files in the project
"""
request = Request('POST', "files/delete",
params =
{
"projectId": projectId,
"name": name
})
return self.api_connection.try_request(request)
def delete_project(self, projectId):
"""Delete a project
Args:
projectId(int): Project id we own and wish to delete
Returns:
Response indicating success
"""
request = Request('POST', "projects/delete",
data=dumps({ "projectId": projectId }))
return self.api_connection.try_request(request)
def create_compile(self, projectId):
"""Create a new compile job request for this project id.
Args:
projectId(int): Project id we wish to compile.
Returns:
Compile object result
"""
request = Request('POST', "compile/create",
data=dumps({ "projectId": projectId }))
return self.api_connection.try_request(request)
def read_compile(self, projectId, compileId):
"""Read a compile packet job result.
Args:
projectId(int): Project id we sent for compile
compileId(str): Compile id return from the creation request
Returns:
Response result
"""
request = Request('GET', "compile/read",
params =
{
"projectId": projectId,
"compileId": compileId
})
return self.api_connection.try_request(request)
def create_backtest(self, projectId, compileId, backtestName):
"""Create a new backtest request and get the id.
Args:
projectId(int): Id for the project to backtest
compileId(str): Compile id return from the creation request
backtestName(str): Name for the new backtest
Returns:
Backtest
"""
request = Request('POST', "backtests/create",
params =
{
"projectId": projectId,
"compileId": compileId,
"backtestName": backtestName
})
return self.api_connection.try_request(request)
def read_backtest(self, projectId, backtestId):
"""Read out a backtest in the project id specified.
Args:
projectId(int): Project id to read.
backtestId(str): Specific backtest id to read.
Returns:
Backtest
"""
request = Request('GET', "backtests/read",
params =
{
"backtestId": backtestId,
"projectId": projectId
})
return self.api_connection.try_request(request)
def update_backtest(self, projectId, backtestId, name, note):
"""Read out a backtest in the project id specified.
Args:
projectId(str): Project for the backtest we want to update
backtestId(str): Specific backtest id to read
name(str): Name we'd like to assign to the backtest
note(str): Note attached to the backtest
Returns:
Request Response
"""
request = Request('POST', "backtests/update",
data = dumps(
{
"projectId": projectId,
"backtestId": backtestId,
"name": name,
"note": note
}))
return self.api_connection.try_request(request)
def list_backtests(self, projectId):
"""List all the backtests for a project
Args:
projectId(int): Project id we'd like to get a list of backtest for
Returns:
Backtest list
"""
request = Request('GET', "backtests/read", params = { "projectId": projectId })
return self.api_connection.try_request(request)
def delete_backtest(self, projectId, backtestId):
"""Delete a backtest from the specified project and backtestId.
Args:
projectId(int): Project for the backtest we want to delete
backtestId(str): Backtest id we want to delete
Returns:
Response
"""
request = Request('GET', "backtests/delete",
params =
{
"backtestId": backtestId,
"projectId": projectId
})
return self.api_connection.try_request(request)
def create_live_algorithm(self, projectId, compileId, serverType, baseLiveAlgorithmSettings, versionId="-1"):
"""Create a live algorithm.
Args:
projectId(int): Id of the project on QuantConnect
compileId(str): Id of the compilation on QuantConnect
serverType(str): Type of server instance that will run the algorithm
baseLiveAlgorithmSettings(BaseLiveAlgorithmSettings): Brokerage specific
versionId(str): The version of the Lean used to run the algorithm.
-1 is master, however, sometimes this can create problems with live deployments.
If you experience problems using, try specifying the version of Lean you would like to use.
Returns:
Information regarding the new algorithm
"""
request = Request('POST', "live/create", headers = {"Accept": "application/json"},
data = dumps(
{
"versionId": versionId,
"projectId": projectId,
"compileId": compileId,
"serverType": serverType,
"brokerage": baseLiveAlgorithmSettings
}))
return self.api_connection.try_request(request)
def list_live_algorithms(self, status=None, startTime=None, endTime=None):
"""Get a list of live running algorithms for user.
Args:
status(str): Filter the statuses of the algorithms returned from the api
Only the following statuses are supported by the Api:
"Liquidated", "Running", "RuntimeError", "Stopped",
startTime(datetime): Earliest launched time of the algorithms returned by the Api
endTime(datetime): Latest launched time of the algorithms returned by the Api
Returns:
Live list
"""
if (
status != None and
status != "Running" and
status != "RuntimeError" and
status != "Stopped" and
status != "Liquidated"):
raise ValueError(
"The Api only supports Algorithm Statuses of Running, Stopped, RuntimeError and Liquidated")
request = Request('GET', "live/read",
params =
{
"status": str(status),
"start": 0 if startTime == None else mktime(startTime.timetuple()),
"end": mktime(datetime.utcnow().timetuple()) if endTime == None else mktime(endTime.timetuple())
})
return self.api_connection.try_request(request)
def read_live_algorithm(self, projectId, deployId):
"""Get a list of live running algorithms for user.
Args:
projectId(int): Project Id of the live running algorithm
deployId: Unique live algorithm deployment identifier
Returns:
Live list
"""
request = Request('GET', "live/read",
params =
{
"projectId": projectId,
"deployId": deployId
})
return self.api_connection.try_request(request)
def liquidate_live_algorithm(self, projectId):
"""Liquidate a live algorithm from the specified project and deployId.
Args:
projectId(int): Project for the live instance we want to stop
Returns:
Request response
"""
request = Request('POST', "live/update/liquidate", params = { "projectId": projectId })
return self.api_connection.try_request(request)
def stop_live_algorithm(self, projectId):
"""Stop a live algorithm from the specified project and deployId.
Args:
projectId(int): Project for the live instance we want to stop.
Returns:
Request response
"""
request = Request('POST', "live/update/stop", params = { "projectId": projectId })
return self.api_connection.try_request(request)
def read_live_logs(self, projectId, algorithmId, startTime=None, endTime=None):
"""Gets the logs of a specific live algorithm.
Args:
projectId(int): Project Id of the live running algorithm
algorithmId(str): Algorithm Id of the live running algorithm
startTime(datetime): No logs will be returned before this time
endTime(datetime): No logs will be returned after this time
Returns:
List of strings that represent the logs of the algorithm
"""
request = Request('GET', "live/read/log",
params =
{
"format": "json",
"projectId": projectId,
"algorithmId": algorithmId,
"start": 0 if startTime == None else mktime(startTime.timetuple()),
"end": mktime(datetime.utcnow().timetuple()) if endTime == None else mktime(endTime.timetuple())
})
return self.api_connection.try_request(request)
def read_data_link(self, symbol, securityType, market, resolution, date):
"""Gets the link to the downloadable data.
Args:
symbol(str): Symbol of security of which data will be requested
securityType(str): Type of underlying asset
market(str): e.g. CBOE, CBOT, FXCM, GDAX etc.
resolution(str): Resolution of data requested
date: Date of the data requested
Returns:
List of strings that represent the logs of the algorithm
"""
request = Request('GET', "data/read",
params =
{
"format": "link",
"ticker": symbol.lower(),
"type": securityType.lower(),
"market": market,
"resolution": resolution,
"date": date.strftime("%Y%m%d")
})
return self.api_connection.try_request(request)
def read_backtest_report(self, projectId, backtestId):
"""Read out the report of a backtest in the project id specified.
Args:
projectId(int): Project id to read.
backtestId(str): Specific backtest id to read.
Returns:
BacktestReport report
"""
request = Request('POST', "backtests/read/report",
params =
{
"backtestId": backtestId,
"projectId": projectId
})
return self.api_connection.try_request(request)
def download_data(self, symbol, securityType, market, resolution, date, fileName):
"""Method to download and save the data purchased through QuantConnect
Args:
symbol(str): Symbol of security of which data will be requested.
securityType(str): Type of underlying asset
market(str): e.g. CBOE, CBOT, FXCM, GDAX etc.
resolution(str): Resolution of data requested.
date(datetime): Date of the data requested.
fileName(str): file name of data download
Returns:
bool indicating whether the data was successfully downloaded or not
"""
# Get a link to the data
link = self.read_data_link(symbol, securityType, market, resolution, date)
# Make sure the link was successfully retrieved
if link['success']:
# download and save the data
request = requests_get(link['link'], stream=True)
with open(fileName + '.zip', "wb") as code:
for chunk in request.iter_content(DOWNLOAD_CHUNK_SIZE):
code.write(chunk)
return link['success']