Skip to content

Commit

Permalink
Merge pull request QuantConnect#3903 from AlexCatarino/bug-3896-pytho…
Browse files Browse the repository at this point in the history
…n-api

Refactors Python Version of the API
  • Loading branch information
jaredbroad authored Dec 13, 2019
2 parents 4c8e0eb + 5ce7abc commit 6313c64
Show file tree
Hide file tree
Showing 6 changed files with 655 additions and 417 deletions.
2 changes: 1 addition & 1 deletion PythonToolbox/QuantConnect.PythonToolbox.pyproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
<ItemGroup>
<Compile Include="CreateLeanReport.py" />
<Compile Include="quantconnect\api.py" />
<Compile Include="quantconnect\Result.py" />
<Compile Include="quantconnect\LeanOutputReader.py" />
<Compile Include="quantconnect\LeanReportCreator.py" />
<Compile Include="quantconnect\symbol.py" />
<Compile Include="quantconnect\__init__.py" />
<Compile Include="setup.py" />
<Compile Include="tests\test_api.py" />
<Compile Include="tests\test_symbol.py" />
Expand Down
199 changes: 199 additions & 0 deletions PythonToolbox/quantconnect/Result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# 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.

import pandas as pd
from math import isnan
from datetime import datetime

class Result:
'''Result represents the live or backtest result of a successfully executed algorithm'''

def __init__(self, json):
'''Creates a new instance of Result'''
tag = 'result'

# LiveResults special case:
self.LiveMode = 'LiveResults' in json
if self.LiveMode:
tag += 's'
json = json.pop('LiveResults', json)

result = json.pop(tag, json)

self.Statistics = Information(result.pop('Statistics', {}))
self.AlphaRuntimeStatistics = Information(result.pop('AlphaRuntimeStatistics', {}))
self.RuntimeStatistics = Information(result.pop('RuntimeStatistics', {}))
self.ClosedTrades = self.__create_closed_trades_table(result)
self.Charts = self.__create_charts_table(result)
self.ProfitLoss = self.__create_profit_loss_table(result)
self.Orders = self.__create_order_table(result)
self.RollingWindow = self.__create_rolling_window_table(result)
self.Information = Information(json)

def __create_order_table(self, json):
'''Creates a dataframe with the orders information'''
orders = json.pop('Orders', None)
if orders is None: return None

# In Live results, orders is a list, so convert to dict keyed by Id.
if isinstance(orders, list):
orders = {x['Id']: x for x in orders}

def __status_int_to_str(value):
if value is None: return None
values = [ 'New', 'Submitted', 'PartiallyFilled', 'Filled', 'Canceled', 'None', 'Invalid', 'CancelPending' ]
return str(values) if value >= len(values) else values[value]

def __security_type_int_to_str(value):
if value is None: return None
values = [ 'Base', 'Equity', 'Option', 'Commodity', 'Forex', 'Future', 'Cfd', 'Crypto' ]
return str(values) if value >= len(values) else values[value]

def __type_int_to_str(value):
if value is None: return None
values = [ 'Market', 'Limit', 'StopMarket', 'StopLimit', 'MarketOnOpen', 'MarketOnClose', 'OptionExercise' ]
return str(values) if value >= len(values) else values[value]

columns = [
'Id', 'Time', 'SecurityType', 'Symbol', 'PriceCurrency',
'Quantity', 'Direction', 'Price', 'Type', 'Status', 'Tag',
'LastFillTime', 'LastUpdateTime', 'CanceledTime' ]

if self.LiveMode:
columns += ['DeployId']

drop_columns = [
'BrokerId', 'ContingentId', 'CreatedTime', 'IsMarketable', 'Value',
'AbsoluteQuantity', 'OrderSubmissionData', 'Properties', 'TimeInForce']

df = pd.DataFrame([v for k, v in orders.items()], columns = columns + drop_columns)
df = df.set_index('Id').drop(drop_columns, axis=1)
df['Time'] = df['Time'].apply(self.__str_to_datetime)
df['CanceledTime'] = df['CanceledTime'].apply(self.__str_to_datetime)
df['LastFillTime'] = df['LastFillTime'].apply(self.__str_to_datetime)
df['LastUpdateTime'] = df['LastUpdateTime'].apply(self.__str_to_datetime)
df['Symbol'] = df['Symbol'].apply(lambda x: x['ID'])
df['Type'] = df['Type'].apply(__type_int_to_str)
df['Direction'] = df['Direction'].apply(self.__direction_int_to_str)
df['Status'] = df['Status'].apply(__status_int_to_str)

