pyalgotrading 2023.7.1__py3-none-any.whl → 2023.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyalgotrading/algobulls/api.py +16 -23
- pyalgotrading/algobulls/connection.py +295 -138
- pyalgotrading/constants.py +44 -0
- pyalgotrading/strategy/__init__.py +2 -0
- pyalgotrading/strategy/strategy_options_base_v2.py +90 -0
- pyalgotrading/utils/func.py +64 -3
- {pyalgotrading-2023.7.1.dist-info → pyalgotrading-2023.10.1.dist-info}/METADATA +4 -3
- {pyalgotrading-2023.7.1.dist-info → pyalgotrading-2023.10.1.dist-info}/RECORD +11 -10
- {pyalgotrading-2023.7.1.dist-info → pyalgotrading-2023.10.1.dist-info}/WHEEL +1 -1
- {pyalgotrading-2023.7.1.dist-info → pyalgotrading-2023.10.1.dist-info}/LICENSE +0 -0
- {pyalgotrading-2023.7.1.dist-info → pyalgotrading-2023.10.1.dist-info}/top_level.txt +0 -0
pyalgotrading/algobulls/api.py
CHANGED
|
@@ -26,6 +26,7 @@ class AlgoBullsAPI:
|
|
|
26
26
|
"""
|
|
27
27
|
self.connection = connection
|
|
28
28
|
self.headers = None
|
|
29
|
+
self.page_size = 1000
|
|
29
30
|
self.__key_backtesting = {} # strategy-cstc_id mapping
|
|
30
31
|
self.__key_papertrading = {} # strategy-cstc_id mapping
|
|
31
32
|
self.__key_realtrading = {} # strategy-cstc_id mapping
|
|
@@ -335,7 +336,7 @@ class AlgoBullsAPI:
|
|
|
335
336
|
TradingType.BACKTESTING: 'backDataDate'
|
|
336
337
|
}
|
|
337
338
|
execute_config = {
|
|
338
|
-
map_trading_type_to_date_key[trading_type]: [start_timestamp.astimezone(timezone.utc).isoformat(), end_timestamp.astimezone(timezone.utc).isoformat()],
|
|
339
|
+
map_trading_type_to_date_key[trading_type]: [start_timestamp.astimezone(timezone.utc).replace(tzinfo=None).isoformat(), end_timestamp.astimezone(timezone.utc).replace(tzinfo=None).isoformat()],
|
|
339
340
|
'isLiveDataTestMode': trading_type in [TradingType.PAPERTRADING, TradingType.REALTRADING],
|
|
340
341
|
'customizationsQuantity': lots,
|
|
341
342
|
'brokingDetails': broker_details,
|
|
@@ -408,14 +409,13 @@ class AlgoBullsAPI:
|
|
|
408
409
|
|
|
409
410
|
return response
|
|
410
411
|
|
|
411
|
-
def get_logs(self, strategy_code: str, trading_type: TradingType,
|
|
412
|
+
def get_logs(self, strategy_code: str, trading_type: TradingType, initial_next_token: str = None) -> dict:
|
|
412
413
|
"""
|
|
413
414
|
Fetch logs for a strategy
|
|
414
415
|
|
|
415
416
|
Args:
|
|
416
417
|
strategy_code: Strategy code
|
|
417
418
|
trading_type: Trading type
|
|
418
|
-
log_type: type of logs, 'partial' or 'full' requests
|
|
419
419
|
initial_next_token: Token of next logs for v4 logs
|
|
420
420
|
|
|
421
421
|
Returns:
|
|
@@ -424,23 +424,17 @@ class AlgoBullsAPI:
|
|
|
424
424
|
Info: ENDPOINT
|
|
425
425
|
`POST`: v2/user/strategy/logs
|
|
426
426
|
"""
|
|
427
|
-
key = self.__get_key(strategy_code=strategy_code, trading_type=trading_type)
|
|
428
|
-
params = None
|
|
429
|
-
|
|
430
|
-
if log_type == 'partial':
|
|
431
|
-
endpoint = 'v4/user/strategy/logs'
|
|
432
|
-
json_data = {'key': key, 'nextToken': initial_next_token, 'limit': 1000, 'direction': 'forward', 'reverse': False, 'type': 'userLogs'}
|
|
433
|
-
params = {'isPythonBuild': True, 'isLive': trading_type == TradingType.REALTRADING}
|
|
434
427
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
428
|
+
key = self.__get_key(strategy_code=strategy_code, trading_type=trading_type)
|
|
429
|
+
endpoint = 'v4/user/strategy/logs'
|
|
430
|
+
json_data = {'key': key, 'nextForwardToken': initial_next_token, 'limit': self.page_size, 'direction': 'forward', 'type': 'userLogs'}
|
|
431
|
+
params = {'isPythonBuild': True, 'isLive': trading_type == TradingType.REALTRADING}
|
|
438
432
|
|
|
439
433
|
response = self._send_request(method='post', endpoint=endpoint, json_data=json_data, params=params)
|
|
440
434
|
|
|
441
435
|
return response
|
|
442
436
|
|
|
443
|
-
def get_reports(self, strategy_code: str, trading_type: TradingType, report_type: TradingReportType,
|
|
437
|
+
def get_reports(self, strategy_code: str, trading_type: TradingType, report_type: TradingReportType, country: str, current_page: int) -> dict:
|
|
444
438
|
"""
|
|
445
439
|
Fetch report for a strategy
|
|
446
440
|
|
|
@@ -448,25 +442,24 @@ class AlgoBullsAPI:
|
|
|
448
442
|
strategy_code: Strategy code
|
|
449
443
|
trading_type: Value of TradingType Enum
|
|
450
444
|
report_type: Value of TradingReportType Enum
|
|
451
|
-
|
|
452
|
-
|
|
445
|
+
country: Country of the exchange
|
|
446
|
+
current_page: current page of data being retrieved (for order history)
|
|
453
447
|
Returns:
|
|
454
448
|
Report data
|
|
455
449
|
|
|
456
450
|
Info: ENDPOINT
|
|
457
|
-
`GET`
|
|
458
|
-
`GET`
|
|
459
|
-
`GET` v2/user/strategy/orderhistory Order History
|
|
451
|
+
`GET` v4/book/pl/data for P&L Table
|
|
452
|
+
`GET` v5/build/python/user/order/charts Order History
|
|
460
453
|
"""
|
|
461
454
|
|
|
462
455
|
key = self.__get_key(strategy_code=strategy_code, trading_type=trading_type)
|
|
463
456
|
if report_type is TradingReportType.PNL_TABLE:
|
|
464
457
|
_filter = json.dumps({"tradingType": trading_type.value})
|
|
465
|
-
endpoint = f'
|
|
466
|
-
params = {'pageSize': 0, 'isPythonBuild': "true", 'strategyId': strategy_code, 'isLive': trading_type is TradingType.REALTRADING, '
|
|
458
|
+
endpoint = f'v4/book/pl/data'
|
|
459
|
+
params = {'pageSize': 0, 'isPythonBuild': "true", 'strategyId': strategy_code, 'isLive': trading_type is TradingType.REALTRADING, 'country': country, 'filters': _filter}
|
|
467
460
|
elif report_type is TradingReportType.ORDER_HISTORY:
|
|
468
|
-
endpoint = '
|
|
469
|
-
params = {'
|
|
461
|
+
endpoint = 'v5/build/python/user/order/charts'
|
|
462
|
+
params = {'strategyId': strategy_code, 'country': country, 'currentPage': current_page, 'pageSize': self.page_size, 'isLive': trading_type is TradingType.REALTRADING}
|
|
470
463
|
else:
|
|
471
464
|
raise NotImplementedError
|
|
472
465
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Module for AlgoBulls connection
|
|
3
3
|
"""
|
|
4
4
|
import inspect
|
|
5
|
+
import os
|
|
5
6
|
import pprint
|
|
6
7
|
import re
|
|
7
8
|
import time
|
|
@@ -14,10 +15,10 @@ from tabulate import tabulate
|
|
|
14
15
|
from tqdm.auto import tqdm
|
|
15
16
|
|
|
16
17
|
from .api import AlgoBullsAPI
|
|
17
|
-
from .exceptions import AlgoBullsAPIBadRequestException, AlgoBullsAPIGatewayTimeoutErrorException
|
|
18
|
-
from ..constants import StrategyMode, TradingType, TradingReportType, CandleInterval, AlgoBullsEngineVersion, EXCHANGE_LOCALE_MAP, Locale,
|
|
18
|
+
from .exceptions import AlgoBullsAPIBadRequestException, AlgoBullsAPIGatewayTimeoutErrorException, AlgoBullsAPIUnauthorizedErrorException
|
|
19
|
+
from ..constants import StrategyMode, TradingType, TradingReportType, CandleInterval, AlgoBullsEngineVersion, Country, ExecutionStatus, EXCHANGE_LOCALE_MAP, Locale, CandleIntervalSecondsMap
|
|
19
20
|
from ..strategy.strategy_base import StrategyBase
|
|
20
|
-
from ..utils.func import get_valid_enum_names, get_datetime_with_tz
|
|
21
|
+
from ..utils.func import get_valid_enum_names, get_datetime_with_tz, calculate_brokerage, calculate_slippage
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class AlgoBullsConnection:
|
|
@@ -36,7 +37,7 @@ class AlgoBullsConnection:
|
|
|
36
37
|
'end_timestamp_map': {}
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
self.
|
|
40
|
+
self.strategy_country_map = {
|
|
40
41
|
TradingType.BACKTESTING: {},
|
|
41
42
|
TradingType.PAPERTRADING: {},
|
|
42
43
|
TradingType.REALTRADING: {},
|
|
@@ -68,19 +69,27 @@ class AlgoBullsConnection:
|
|
|
68
69
|
url = 'https://app.algobulls.com/settings?section=developerOptions'
|
|
69
70
|
print(f'Please login to this URL to get your unique token: {url}')
|
|
70
71
|
|
|
71
|
-
def set_access_token(self, access_token):
|
|
72
|
+
def set_access_token(self, access_token, validate_token=True):
|
|
72
73
|
"""
|
|
73
74
|
Set the access token
|
|
74
75
|
|
|
75
76
|
Args:
|
|
76
77
|
access_token: access token
|
|
77
|
-
|
|
78
|
+
validate_token: whether you want to validate the access-token
|
|
78
79
|
Returns:
|
|
79
80
|
None
|
|
80
81
|
"""
|
|
81
82
|
assert isinstance(access_token, str), f'Argument "access_token" should be a string'
|
|
82
83
|
self.api.set_access_token(access_token)
|
|
83
84
|
|
|
85
|
+
if validate_token:
|
|
86
|
+
try:
|
|
87
|
+
_ = self.api.get_all_strategies()
|
|
88
|
+
print("Access token is valid.")
|
|
89
|
+
except AlgoBullsAPIUnauthorizedErrorException:
|
|
90
|
+
print(f"Access token is invalid. ", end='')
|
|
91
|
+
self.get_token_url()
|
|
92
|
+
|
|
84
93
|
def create_strategy(self, strategy, overwrite=False, strategy_code=None, abc_version=None):
|
|
85
94
|
"""
|
|
86
95
|
Method to upload new strategy.
|
|
@@ -270,89 +279,121 @@ class AlgoBullsConnection:
|
|
|
270
279
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
271
280
|
assert isinstance(trading_type, TradingType), f'Argument "trading_type" should be an enum of type {TradingType.__name__}'
|
|
272
281
|
|
|
273
|
-
|
|
282
|
+
self.api.stop_strategy_algotrading(strategy_code=strategy_code, trading_type=trading_type)
|
|
274
283
|
|
|
275
|
-
def get_logs(self, strategy_code, trading_type,
|
|
284
|
+
def get_logs(self, strategy_code, trading_type, display_progress_bar=True, print_live_logs=False):
|
|
276
285
|
"""
|
|
277
286
|
Fetch logs for a strategy
|
|
278
287
|
|
|
279
288
|
Args:
|
|
280
289
|
strategy_code: strategy code
|
|
281
290
|
trading_type: trading type
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
291
|
+
display_progress_bar: to track the execution progress bar as your strategy is executed
|
|
292
|
+
print_live_logs: to print the logs as they are fetched
|
|
285
293
|
Returns:
|
|
286
294
|
Execution logs
|
|
287
295
|
"""
|
|
288
296
|
|
|
289
297
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
290
298
|
assert isinstance(trading_type, TradingType), f'Argument "trading_type" should be an enum of type {TradingType.__name__}'
|
|
291
|
-
assert isinstance(auto_update, bool), f'Argument "show_progress_bar" should be a boolean'
|
|
292
299
|
|
|
293
300
|
# TODO: to extract timestamp from a different source which will be independent of whether save parameters are present in the object
|
|
294
301
|
start_timestamp_map = self.saved_parameters.get('start_timestamp_map')
|
|
295
302
|
end_timestamp_map = self.saved_parameters.get('end_timestamp_map')
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# logging with progress bar
|
|
299
|
-
if auto_update and start_timestamp_map.get(trading_type) and end_timestamp_map.get(trading_type):
|
|
300
|
-
tqdm_progress_bar = None
|
|
301
|
-
initial_next_token = None
|
|
302
|
-
error_counter = 0
|
|
303
|
-
status = None
|
|
303
|
+
sleep_time = 1
|
|
304
|
+
total_seconds = 1
|
|
304
305
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
# calculate the sleep time for RT and PT
|
|
307
|
+
if trading_type is not TradingType.BACKTESTING:
|
|
308
|
+
try:
|
|
309
|
+
sleep_time = CandleIntervalSecondsMap[self.saved_parameters.get('candle_interval').value]
|
|
310
|
+
print(f"Your candle interval is {self.saved_parameters.get('candle_interval').value}, therefore logs will be fetched every {sleep_time} seconds.")
|
|
311
|
+
except AttributeError:
|
|
312
|
+
print("WARNING: Could not fetch the candle interval from saved parameters. Logs will be fetched every 60 seconds.")
|
|
313
|
+
sleep_time = 60
|
|
314
|
+
|
|
315
|
+
# initialize the parameters required for displaying progress bar
|
|
316
|
+
if display_progress_bar:
|
|
317
|
+
if start_timestamp_map.get(trading_type) and end_timestamp_map.get(trading_type):
|
|
318
|
+
start_timestamp = start_timestamp_map.get(trading_type).replace(tzinfo=None)
|
|
319
|
+
end_timestamp = end_timestamp_map.get(trading_type).replace(tzinfo=None)
|
|
320
|
+
total_seconds = (end_timestamp - start_timestamp).total_seconds()
|
|
321
|
+
else:
|
|
322
|
+
display_progress_bar = False
|
|
308
323
|
|
|
309
|
-
|
|
310
|
-
|
|
324
|
+
# initialize all the variables
|
|
325
|
+
all_logs = ''
|
|
326
|
+
tqdm_progress_bar = None
|
|
327
|
+
initial_next_token = None
|
|
328
|
+
error_counter = 0
|
|
329
|
+
status = None
|
|
330
|
+
count_starting_status = 0
|
|
331
|
+
response = {}
|
|
332
|
+
logs = []
|
|
333
|
+
|
|
334
|
+
while True:
|
|
335
|
+
# if logs are in starting phase, we wait until it changes
|
|
336
|
+
if status is None or status == ExecutionStatus.STARTING.value:
|
|
337
|
+
status = self.get_job_status(strategy_code, trading_type)["message"]
|
|
338
|
+
time.sleep(5)
|
|
339
|
+
|
|
340
|
+
# log the counting for "STARTING" phase of execution
|
|
341
|
+
if status == ExecutionStatus.STARTING.value:
|
|
342
|
+
count_starting_status += 1
|
|
343
|
+
print('\r', end=f'Looking for a dedicated virtual server to execute your strategy... ({count_starting_status})')
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
# if logs get in started phase, we initialize the tqdm object for progress tracking
|
|
347
|
+
if display_progress_bar:
|
|
348
|
+
if tqdm_progress_bar is None and status == ExecutionStatus.STARTED.value:
|
|
349
|
+
tqdm_progress_bar = tqdm(desc='Execution Progress', total=total_seconds, position=0, leave=True, bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]')
|
|
311
350
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
351
|
+
# try the logs API 5 times or until it succeeds
|
|
352
|
+
for i in range(5):
|
|
353
|
+
try:
|
|
354
|
+
response = self.api.get_logs(strategy_code=strategy_code, trading_type=trading_type, initial_next_token=initial_next_token)
|
|
355
|
+
logs = response.get('data')
|
|
356
|
+
if logs:
|
|
357
|
+
break
|
|
358
|
+
except AlgoBullsAPIGatewayTimeoutErrorException:
|
|
359
|
+
tqdm.write(f"\n{'----' * 10}\nFaced an error while fetching logs.\n{'----' * 10}\n")
|
|
315
360
|
time.sleep(5)
|
|
316
|
-
if status == ExecutionStatus.STARTING.value:
|
|
317
|
-
count_starting_status += 1
|
|
318
|
-
print('\r', end=f'Looking for a dedicated virtual server to execute your strategy... ({count_starting_status})')
|
|
319
|
-
continue
|
|
320
361
|
|
|
321
|
-
|
|
322
|
-
if tqdm_progress_bar is None and status == ExecutionStatus.STARTED.value:
|
|
323
|
-
tqdm_progress_bar = tqdm(desc='Execution Progress', total=total_seconds, position=0, leave=True, bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]')
|
|
362
|
+
initial_next_token = response.get('nextForwardToken')
|
|
324
363
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
all_logs += '\n'.join(logs) + '\n'
|
|
364
|
+
# if logs are empty we validate the status of execution
|
|
365
|
+
if not logs:
|
|
366
|
+
status = self.get_job_status(strategy_code, trading_type)["message"]
|
|
329
367
|
|
|
330
|
-
# if
|
|
331
|
-
if
|
|
332
|
-
status = self.get_job_status(strategy_code, trading_type)["message"]
|
|
368
|
+
# if status is stopped we break the while loop
|
|
369
|
+
if status in [ExecutionStatus.STOPPED.value, ExecutionStatus.STOPPING.value]:
|
|
333
370
|
|
|
334
|
-
#
|
|
335
|
-
if
|
|
336
|
-
# tqdm.write(f'INFO: Got status as {status}, strategy execution completed.') # for debug
|
|
371
|
+
# tqdm.write(f"INFO: Got status as {status}, strategy execution completed.") # for debug
|
|
372
|
+
if display_progress_bar:
|
|
337
373
|
if tqdm_progress_bar is not None:
|
|
338
374
|
tqdm_progress_bar.close()
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
# continue if logs are not fetched
|
|
342
|
-
else:
|
|
343
|
-
# tqdm.write(f'WARNING: got no data, current status is {status}...') # for debug
|
|
344
|
-
time.sleep(5)
|
|
345
|
-
continue
|
|
375
|
+
break
|
|
346
376
|
|
|
377
|
+
# continue if logs are not fetched
|
|
347
378
|
else:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
379
|
+
time.sleep(5)
|
|
380
|
+
|
|
381
|
+
# tqdm.write(f"WARNING: got no data, current status is {status}...") # for debug
|
|
382
|
+
continue
|
|
351
383
|
|
|
352
|
-
|
|
384
|
+
else:
|
|
385
|
+
# incoming logs are in list, merge them to string
|
|
386
|
+
if type(logs) is list and initial_next_token:
|
|
387
|
+
all_logs += ''.join(logs)
|
|
388
|
+
|
|
389
|
+
# print the logs below progressbar
|
|
390
|
+
if print_live_logs:
|
|
391
|
+
tqdm.write(''.join(logs))
|
|
392
|
+
|
|
393
|
+
# iterate in reverse order
|
|
394
|
+
if display_progress_bar:
|
|
353
395
|
for log in logs[::-1]:
|
|
354
396
|
try:
|
|
355
|
-
|
|
356
397
|
# extract log terms inside square brackets
|
|
357
398
|
_ = re.findall(r'\[(.*?)\]', log)
|
|
358
399
|
|
|
@@ -367,26 +408,28 @@ class AlgoBullsConnection:
|
|
|
367
408
|
tqdm.write(f'WARNING: faced an error while updating logs process. Error: {ex}')
|
|
368
409
|
error_counter += 1
|
|
369
410
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
411
|
+
# avoid infinite loop in case of error
|
|
412
|
+
if error_counter > 5:
|
|
413
|
+
break
|
|
414
|
+
|
|
415
|
+
if len(logs) >= self.api.page_size:
|
|
416
|
+
# tqdm.write(f"\n{'-----' * 5}\nWaiting {sleep_time} seconds for fetching next logs ...\n{'-----' * 5}\n") # for debug
|
|
417
|
+
time.sleep(sleep_time)
|
|
418
|
+
else:
|
|
419
|
+
time.sleep(1)
|
|
373
420
|
|
|
374
|
-
initial_next_token = response.get('initialNextToken')
|
|
375
|
-
else:
|
|
376
|
-
all_logs = self.api.get_logs(strategy_code=strategy_code, trading_type=trading_type, log_type='full').get('data')
|
|
377
421
|
return all_logs
|
|
378
422
|
|
|
379
|
-
def
|
|
423
|
+
def get_report_order_history(self, strategy_code, trading_type, render_as_dataframe=False, show_all_rows=True, country=None):
|
|
380
424
|
"""
|
|
381
425
|
Fetch report for a strategy
|
|
382
426
|
|
|
383
427
|
Args:
|
|
384
428
|
strategy_code: Strategy code
|
|
385
429
|
trading_type: Value of TradingType Enum
|
|
386
|
-
report_type: Value of TradingReportType Enum
|
|
387
430
|
render_as_dataframe: True or False
|
|
388
431
|
show_all_rows: True or False
|
|
389
|
-
|
|
432
|
+
country: country of the Exchange
|
|
390
433
|
|
|
391
434
|
Returns:
|
|
392
435
|
report details
|
|
@@ -394,30 +437,86 @@ class AlgoBullsConnection:
|
|
|
394
437
|
|
|
395
438
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
396
439
|
assert isinstance(trading_type, TradingType), f'Argument "trading_type" should be an enum of type {TradingType.__name__}'
|
|
397
|
-
assert isinstance(report_type, TradingReportType), f'Argument "report_type" should be an enum of type {TradingReportType.__name__}'
|
|
398
440
|
assert isinstance(render_as_dataframe, bool), f'Argument "render_as_dataframe" should be a bool'
|
|
399
441
|
assert isinstance(show_all_rows, bool), f'Argument "show_all_rows" should be a bool'
|
|
400
442
|
# assert (broker is None or isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be None or an enum of type {AlgoBullsSupportedBrokers.__name__}'
|
|
401
|
-
|
|
402
|
-
|
|
443
|
+
|
|
444
|
+
main_data = []
|
|
445
|
+
total_data = 0
|
|
446
|
+
_total = 0
|
|
447
|
+
i = 1
|
|
448
|
+
|
|
449
|
+
if country is None:
|
|
450
|
+
country = self.strategy_country_map[trading_type].get(strategy_code, Country.DEFAULT.value)
|
|
451
|
+
|
|
452
|
+
while True:
|
|
453
|
+
for _ in range(5):
|
|
454
|
+
try:
|
|
455
|
+
response = self.api.get_reports(strategy_code=strategy_code, trading_type=trading_type, report_type=TradingReportType.ORDER_HISTORY, country=country, current_page=i)
|
|
456
|
+
_total = response.get("totalTrades")
|
|
457
|
+
_data = response.get("data")
|
|
458
|
+
|
|
459
|
+
# if data is retrieved as a list then update the main data
|
|
460
|
+
if _data and isinstance(_data, list):
|
|
461
|
+
main_data.extend(_data)
|
|
462
|
+
total_data += self.api.page_size
|
|
463
|
+
i += 1
|
|
464
|
+
break
|
|
465
|
+
|
|
466
|
+
except AlgoBullsAPIGatewayTimeoutErrorException:
|
|
467
|
+
time.sleep(5)
|
|
468
|
+
|
|
469
|
+
# break if total requested data is more than total data
|
|
470
|
+
if total_data > _total:
|
|
471
|
+
break
|
|
472
|
+
|
|
473
|
+
if main_data:
|
|
474
|
+
|
|
475
|
+
# for rendering as dataframe
|
|
403
476
|
if render_as_dataframe:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
477
|
+
pandas_dataframe_all_rows()
|
|
478
|
+
|
|
479
|
+
# explode the "customer_tradebook_states" column to get separate rows for every state
|
|
480
|
+
_response = pd.DataFrame(main_data)
|
|
481
|
+
df = _response.explode('customer_tradebook_states').reset_index(drop=True)
|
|
482
|
+
df = df.join(pd.json_normalize(df.pop('customer_tradebook_states')))
|
|
483
|
+
_response = df.set_index("orderId").sort_values("timestamp_created")[["timestamp_created", "transaction_type", "state", "instrument", "quantity", "currency", "price"]]
|
|
484
|
+
|
|
485
|
+
# for rendering as string or json
|
|
407
486
|
else:
|
|
408
|
-
_response =
|
|
487
|
+
_response = main_data
|
|
488
|
+
main_order_string = ""
|
|
489
|
+
|
|
490
|
+
for i in range(len(main_data)):
|
|
491
|
+
# tables for displaying order details and order stats
|
|
492
|
+
order_detail = [
|
|
493
|
+
["Order ID", main_data[i]["orderId"]],
|
|
494
|
+
["Transaction Type", main_data[i]["transaction_type"]],
|
|
495
|
+
["Instrument", main_data[i]["instrument"]],
|
|
496
|
+
["Quantity", main_data[i]["quantity"]],
|
|
497
|
+
["Price", str(main_data[i]["currency"]) + str(main_data[i]["price"])]
|
|
498
|
+
]
|
|
499
|
+
main_order_string += tabulate(order_detail, tablefmt="psql") + "\n"
|
|
500
|
+
main_order_string += tabulate(main_data[i]["customer_tradebook_states"], headers="keys", tablefmt="psql") + "\n"
|
|
501
|
+
main_order_string += '\n' + '======' * 15 + '\n'
|
|
502
|
+
|
|
503
|
+
_response = main_order_string
|
|
409
504
|
return _response
|
|
410
505
|
else:
|
|
411
|
-
print(
|
|
506
|
+
print("Report not available yet. Please retry in sometime")
|
|
412
507
|
|
|
413
|
-
def
|
|
508
|
+
def get_report_pnl_table(self, strategy_code, trading_type, country, brokerage_percentage=None, brokerage_flat_price=None, slippage_percent=None, show_all_rows=True):
|
|
414
509
|
"""
|
|
415
510
|
Fetch BT/PT/RT Profit & Loss details
|
|
416
511
|
|
|
417
512
|
Args:
|
|
418
513
|
strategy_code: strategy code
|
|
419
514
|
trading_type: type of trades : Backtesting, Papertrading, Realtrading
|
|
420
|
-
|
|
515
|
+
country: country of the exchange
|
|
516
|
+
brokerage_percentage: Percentage of broker commission per trade
|
|
517
|
+
brokerage_flat_price: Broker fee per trade
|
|
518
|
+
slippage_percent: percentage of slippage per order
|
|
519
|
+
show_all_rows: show all rows of the dataframe returned
|
|
421
520
|
|
|
422
521
|
Returns:
|
|
423
522
|
Report details
|
|
@@ -425,11 +524,16 @@ class AlgoBullsConnection:
|
|
|
425
524
|
|
|
426
525
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
427
526
|
|
|
527
|
+
# Set the country code
|
|
528
|
+
if country is None:
|
|
529
|
+
country = self.strategy_country_map[trading_type].get(strategy_code, Country.DEFAULT.value)
|
|
530
|
+
|
|
428
531
|
# Fetch the data
|
|
429
|
-
|
|
430
|
-
|
|
532
|
+
response = self.api.get_reports(strategy_code=strategy_code, trading_type=trading_type, report_type=TradingReportType.PNL_TABLE, country=country, current_page=1)
|
|
533
|
+
data = response.get("data")
|
|
431
534
|
|
|
432
|
-
|
|
535
|
+
if show_all_rows:
|
|
536
|
+
pandas_dataframe_all_rows()
|
|
433
537
|
|
|
434
538
|
# Post-processing: Cleanup & converting data to dataframe
|
|
435
539
|
column_rename_map = OrderedDict([
|
|
@@ -447,6 +551,7 @@ class AlgoBullsConnection:
|
|
|
447
551
|
('exit.price', 'exit_price'),
|
|
448
552
|
('pnlAbsolute.value', 'pnl_absolute')
|
|
449
553
|
])
|
|
554
|
+
|
|
450
555
|
if data:
|
|
451
556
|
# Generate df from json data & perform cleanups
|
|
452
557
|
_df = pd.json_normalize(data[::-1])[list(column_rename_map.keys())].rename(columns=column_rename_map)
|
|
@@ -455,13 +560,19 @@ class AlgoBullsConnection:
|
|
|
455
560
|
_df['exit_transaction_type'] = _df['exit_transaction_type'].apply(lambda _: 'BUY' if _ else 'SELL')
|
|
456
561
|
_df["pnl_cumulative_absolute"] = _df["pnl_absolute"].cumsum(axis=0, skipna=True)
|
|
457
562
|
|
|
563
|
+
# generate slippage data
|
|
564
|
+
if slippage_percent:
|
|
565
|
+
_df = calculate_slippage(pnl_df=_df, slippage_percent=slippage_percent)
|
|
566
|
+
|
|
567
|
+
_df = calculate_brokerage(pnl_df=_df, brokerage_percentage=brokerage_percentage, brokerage_flat_price=brokerage_flat_price)
|
|
458
568
|
else:
|
|
459
569
|
# No data available, send back an empty dataframe
|
|
460
570
|
_df = pd.DataFrame(columns=list(column_rename_map.values()))
|
|
571
|
+
_df['net_pnl'] = None
|
|
461
572
|
|
|
462
573
|
return _df
|
|
463
574
|
|
|
464
|
-
def get_report_statistics(self, strategy_code, initial_funds, report, html_dump, pnl_df):
|
|
575
|
+
def get_report_statistics(self, strategy_code=None, initial_funds=None, report="full", html_dump=True, pnl_df=None, file_path="None", date_time_format="%Y-%m-%d %H:%M:%S%z"):
|
|
465
576
|
"""
|
|
466
577
|
Fetch BT/PT/RT report statistics
|
|
467
578
|
|
|
@@ -469,19 +580,45 @@ class AlgoBullsConnection:
|
|
|
469
580
|
strategy_code: strategy code
|
|
470
581
|
report: format and content of the report
|
|
471
582
|
html_dump: save it as a html file
|
|
472
|
-
pnl_df: dataframe containing pnl reports
|
|
583
|
+
pnl_df: dataframe containing pnl reports; this parameter will be ignored if file_path is provided
|
|
473
584
|
initial_funds: initial funds to before starting the job
|
|
585
|
+
file_path: file path of the csv or xlsx containing pnl data for statistics; if provided, pnl_df would be ignored
|
|
586
|
+
date_time_format: datetime format of the column inside the "entry_timestamp" column in the csv or xlxs file
|
|
474
587
|
Returns:
|
|
475
588
|
Report details
|
|
476
589
|
"""
|
|
477
590
|
|
|
591
|
+
# read and validate the csv given in path file
|
|
592
|
+
if os.path.isfile(file_path):
|
|
593
|
+
# only accept csv or xlxs files
|
|
594
|
+
_, _ext = os.path.splitext(file_path)
|
|
595
|
+
if _ext == '.csv':
|
|
596
|
+
pnl_df = pd.read_csv(file_path)
|
|
597
|
+
elif _ext == '.xlxs':
|
|
598
|
+
pnl_df = pd.read_excel(file_path)
|
|
599
|
+
else:
|
|
600
|
+
raise Exception(f'ERROR: File with extension {_ext} is not supported.\n Please provide path to files with extension as ".csv" or ".xlxs"')
|
|
601
|
+
|
|
602
|
+
# handle the exceptions gracefully, check the validity of the input file
|
|
603
|
+
if "entry_timestamp" not in pnl_df.columns or "net_pnl" not in pnl_df.columns:
|
|
604
|
+
raise Exception(f"ERROR: Given {_ext} file does not have the required columns 'entry_timestamp' and 'net_pnl'.")
|
|
605
|
+
try:
|
|
606
|
+
dt.strptime(pnl_df.iloc[0]["entry_timestamp"], date_time_format)
|
|
607
|
+
except ValueError:
|
|
608
|
+
raise ValueError(f"ERROR: Datetime strings inside 'entry_timestamp' column should be of the format {date_time_format}.")
|
|
609
|
+
|
|
610
|
+
# cleanup dataframe generated from read files
|
|
611
|
+
pnl_df[['entry_timestamp']] = pnl_df[['entry_timestamp']].apply(pd.to_datetime, format=date_time_format, errors="coerce")
|
|
612
|
+
|
|
478
613
|
order_report = None
|
|
614
|
+
if initial_funds is None:
|
|
615
|
+
initial_funds = self.saved_parameters.get("initial_funds_virtual") or 1e9
|
|
479
616
|
|
|
480
617
|
# get pnl data and cleanup as per quantstats format
|
|
481
|
-
_returns_df = pnl_df[['entry_timestamp', '
|
|
618
|
+
_returns_df = pnl_df[['entry_timestamp', 'net_pnl']]
|
|
482
619
|
_returns_df['entry_timestamp'] = _returns_df['entry_timestamp'].dt.tz_localize(None) # Note: Quantstats has a bug. It doesn't accept the df index, which is set below, with timezone. Hence, we have to drop the timezone info
|
|
483
620
|
_returns_df = _returns_df.set_index('entry_timestamp')
|
|
484
|
-
_returns_df["total_funds"] = _returns_df.
|
|
621
|
+
_returns_df["total_funds"] = _returns_df.net_pnl.cumsum() + initial_funds
|
|
485
622
|
_returns_df = _returns_df.dropna()
|
|
486
623
|
|
|
487
624
|
# Note: Quantstats has a potential bug. It cannot work with multiple entries having the same timestamp. For now, we are dropping multiple entries with the same entry_timestamp (else the quantstats code below would throw an error)
|
|
@@ -492,15 +629,22 @@ class AlgoBullsConnection:
|
|
|
492
629
|
total_funds_series = _returns_df.total_funds
|
|
493
630
|
|
|
494
631
|
# select report type
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
632
|
+
try:
|
|
633
|
+
if report == "metrics":
|
|
634
|
+
order_report = qs.reports.metrics(total_funds_series)
|
|
635
|
+
elif report == "full":
|
|
636
|
+
order_report = qs.reports.full(total_funds_series)
|
|
637
|
+
except ZeroDivisionError:
|
|
638
|
+
raise Exception("ERROR: PnL data generated is too less to perform statistical analysis")
|
|
499
639
|
|
|
500
640
|
# save as html file
|
|
501
641
|
if html_dump:
|
|
502
|
-
|
|
503
|
-
|
|
642
|
+
# if there is an error in calling the API, give a default name to html file.
|
|
643
|
+
try:
|
|
644
|
+
all_strategies = self.get_all_strategies()
|
|
645
|
+
strategy_name = all_strategies.loc[all_strategies['strategyCode'] == strategy_code]['strategyName'].iloc[0]
|
|
646
|
+
except Exception:
|
|
647
|
+
strategy_name = 'strategy_results'
|
|
504
648
|
qs.reports.html(total_funds_series, title=strategy_name, output='', download_filename=f'report_{strategy_name}_{time.time():.0f}.html')
|
|
505
649
|
|
|
506
650
|
return order_report
|
|
@@ -563,7 +707,6 @@ class AlgoBullsConnection:
|
|
|
563
707
|
|
|
564
708
|
Returns:
|
|
565
709
|
job submission status
|
|
566
|
-
location of the instruments
|
|
567
710
|
"""
|
|
568
711
|
|
|
569
712
|
# check if values received by new parameter names, else extract from old parameter names, else extract from saved parameters
|
|
@@ -603,6 +746,8 @@ class AlgoBullsConnection:
|
|
|
603
746
|
candle_interval = CandleInterval[_]
|
|
604
747
|
if isinstance(instruments, str):
|
|
605
748
|
instruments = [instruments]
|
|
749
|
+
if strategy_parameters == {} or strategy_parameters is None:
|
|
750
|
+
strategy_parameters = dict()
|
|
606
751
|
|
|
607
752
|
# Sanity checks - Validate config parameters
|
|
608
753
|
assert isinstance(strategy_code, str), f'Argument "strategy" should be a valid string'
|
|
@@ -697,7 +842,7 @@ class AlgoBullsConnection:
|
|
|
697
842
|
response = self.api.start_strategy_algotrading(strategy_code=strategy_code, start_timestamp=start_timestamp, end_timestamp=end_timestamp, trading_type=trading_type,
|
|
698
843
|
lots=lots, initial_funds_virtual=initial_funds_virtual, broker_details=broking_details, location=location)
|
|
699
844
|
|
|
700
|
-
self.
|
|
845
|
+
self.strategy_country_map[trading_type][strategy_code] = Country[Locale(location).name].value
|
|
701
846
|
return response
|
|
702
847
|
|
|
703
848
|
def backtest(self, strategy=None, start=None, end=None, instruments=None, lots=None, parameters=None, candle=None, mode=None, delete_previous_trades=True, initial_funds_virtual=None, vendor_details=None, **kwargs):
|
|
@@ -731,7 +876,7 @@ class AlgoBullsConnection:
|
|
|
731
876
|
"""
|
|
732
877
|
|
|
733
878
|
# start backtesting job
|
|
734
|
-
|
|
879
|
+
_ = self.start_job(
|
|
735
880
|
strategy_code=strategy, start_timestamp=start, end_timestamp=end, instruments=instruments, lots=lots, strategy_parameters=parameters, candle_interval=candle, strategy_mode=mode,
|
|
736
881
|
initial_funds_virtual=initial_funds_virtual, delete_previous_trades=delete_previous_trades, trading_type=TradingType.BACKTESTING, broking_details=vendor_details, **kwargs
|
|
737
882
|
)
|
|
@@ -769,50 +914,53 @@ class AlgoBullsConnection:
|
|
|
769
914
|
|
|
770
915
|
return self.stop_job(strategy_code=strategy_code, trading_type=TradingType.BACKTESTING)
|
|
771
916
|
|
|
772
|
-
def get_backtesting_logs(self, strategy_code,
|
|
917
|
+
def get_backtesting_logs(self, strategy_code, display_progress_bar=True, print_live_logs=False):
|
|
773
918
|
"""
|
|
774
919
|
Fetch Back Testing logs
|
|
775
920
|
|
|
776
921
|
Args:
|
|
777
922
|
strategy_code: Strategy code
|
|
778
|
-
|
|
779
|
-
|
|
923
|
+
display_progress_bar: to track the execution on progress bar as your strategy is executed
|
|
924
|
+
print_live_logs: to print the live logs as your strategy is executed
|
|
780
925
|
|
|
781
926
|
Returns:
|
|
782
927
|
Report details
|
|
783
928
|
"""
|
|
784
929
|
|
|
785
930
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
931
|
+
assert isinstance(display_progress_bar, bool), f'Argument "display_progress_bar" should be a boolean'
|
|
932
|
+
assert isinstance(print_live_logs, bool), f'Argument "print_live_logs" should be a boolean'
|
|
786
933
|
|
|
787
|
-
return self.get_logs(strategy_code, trading_type=TradingType.BACKTESTING,
|
|
934
|
+
return self.get_logs(strategy_code, trading_type=TradingType.BACKTESTING, display_progress_bar=display_progress_bar, print_live_logs=print_live_logs)
|
|
788
935
|
|
|
789
|
-
def get_backtesting_report_pnl_table(self, strategy_code,
|
|
936
|
+
def get_backtesting_report_pnl_table(self, strategy_code, country=None, force_fetch=False, broker_commission_percentage=None, broker_commission_price=None, slippage_percent=None):
|
|
790
937
|
"""
|
|
791
938
|
Fetch Back Testing Profit & Loss details
|
|
792
939
|
|
|
793
940
|
Args:
|
|
794
941
|
strategy_code: strategy code
|
|
795
|
-
|
|
796
|
-
show_all_rows: True or False
|
|
942
|
+
country: country of Exchange
|
|
797
943
|
force_fetch: Forcefully fetch PnL data
|
|
944
|
+
broker_commission_percentage: Percentage of broker commission per trade
|
|
945
|
+
broker_commission_price: Broker fee per trade
|
|
946
|
+
slippage_percent: Slippage percentage value
|
|
798
947
|
|
|
799
948
|
Returns:
|
|
800
949
|
Report details
|
|
801
950
|
"""
|
|
802
951
|
|
|
803
|
-
if self.backtesting_pnl_data is None or
|
|
804
|
-
self.backtesting_pnl_data = self.
|
|
952
|
+
if self.backtesting_pnl_data is None or country is not None or force_fetch:
|
|
953
|
+
self.backtesting_pnl_data = self.get_report_pnl_table(strategy_code, TradingType.BACKTESTING, country, broker_commission_percentage, broker_commission_price, slippage_percent)
|
|
805
954
|
|
|
806
955
|
return self.backtesting_pnl_data
|
|
807
956
|
|
|
808
|
-
def get_backtesting_report_statistics(self, strategy_code, initial_funds=
|
|
957
|
+
def get_backtesting_report_statistics(self, strategy_code, initial_funds=None, report='metrics', html_dump=False):
|
|
809
958
|
"""
|
|
810
959
|
Fetch Back Testing report statistics
|
|
811
960
|
|
|
812
961
|
Args:
|
|
813
962
|
strategy_code: strategy code
|
|
814
963
|
initial_funds: initial funds that were set before backtesting
|
|
815
|
-
mode: extension used to generate statistics
|
|
816
964
|
report: format and content of the report
|
|
817
965
|
html_dump: save it as a html file
|
|
818
966
|
|
|
@@ -831,20 +979,21 @@ class AlgoBullsConnection:
|
|
|
831
979
|
|
|
832
980
|
return order_report
|
|
833
981
|
|
|
834
|
-
def get_backtesting_report_order_history(self, strategy_code):
|
|
982
|
+
def get_backtesting_report_order_history(self, strategy_code, country=None, render_as_dataframe=False):
|
|
835
983
|
"""
|
|
836
984
|
Fetch Back Testing order history
|
|
837
985
|
|
|
838
986
|
Args:
|
|
839
987
|
strategy_code: strategy code
|
|
840
|
-
|
|
988
|
+
country: country of the segment
|
|
989
|
+
render_as_dataframe: return order history as dataframe or pretty string
|
|
841
990
|
Returns:
|
|
842
991
|
Report details
|
|
843
992
|
"""
|
|
844
993
|
|
|
845
994
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
846
995
|
|
|
847
|
-
return self.
|
|
996
|
+
return self.get_report_order_history(strategy_code=strategy_code, trading_type=TradingType.BACKTESTING, render_as_dataframe=render_as_dataframe, country=country)
|
|
848
997
|
|
|
849
998
|
def papertrade(self, strategy=None, start=None, end=None, instruments=None, lots=None, parameters=None, candle=None, mode=None, delete_previous_trades=True, initial_funds_virtual=None, vendor_details=None, **kwargs):
|
|
850
999
|
"""
|
|
@@ -877,7 +1026,7 @@ class AlgoBullsConnection:
|
|
|
877
1026
|
"""
|
|
878
1027
|
|
|
879
1028
|
# start papertrading job
|
|
880
|
-
|
|
1029
|
+
_ = self.start_job(
|
|
881
1030
|
strategy_code=strategy, start_timestamp=start, end_timestamp=end, instruments=instruments, lots=lots, strategy_parameters=parameters, candle_interval=candle, strategy_mode=mode,
|
|
882
1031
|
initial_funds_virtual=initial_funds_virtual, delete_previous_trades=delete_previous_trades, trading_type=TradingType.PAPERTRADING, broking_details=vendor_details, **kwargs
|
|
883
1032
|
)
|
|
@@ -915,50 +1064,53 @@ class AlgoBullsConnection:
|
|
|
915
1064
|
|
|
916
1065
|
return self.stop_job(strategy_code=strategy_code, trading_type=TradingType.PAPERTRADING)
|
|
917
1066
|
|
|
918
|
-
def get_papertrading_logs(self, strategy_code,
|
|
1067
|
+
def get_papertrading_logs(self, strategy_code, display_progress_bar=True, print_live_logs=True):
|
|
919
1068
|
"""
|
|
920
1069
|
Fetch Paper Trading logs
|
|
921
1070
|
|
|
922
1071
|
Args:
|
|
923
1072
|
strategy_code: Strategy code
|
|
924
|
-
|
|
925
|
-
|
|
1073
|
+
display_progress_bar: to track the execution on progress bar as your strategy is executed
|
|
1074
|
+
print_live_logs: to print the live logs as your strategy is executed
|
|
926
1075
|
|
|
927
1076
|
Returns:
|
|
928
1077
|
Report details
|
|
929
1078
|
"""
|
|
930
1079
|
|
|
931
1080
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
1081
|
+
assert isinstance(display_progress_bar, bool), f'Argument "display_progress_bar" should be a boolean'
|
|
1082
|
+
assert isinstance(print_live_logs, bool), f'Argument "print_live_logs" should be a boolean'
|
|
932
1083
|
|
|
933
|
-
return self.get_logs(strategy_code
|
|
1084
|
+
return self.get_logs(strategy_code, trading_type=TradingType.PAPERTRADING, display_progress_bar=display_progress_bar, print_live_logs=print_live_logs)
|
|
934
1085
|
|
|
935
|
-
def get_papertrading_report_pnl_table(self, strategy_code,
|
|
1086
|
+
def get_papertrading_report_pnl_table(self, strategy_code, country=None, force_fetch=False, broker_commission_percentage=None, broker_commission_price=None, slippage_percent=None):
|
|
936
1087
|
"""
|
|
937
1088
|
Fetch Paper Trading Profit & Loss details
|
|
938
1089
|
|
|
939
1090
|
Args:
|
|
940
1091
|
strategy_code: strategy code
|
|
941
|
-
|
|
942
|
-
show_all_rows: True or False
|
|
1092
|
+
country: country of the exchange
|
|
943
1093
|
force_fetch: Forcefully fetch PnL data
|
|
1094
|
+
broker_commission_percentage: Percentage of broker commission per trade
|
|
1095
|
+
broker_commission_price: Broker fee per trade
|
|
1096
|
+
slippage_percent: Slippage percentage value
|
|
944
1097
|
|
|
945
1098
|
Returns:
|
|
946
1099
|
Report details
|
|
947
1100
|
"""
|
|
948
1101
|
|
|
949
|
-
if self.papertrade_pnl_data is None or
|
|
950
|
-
self.papertrade_pnl_data = self.
|
|
1102
|
+
if self.papertrade_pnl_data is None or country is not None or force_fetch:
|
|
1103
|
+
self.papertrade_pnl_data = self.get_report_pnl_table(strategy_code, TradingType.PAPERTRADING, country, broker_commission_percentage, broker_commission_price, slippage_percent)
|
|
951
1104
|
|
|
952
1105
|
return self.papertrade_pnl_data
|
|
953
1106
|
|
|
954
|
-
def get_papertrading_report_statistics(self, strategy_code, initial_funds=
|
|
1107
|
+
def get_papertrading_report_statistics(self, strategy_code, initial_funds=None, report='metrics', html_dump=False):
|
|
955
1108
|
"""
|
|
956
1109
|
Fetch Paper Trading report statistics
|
|
957
1110
|
|
|
958
1111
|
Args:
|
|
959
1112
|
strategy_code: strategy code
|
|
960
1113
|
initial_funds: initial funds allotted before papertrading
|
|
961
|
-
mode: extension used to generate statistics
|
|
962
1114
|
report: format and content of the report
|
|
963
1115
|
html_dump: save it as a html file
|
|
964
1116
|
|
|
@@ -977,12 +1129,14 @@ class AlgoBullsConnection:
|
|
|
977
1129
|
|
|
978
1130
|
return order_report
|
|
979
1131
|
|
|
980
|
-
def get_papertrading_report_order_history(self, strategy_code):
|
|
1132
|
+
def get_papertrading_report_order_history(self, strategy_code, country=None, render_as_dataframe=False):
|
|
981
1133
|
"""
|
|
982
1134
|
Fetch Paper Trading order history
|
|
983
1135
|
|
|
984
1136
|
Args:
|
|
985
|
-
strategy_code:
|
|
1137
|
+
strategy_code: strategy code
|
|
1138
|
+
country: country of the segment
|
|
1139
|
+
render_as_dataframe: return order history as dataframe or pretty string
|
|
986
1140
|
|
|
987
1141
|
Returns:
|
|
988
1142
|
Report details
|
|
@@ -990,7 +1144,7 @@ class AlgoBullsConnection:
|
|
|
990
1144
|
|
|
991
1145
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
992
1146
|
|
|
993
|
-
return self.
|
|
1147
|
+
return self.get_report_order_history(strategy_code=strategy_code, trading_type=TradingType.PAPERTRADING, render_as_dataframe=render_as_dataframe, country=country)
|
|
994
1148
|
|
|
995
1149
|
def realtrade(self, strategy=None, start=None, end=None, instruments=None, lots=None, parameters=None, candle=None, mode=None, broking_details=None, **kwargs):
|
|
996
1150
|
"""
|
|
@@ -1024,9 +1178,8 @@ class AlgoBullsConnection:
|
|
|
1024
1178
|
"""
|
|
1025
1179
|
|
|
1026
1180
|
# start realtrading job
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
**kwargs)
|
|
1181
|
+
_ = self.start_job(strategy_code=strategy, start_timestamp=start, end_timestamp=end, instruments=instruments, lots=lots, strategy_parameters=parameters, candle_interval=candle, strategy_mode=mode, trading_type=TradingType.REALTRADING,
|
|
1182
|
+
broking_details=broking_details, **kwargs)
|
|
1030
1183
|
|
|
1031
1184
|
# Update previously saved pnl data and exchange location
|
|
1032
1185
|
self.realtrade_pnl_data = None
|
|
@@ -1064,50 +1217,52 @@ class AlgoBullsConnection:
|
|
|
1064
1217
|
|
|
1065
1218
|
return self.stop_job(strategy_code=strategy_code, trading_type=TradingType.REALTRADING)
|
|
1066
1219
|
|
|
1067
|
-
def get_realtrading_logs(self, strategy_code,
|
|
1220
|
+
def get_realtrading_logs(self, strategy_code, display_progress_bar=True, print_live_logs=True):
|
|
1068
1221
|
"""
|
|
1069
1222
|
Fetch Real Trading logs
|
|
1070
1223
|
|
|
1071
1224
|
Args:
|
|
1072
1225
|
strategy_code: Strategy code
|
|
1073
|
-
|
|
1074
|
-
|
|
1226
|
+
display_progress_bar: to track the execution on progress bar as your strategy is executed
|
|
1227
|
+
print_live_logs: to print the live logs as your strategy is executed
|
|
1075
1228
|
|
|
1076
1229
|
Returns:
|
|
1077
1230
|
Report details
|
|
1078
1231
|
"""
|
|
1079
1232
|
|
|
1080
1233
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
1234
|
+
assert isinstance(display_progress_bar, bool), f'Argument "display_progress_bar" should be a boolean'
|
|
1235
|
+
assert isinstance(print_live_logs, bool), f'Argument "print_live_logs" should be a boolean'
|
|
1081
1236
|
|
|
1082
|
-
return self.get_logs(strategy_code
|
|
1237
|
+
return self.get_logs(strategy_code, trading_type=TradingType.REALTRADING, display_progress_bar=display_progress_bar, print_live_logs=print_live_logs)
|
|
1083
1238
|
|
|
1084
|
-
def get_realtrading_report_pnl_table(self, strategy_code,
|
|
1239
|
+
def get_realtrading_report_pnl_table(self, strategy_code, country=None, force_fetch=False, broker_commission_percentage=None, broker_commission_price=None):
|
|
1085
1240
|
"""
|
|
1086
1241
|
Fetch Real Trading Profit & Loss details
|
|
1087
1242
|
|
|
1088
1243
|
Args:
|
|
1089
1244
|
strategy_code: strategy code
|
|
1090
|
-
|
|
1091
|
-
show_all_rows: True or False
|
|
1245
|
+
country: country of the Exchange
|
|
1092
1246
|
force_fetch: Forcefully fetch PnL data
|
|
1247
|
+
broker_commission_percentage: Percentage of broker commission per trade
|
|
1248
|
+
broker_commission_price: Broker fee per trade
|
|
1093
1249
|
|
|
1094
1250
|
Returns:
|
|
1095
1251
|
Report details
|
|
1096
1252
|
"""
|
|
1097
1253
|
|
|
1098
|
-
if self.realtrade_pnl_data is None or
|
|
1099
|
-
self.realtrade_pnl_data = self.
|
|
1254
|
+
if self.realtrade_pnl_data is None or country is not None or force_fetch:
|
|
1255
|
+
self.realtrade_pnl_data = self.get_report_pnl_table(strategy_code, TradingType.REALTRADING, country, broker_commission_percentage, broker_commission_price)
|
|
1100
1256
|
|
|
1101
1257
|
return self.realtrade_pnl_data
|
|
1102
1258
|
|
|
1103
|
-
def get_realtrading_report_statistics(self, strategy_code, initial_funds=
|
|
1259
|
+
def get_realtrading_report_statistics(self, strategy_code, initial_funds=None, report='metrics', html_dump=False):
|
|
1104
1260
|
"""
|
|
1105
1261
|
Fetch Real Trading report statistics
|
|
1106
1262
|
|
|
1107
1263
|
Args:
|
|
1108
1264
|
strategy_code: strategy code
|
|
1109
1265
|
initial_funds: initial funds allotted before realtrading
|
|
1110
|
-
mode: extension used to generate statistics
|
|
1111
1266
|
report: format and content of the report
|
|
1112
1267
|
html_dump: save it as a html file
|
|
1113
1268
|
|
|
@@ -1126,11 +1281,13 @@ class AlgoBullsConnection:
|
|
|
1126
1281
|
|
|
1127
1282
|
return order_report
|
|
1128
1283
|
|
|
1129
|
-
def get_realtrading_report_order_history(self, strategy_code):
|
|
1284
|
+
def get_realtrading_report_order_history(self, strategy_code, country=None, render_as_dataframe=False):
|
|
1130
1285
|
"""
|
|
1131
1286
|
Fetch Real Trading order history
|
|
1132
1287
|
Args:
|
|
1133
|
-
strategy_code:
|
|
1288
|
+
strategy_code: strategy code
|
|
1289
|
+
country: country of the segment
|
|
1290
|
+
render_as_dataframe: return order history as dataframe or pretty string
|
|
1134
1291
|
|
|
1135
1292
|
Returns:
|
|
1136
1293
|
Report details
|
|
@@ -1138,7 +1295,7 @@ class AlgoBullsConnection:
|
|
|
1138
1295
|
# assert (isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be an enum of type {AlgoBullsSupportedBrokers.__name__}'
|
|
1139
1296
|
assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
|
|
1140
1297
|
|
|
1141
|
-
return self.
|
|
1298
|
+
return self.get_report_order_history(strategy_code=strategy_code, trading_type=TradingType.REALTRADING, render_as_dataframe=render_as_dataframe, country=country)
|
|
1142
1299
|
|
|
1143
1300
|
|
|
1144
1301
|
def pandas_dataframe_all_rows():
|
pyalgotrading/constants.py
CHANGED
|
@@ -255,6 +255,15 @@ class Locale(Enum):
|
|
|
255
255
|
USA = 'en-US'
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
class Country(Enum):
|
|
259
|
+
"""
|
|
260
|
+
A class of countries
|
|
261
|
+
"""
|
|
262
|
+
DEFAULT = 'USA'
|
|
263
|
+
INDIA = 'India'
|
|
264
|
+
USA = 'USA'
|
|
265
|
+
|
|
266
|
+
|
|
258
267
|
class ExecutionStatus(Enum):
|
|
259
268
|
"""
|
|
260
269
|
A class of status values of execution
|
|
@@ -265,6 +274,41 @@ class ExecutionStatus(Enum):
|
|
|
265
274
|
STOPPED = 'STOPPED'
|
|
266
275
|
|
|
267
276
|
|
|
277
|
+
class OptionsStrikeDirection(Enum):
|
|
278
|
+
ITM = 'ITM'
|
|
279
|
+
ATM = 'ATM'
|
|
280
|
+
OTM = 'OTM'
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class OptionsTradingsymbolSuffix(Enum):
|
|
284
|
+
CE = 'CE'
|
|
285
|
+
PE = 'PE'
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class OptionsExpiryKey(Enum):
|
|
289
|
+
WEEKLY_CURRENT = 'WEEKLY_CURRENT'
|
|
290
|
+
WEEKLY_NEXT = 'WEEKLY_NEXT'
|
|
291
|
+
MONTHLY_CURRENT = 'MONTHLY_CURRENT'
|
|
292
|
+
MONTHLY_NEXT = 'MONTHLY_NEXT'
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class OptionsInstrumentDirection(Enum):
|
|
296
|
+
EXACT = 'EXACT'
|
|
297
|
+
ROUNDUP = 'ROUNDUP'
|
|
298
|
+
ROUNDDOWN = 'ROUNDDOWN'
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
CandleIntervalSecondsMap = {
|
|
302
|
+
'minute': 60,
|
|
303
|
+
'3minutes': 180,
|
|
304
|
+
'5minutes': 300,
|
|
305
|
+
'10minutes': 600,
|
|
306
|
+
'15minutes': 900,
|
|
307
|
+
'30minutes': 1800,
|
|
308
|
+
'60minutes': 3600,
|
|
309
|
+
'day': 86400
|
|
310
|
+
}
|
|
311
|
+
|
|
268
312
|
KEY_DT_FORMAT_WITH_TIMEZONE = 0
|
|
269
313
|
KEY_DT_FORMAT_WITHOUT_TIMEZONE = 1
|
|
270
314
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from pyalgotrading.strategy.strategy_base import StrategyBase
|
|
2
|
+
from pyalgotrading.constants import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class StrategyOptionsBaseV2(StrategyBase):
|
|
6
|
+
"""
|
|
7
|
+
Dummy placeholder class. Here to ensure all required methods are implemented and as per requirements.
|
|
8
|
+
|
|
9
|
+
Once uploaded, this strategy will be replaced with the real base class strategy
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def get_options_ref_key(instrument, expiry_date):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def initialize_instrument(self, instrument):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
def get_allowed_expiry_dates(self):
|
|
20
|
+
"""
|
|
21
|
+
Gives the allowed expiry date, depending on the selection of monthly expiry or weekly expiry.
|
|
22
|
+
Checkout the documentation to understand in more detail
|
|
23
|
+
"""
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
def options_instruments_set_up(self, base_instrument, instrument_direction, expiry_date, tradingsymbol_suffix, ltp=None, apply_modulo=False, modulo_value=100):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def get_options_instruments(self, base_instrument, expiry_date, tradingsymbol_suffix, instrument_direction, ltp, apply_modulo=False, modulo_value=100):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def get_options_instrument_with_strike_direction(self, base_instrument, expiry_date, tradingsymbol_suffix, strike_direction, no_of_strikes):
|
|
33
|
+
instrument = None
|
|
34
|
+
return instrument
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class IntrumentsMappingManager:
|
|
38
|
+
def __init__(self):
|
|
39
|
+
self.base_instrument_to_instrument_map_list = {}
|
|
40
|
+
self.instrument_to_base_instrument_map = {}
|
|
41
|
+
|
|
42
|
+
def add_mappings(self, base_instrument, child_instruments):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def is_base_instrument(self, instrument):
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
def is_child_instrument(self, instrument):
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
def get_base_instrument(self, instrument):
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
def get_child_instruments_list(self, instrument):
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class OrderTagManager:
|
|
59
|
+
|
|
60
|
+
def add_order(self, order, tags=None):
|
|
61
|
+
"""
|
|
62
|
+
Adds an order to tags extracted from the order object as well as given tags (if any)
|
|
63
|
+
"""
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
# take 1 or more tags; return 1 or more items if many=True; return only 1 item if many=False;
|
|
67
|
+
def get_orders(self, tags, many=False, ignore_errors=False):
|
|
68
|
+
"""
|
|
69
|
+
Takes 1 or more tags; returns 1 or more orders if many=True; returns only 1 order if many=False
|
|
70
|
+
"""
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
def remove_tags(self, tags):
|
|
74
|
+
"""
|
|
75
|
+
Removes 1 or more tags from the data structure
|
|
76
|
+
"""
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
def remove_order(self, order):
|
|
80
|
+
"""
|
|
81
|
+
Removes an order from the data structure
|
|
82
|
+
"""
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
def get_internals(self):
|
|
86
|
+
"""
|
|
87
|
+
Returns the complete data structure
|
|
88
|
+
The developer should use this for debugging, but remove its usages before finalizing
|
|
89
|
+
"""
|
|
90
|
+
return
|
pyalgotrading/utils/func.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
A module for plotting candlesticks
|
|
3
3
|
"""
|
|
4
4
|
from datetime import datetime as dt, timezone
|
|
5
|
-
|
|
5
|
+
import random
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
8
|
from pyalgotrading.constants import PlotType, TRADING_TYPE_DT_FORMAT_MAP, KEY_DT_FORMAT_WITHOUT_TIMEZONE, KEY_DT_FORMAT_WITH_TIMEZONE
|
|
@@ -156,8 +156,69 @@ def get_datetime_with_tz(timestamp_str, trading_type, label=''):
|
|
|
156
156
|
timestamp_str = dt.strptime(timestamp_str, TRADING_TYPE_DT_FORMAT_MAP[trading_type][KEY_DT_FORMAT_WITHOUT_TIMEZONE])
|
|
157
157
|
timestamp_str = timestamp_str.replace(tzinfo=timezone.utc)
|
|
158
158
|
print(f'Warning: Timezone info not provided. Expected timestamp format is "{TRADING_TYPE_DT_FORMAT_MAP[trading_type][KEY_DT_FORMAT_WITH_TIMEZONE]}", received time "{timestamp_str}". Assuming timezone as UTC(+0000)...')
|
|
159
|
-
except ValueError
|
|
160
|
-
raise ValueError(f'Error: Invalid string timestamp format for argument "{label}".\nExpected timestamp format for {trading_type.name} is "{TRADING_TYPE_DT_FORMAT_MAP[trading_type][KEY_DT_FORMAT_WITH_TIMEZONE]}".
|
|
159
|
+
except ValueError:
|
|
160
|
+
raise ValueError(f'Error: Invalid string timestamp format for argument "{label}".\nExpected timestamp format for {trading_type.name} is "{TRADING_TYPE_DT_FORMAT_MAP[trading_type][KEY_DT_FORMAT_WITH_TIMEZONE]}". '
|
|
161
|
+
f'Received "{timestamp_str}" instead.')
|
|
161
162
|
|
|
162
163
|
return timestamp_str
|
|
163
164
|
|
|
165
|
+
|
|
166
|
+
def calculate_slippage(pnl_df, slippage_percent):
|
|
167
|
+
if 'exit_variety' not in pnl_df.columns or 'entry_variety' not in pnl_df.columns:
|
|
168
|
+
pnl_df['exit_variety'] = 'MARKET'
|
|
169
|
+
pnl_df['entry_variety'] = 'MARKET'
|
|
170
|
+
print('WARNING: Column for Order Variety not found. Assuming all trades are Market Orders.')
|
|
171
|
+
|
|
172
|
+
pnl_df[['entry_price', 'exit_price']] = pnl_df.apply(
|
|
173
|
+
lambda row: (slippage(row.entry_price, row.entry_variety, row.entry_transaction_type, slippage_percent), slippage(row.exit_price, row.exit_variety, row.exit_transaction_type, slippage_percent)), axis=1, result_type='expand')
|
|
174
|
+
|
|
175
|
+
pnl_df['pnl_absolute'] = pnl_df['exit_price'] - pnl_df['entry_price']
|
|
176
|
+
return pnl_df
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def calculate_brokerage(pnl_df, brokerage_percentage, brokerage_flat_price):
|
|
180
|
+
# generate brokerage data
|
|
181
|
+
brokerage_generated = False
|
|
182
|
+
|
|
183
|
+
# brokerage commission based on only percentage
|
|
184
|
+
if brokerage_percentage is not None:
|
|
185
|
+
pnl_df['brokerage'] = ((pnl_df['entry_price'] * pnl_df['entry_quantity']) + (pnl_df['exit_price'] * pnl_df['exit_quantity'])) * (brokerage_percentage / 100)
|
|
186
|
+
brokerage_generated = True
|
|
187
|
+
|
|
188
|
+
# brokerage commission based on only flat price
|
|
189
|
+
elif brokerage_flat_price is not None:
|
|
190
|
+
pnl_df['brokerage'] = brokerage_flat_price
|
|
191
|
+
brokerage_generated = True
|
|
192
|
+
|
|
193
|
+
# brokerage 0
|
|
194
|
+
else:
|
|
195
|
+
pnl_df['brokerage'] = 0
|
|
196
|
+
|
|
197
|
+
# brokerage commission based on minimum of percentage and flat price
|
|
198
|
+
if brokerage_percentage is not None and brokerage_flat_price is not None:
|
|
199
|
+
pnl_df["brokerage"].loc[pnl_df["brokerage"] > brokerage_flat_price] = brokerage_flat_price
|
|
200
|
+
brokerage_generated = True
|
|
201
|
+
|
|
202
|
+
if brokerage_generated:
|
|
203
|
+
pnl_df['net_pnl'] = pnl_df['pnl_absolute'] - pnl_df['brokerage']
|
|
204
|
+
else:
|
|
205
|
+
pnl_df['net_pnl'] = pnl_df['pnl_absolute']
|
|
206
|
+
|
|
207
|
+
return pnl_df
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def slippage(price, variety, transaction_type, slip_percent=1):
|
|
211
|
+
|
|
212
|
+
# convert slippage percentage to decimal
|
|
213
|
+
slip_percent = abs(slip_percent) / 100
|
|
214
|
+
|
|
215
|
+
# if market orders, we consider negative as well as positive slippage
|
|
216
|
+
if variety in ['MARKET', 'STOPLOSS_MARKET']:
|
|
217
|
+
return price*(1 + random.choice([1, 0, -1]) * slip_percent)
|
|
218
|
+
|
|
219
|
+
# if limit orders, we consider only positive slippage
|
|
220
|
+
else:
|
|
221
|
+
if transaction_type == 'BUY':
|
|
222
|
+
return price*(1 + random.choice([0, -1]) * slip_percent)
|
|
223
|
+
else:
|
|
224
|
+
return price*(1 + random.choice([1, 0]) * slip_percent)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyalgotrading
|
|
3
|
-
Version: 2023.
|
|
3
|
+
Version: 2023.10.1
|
|
4
4
|
Summary: Official Python Package for Algorithmic Trading APIs powered by AlgoBulls
|
|
5
5
|
Home-page: https://github.com/algobulls/pyalgotrading
|
|
6
6
|
Author: Pushpak Dagade
|
|
@@ -25,8 +25,9 @@ Classifier: Programming Language :: Python :: 3.7
|
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.8
|
|
26
26
|
Requires-Python: >=3.6
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
|
-
|
|
29
|
-
Requires-Dist: pandas
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: pandas >=0.25.3
|
|
30
|
+
Requires-Dist: requests >=2.24.0
|
|
30
31
|
|
|
31
32
|
# pyalgotrading
|
|
32
33
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
pyalgotrading/__init__.py,sha256=e0lmS8IOW1JBf_jfyDAO-9cCEyNsNoSCjwetSCWPeIg,181
|
|
2
|
-
pyalgotrading/constants.py,sha256=
|
|
2
|
+
pyalgotrading/constants.py,sha256=gAzDcdmOqYGFC2GYC9w1i_aSCUF03eFhCLNZNOYCwLM,7309
|
|
3
3
|
pyalgotrading/algobulls/__init__.py,sha256=IRkbWtJfgQnPH7gikjqpa6QsYnmk_NQ1lwtx7LPIC6c,133
|
|
4
|
-
pyalgotrading/algobulls/api.py,sha256=
|
|
5
|
-
pyalgotrading/algobulls/connection.py,sha256=
|
|
4
|
+
pyalgotrading/algobulls/api.py,sha256=fhW0Ms73FqLGRSzz5HDB95ViNsnstXFig8aqGECODCY,19594
|
|
5
|
+
pyalgotrading/algobulls/connection.py,sha256=DKCzloCaZ8sQCr5YXqapQKxpdE_B1svKYkgvETHrzYY,59399
|
|
6
6
|
pyalgotrading/algobulls/exceptions.py,sha256=B96On8cN8tgtX7i4shKOlYfvjSjvspIRPbOpyF-jn0I,2187
|
|
7
7
|
pyalgotrading/broker/__init__.py,sha256=jXVWBVRlqzevKxNDqrPANJz9WubROBes8gaKcxcYz58,66
|
|
8
8
|
pyalgotrading/broker/broker_connection_base.py,sha256=vAJN5EAQuVfL8Ngf03wsaI5TTGdziCHKCb0bczGgSJ0,7594
|
|
@@ -14,17 +14,18 @@ pyalgotrading/order/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
|
14
14
|
pyalgotrading/order/order_base.py,sha256=trC_bvazLNvvuGqZEdacxzlacc3g73ZvqpedYYwuKJA,1742
|
|
15
15
|
pyalgotrading/order/order_bracket_base.py,sha256=7Sj0nlCWE20wZiZ12NciptPYKKGE9KsnKLCjdO7sKV4,3576
|
|
16
16
|
pyalgotrading/order/order_regular_base.py,sha256=wOFmv7QnxJvNKtqdoZn0a-cbTotik4cap5Z5mz0Qcs0,2313
|
|
17
|
-
pyalgotrading/strategy/__init__.py,sha256=
|
|
17
|
+
pyalgotrading/strategy/__init__.py,sha256=V58WuYIWpsmbs5pat5Z1FLxOR3nHwjaIhnJTQ8tzAUA,126
|
|
18
18
|
pyalgotrading/strategy/strategy_base.py,sha256=9I6ZdZT1IimO9g03QURrFo2Pxvs4Dsk8Ybdtij8ZIUQ,4717
|
|
19
|
+
pyalgotrading/strategy/strategy_options_base_v2.py,sha256=3SfCvWjz8eGXtlB2hIXREAODV0PbrtXIYgXRFB08UjQ,2765
|
|
19
20
|
pyalgotrading/strategy/validate_strategy.py,sha256=Ot2kvROtG-tpcbK_Fv-OdapG8oNdgvSNV2hTWtfTCQI,482
|
|
20
21
|
pyalgotrading/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
pyalgotrading/utils/func.py,sha256=
|
|
22
|
+
pyalgotrading/utils/func.py,sha256=Vadc2kbJox0vmBBUWD6DvuWB08rvYGDfvDi-pyOefUA,11155
|
|
22
23
|
pyalgotrading/utils/candlesticks/__init__.py,sha256=maIn__tvTvJDjldPhU9agBcNNuROt_lpNTV4CZ1Yl6I,83
|
|
23
24
|
pyalgotrading/utils/candlesticks/heikinashi.py,sha256=SpcuK7AYV0sgzcw-DMfLNtTDn2RfWqGvWBt4no7yKD4,2003
|
|
24
25
|
pyalgotrading/utils/candlesticks/linebreak.py,sha256=cYwoETMrenWOa06d03xASZoiou-qRz7n2mZYCi5ilEs,1434
|
|
25
26
|
pyalgotrading/utils/candlesticks/renko.py,sha256=zovQ6D658pBLas86FuTu9fU3-Kkv2hM-4h7OQJjdxng,2089
|
|
26
|
-
pyalgotrading-2023.
|
|
27
|
-
pyalgotrading-2023.
|
|
28
|
-
pyalgotrading-2023.
|
|
29
|
-
pyalgotrading-2023.
|
|
30
|
-
pyalgotrading-2023.
|
|
27
|
+
pyalgotrading-2023.10.1.dist-info/LICENSE,sha256=-LLEprvixKS-LwHef99YQSOFon_tWeTwJRAWuUwwr1g,1066
|
|
28
|
+
pyalgotrading-2023.10.1.dist-info/METADATA,sha256=bEA0ZdUGea69WTYNxyEdqPKTmO9SbRtt3pJom3I-aI0,5142
|
|
29
|
+
pyalgotrading-2023.10.1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
30
|
+
pyalgotrading-2023.10.1.dist-info/top_level.txt,sha256=A12PTnbXqO3gsZ0D0Gkyzf_OYRQxjJvtg3MqN-Ur2zY,14
|
|
31
|
+
pyalgotrading-2023.10.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|