df['SecurityType'] = df['SecurityType'].apply(__security_type_int_to_str)
return df.dropna(how='all', axis=1)

def __create_profit_loss_table(self, json):
'''Creates a dataframe with the algorithm P&L'''
profitLoss = json.pop('ProfitLoss', None)
if profitLoss is None: return None

df = pd.DataFrame({'profit_loss' : profitLoss})
df.index.name = 'time'
df.index = df.index.map(self.__str_to_datetime)
return df

def __create_closed_trades_table(self, json):
'''Creates a dataframe with the closed trades information'''
total = json.get('TotalPerformance', None)
if total is None: return None
trades = total.get('ClosedTrades', None)
if trades is None: return None

df = pd.DataFrame(trades, columns = [
'Symbol', 'Quantity', 'Direction', 'EntryTime', 'EntryPrice',
'ExitPrice', 'ExitTime', 'Duration', 'EndTradeDrawdown',
'MAE', 'MFE', 'ProfitLoss', 'TotalFees'
])
df['Symbol'] = df['Symbol'].apply(lambda x: x['ID'])
df['Direction'] = df['Direction'].apply(self.__direction_int_to_str)
df['EntryTime'] = df['EntryTime'].apply(self.__str_to_datetime)
df['ExitTime'] = df['ExitTime'].apply(self.__str_to_datetime)
df['Duration'] = df['ExitTime'] - df['EntryTime']
return df.set_index('EntryTime')

def __create_charts_table(self, json):
'''Creates a dataframe with the charts information.
By converting the json into a dataframe, it makes data visualization easier'''
charts = json.pop('Charts', None)
if charts is None: return None

df_charts = dict()
for name, chart in charts.items():
# Skip Meta data
if name == 'Meta': continue
columns = list()
for column, series in chart['Series'].items():
df = pd.DataFrame(series['Values'])
df['x'] = pd.to_datetime(df['x'], unit='s')
df = df.rename(index=str, columns={"x": "time", "y": column})
columns.append(df.set_index('time'))
if len(columns) > 1:
df = pd.concat(columns, axis = 1, sort = True)
df = df.fillna(method = 'ffill')
df = df.fillna(method = 'bfill')
df_charts[name] = df
return df_charts

def __create_rolling_window_table(self, json):
'''Creates a dataframe with the rolling statistics information.
By converting the json into a dataframe, it makes data visualization easier'''
rollingWindow = json.pop('RollingWindow', None)
if rollingWindow is None: return None

series = dict()
if 'TotalPerformance' in json:
window = json['TotalPerformance']
if window is None: window = dict()
stats = window.get('PortfolioStatistics', dict())
stats.update(window.get('TradeStatistics', dict()))
series = {'TotalPerformance': pd.Series(stats)}

for row, window in rollingWindow.items():
stats = window.get('PortfolioStatistics', dict())
stats.update(window.get('TradeStatistics', dict()))
series.update({row: pd.Series(stats)})

return pd.DataFrame(series).transpose()

def __direction_int_to_str(self, value):
if value is None: return None
return [ 'Buy', 'Sell', 'Hold' ][value]

def __str_to_datetime(self, value):
if value is None: return None
if isinstance(value, float) and isnan(value): return None
fmt = '%Y-%m-%dT%H:%M:%SZ' if len(value) == 20 else '%Y-%m-%dT%H:%M:%S.%fZ'
return datetime.strptime(value, fmt)


class Information(dict):
def __init__(self, d):

d = d if d is not None else {}
super().__init__(d)

self.__repr = ''

for k, b in d.items():
a = k.replace(' ','').replace('-','')
if isinstance(b, (list, tuple)):
setattr(self, a, [Information(x) if isinstance(x, dict) else x for x in b])
elif isinstance(b, dict):
x = Information(b)
setattr(self, a, x)
s = '\n'.join([f' {l}' for l in repr(x).splitlines()])
self.__repr += f'{a}:\n{s}\n'
else:
setattr(self, a, b)
self.__repr += f'{a}: {b}\n'

def __repr__(self):
return self.__repr
69 changes: 0 additions & 69 deletions PythonToolbox/quantconnect/__init__.py

This file was deleted.

Loading

0 comments on commit 6313c64

Please sign in to comment.