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.
@@ -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, log_type: str, initial_next_token: str = None) -> dict:
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
- else:
436
- endpoint = 'v2/user/strategy/logs'
437
- json_data = {'key': key}
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, location: str) -> dict:
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
- location: Location of the exchange
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` v2/user/strategy/pltable for P&L Table
458
- `GET` v2/user/strategy/statstable for Stats Table
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'v3/book/pl/data'
466
- params = {'pageSize': 0, 'isPythonBuild': "true", 'strategyId': strategy_code, 'isLive': trading_type is TradingType.REALTRADING, 'location': location, 'filters': _filter}
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 = 'v2/user/strategy/orderhistory'
469
- params = {'key': key}
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, ExecutionStatus
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.strategy_locale_map = {
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
- response = self.api.stop_strategy_algotrading(strategy_code=strategy_code, trading_type=trading_type)
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, auto_update=True, display_logs_in_auto_update_mode=False):
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
- auto_update: If True, logs will be continuously fetched until strategy execution is complete. A progress bar will show the live status of strategy execution
283
- display_logs_in_auto_update_mode: Applicable only if auto_update is True; display the logs as they are fetched
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
- all_logs = ''
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
- start_timestamp = start_timestamp_map.get(trading_type).replace(tzinfo=None)
306
- end_timestamp = end_timestamp_map.get(trading_type).replace(tzinfo=None)
307
- total_seconds = (end_timestamp - start_timestamp).total_seconds()
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
- count_starting_status = 0
310
- while True:
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
- # if logs are in starting phase, we wait until it changes
313
- if status is None or status == ExecutionStatus.STARTING.value:
314
- status = self.get_job_status(strategy_code, trading_type)["message"]
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
- # if logs get in started phase, we initialize the tqdm object for progress tracking
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
- response = self.api.get_logs(strategy_code=strategy_code, trading_type=trading_type, log_type='partial', initial_next_token=initial_next_token)
326
- logs = response.get('data')
327
- if type(logs) is list:
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 logs are empty we wait
331
- if not logs:
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
- # if status is stopped we break the while loop
335
- if status in [ExecutionStatus.STOPPED.value, ExecutionStatus.STOPPING.value]:
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
- break
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
- # print the logs below progressbar
349
- if display_logs_in_auto_update_mode:
350
- tqdm.write('\n'.join(logs))
379
+ time.sleep(5)
380
+
381
+ # tqdm.write(f"WARNING: got no data, current status is {status}...") # for debug
382
+ continue
351
383
 
352
- # iterate in reverse order
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
- # avoid infinite loop in case of error
371
- if error_counter > 5:
372
- break
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 get_report(self, strategy_code, trading_type, report_type, render_as_dataframe=False, show_all_rows=False, location=None):
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
- location: Location of the Exchange
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
- response = self.api.get_reports(strategy_code=strategy_code, trading_type=trading_type, report_type=report_type, location=location)
402
- if response.get('data'):
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
- if show_all_rows:
405
- pandas_dataframe_all_rows()
406
- _response = pd.DataFrame(response['data'])
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 = response['data']
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('Report not available yet. Please retry in sometime')
506
+ print("Report not available yet. Please retry in sometime")
412
507
 
413
- def get_pnl_report_table(self, strategy_code, trading_type, location):
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
- location: Location of the exchange
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
- if location is None:
430
- location = self.strategy_locale_map[trading_type].get(strategy_code, Locale.DEFAULT.value)
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
- data = self.get_report(strategy_code=strategy_code, trading_type=trading_type, report_type=TradingReportType.PNL_TABLE, location=location)
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', 'pnl_absolute']]
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.pnl_absolute.cumsum() + initial_funds
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
- if report == "metrics":
496
- order_report = qs.reports.metrics(total_funds_series)
497
- elif report == "full":
498
- order_report = qs.reports.full(total_funds_series)
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
- all_strategies = self.get_all_strategies()
503
- strategy_name = all_strategies.loc[all_strategies['strategyCode'] == strategy_code]['strategyName'].iloc[0]
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.strategy_locale_map[trading_type][strategy_code] = location
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
- response = self.start_job(
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, auto_update=True, display_logs_in_auto_update_mode=False):
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
- auto_update: If True, logs will be continuously fetched until strategy execution is complete. A progress bar will show the live status of strategy execution
779
- display_logs_in_auto_update_mode: Applicable only if auto_update is True; display the logs as they are fetched
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, auto_update=auto_update, display_logs_in_auto_update_mode=display_logs_in_auto_update_mode)
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, location=None, show_all_rows=False, force_fetch=False):
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
- location: Location of Exchange
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 location is not None or force_fetch:
804
- self.backtesting_pnl_data = self.get_pnl_report_table(strategy_code, TradingType.BACKTESTING, location)
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=1e9, mode='quantstats', report='metrics', html_dump=False):
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.get_report(strategy_code=strategy_code, trading_type=TradingType.BACKTESTING, report_type=TradingReportType.ORDER_HISTORY)
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
- response = self.start_job(
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, auto_update=False, display_logs_in_auto_update_mode=False):
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
- auto_update: If True, logs will be continuously fetched until strategy execution is complete. A progress bar will show the live status of strategy execution
925
- display_logs_in_auto_update_mode: Applicable only if auto_update is True; display the logs as they are fetched
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=strategy_code, trading_type=TradingType.PAPERTRADING, auto_update=auto_update, display_logs_in_auto_update_mode=display_logs_in_auto_update_mode)
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, location=None, show_all_rows=False, force_fetch=False):
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
- location: Location of the exchange
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 location is not None or force_fetch:
950
- self.papertrade_pnl_data = self.get_pnl_report_table(strategy_code, TradingType.PAPERTRADING, location)
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=1e9, mode='quantstats', report='metrics', html_dump=False):
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: 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.get_report(strategy_code=strategy_code, trading_type=TradingType.PAPERTRADING, report_type=TradingReportType.ORDER_HISTORY)
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
- response = 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,
1028
- broking_details=broking_details,
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, auto_update=False, display_logs_in_auto_update_mode=False):
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
- auto_update: If True, logs will be continuously fetched until strategy execution is complete. A progress bar will show the live status of strategy execution
1074
- display_logs_in_auto_update_mode: Applicable only if auto_update is True; display the logs as they are fetched
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=strategy_code, trading_type=TradingType.REALTRADING, auto_update=auto_update, display_logs_in_auto_update_mode=display_logs_in_auto_update_mode)
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, location=None, show_all_rows=False, force_fetch=False):
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
- location: Location of the Exchange
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 location is not None or force_fetch:
1099
- self.realtrade_pnl_data = self.get_pnl_report_table(strategy_code, TradingType.REALTRADING, location)
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=1e9, mode='quantstats', report='metrics', html_dump=False):
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: 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.get_report(strategy_code=strategy_code, trading_type=TradingType.REALTRADING, report_type=TradingReportType.ORDER_HISTORY)
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():
@@ -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
 
@@ -1 +1,3 @@
1
+ from ..constants import *
1
2
  from .strategy_base import StrategyBase
3
+ from .strategy_options_base_v2 import StrategyOptionsBaseV2
@@ -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
@@ -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 as ex:
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]}". Received "{timestamp_str}" instead.')
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.7.1
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
- Requires-Dist: requests (>=2.24.0)
29
- Requires-Dist: pandas (>=0.25.3)
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=tXclfw_CW5A9arWh2ZmHBRBqWRmOHGAji_AAVIhUl-E,6544
2
+ pyalgotrading/constants.py,sha256=gAzDcdmOqYGFC2GYC9w1i_aSCUF03eFhCLNZNOYCwLM,7309
3
3
  pyalgotrading/algobulls/__init__.py,sha256=IRkbWtJfgQnPH7gikjqpa6QsYnmk_NQ1lwtx7LPIC6c,133
4
- pyalgotrading/algobulls/api.py,sha256=EIh0PUFyWzVXin_jWvd9MpF45gs9EWvdUPkQ05aOfYE,19585
5
- pyalgotrading/algobulls/connection.py,sha256=oNlGOWjrPF0exKjJBSaQ9HUFZ5iQ7sR-_z5oDf4y3EI,51042
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=NSR4t9X44sjL8jVEg11SBt-_J8i8sArHE_LZX6m3lcU,40
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=V1ktGhQqr1Hdyw30B6TMQ5wntnrW7NVwQZd1enyjyFc,8656
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.7.1.dist-info/LICENSE,sha256=-LLEprvixKS-LwHef99YQSOFon_tWeTwJRAWuUwwr1g,1066
27
- pyalgotrading-2023.7.1.dist-info/METADATA,sha256=8JVGrO4p_ZvHy1WAvX4P4iA6UapgYhLGRVjCxJc0dW8,5123
28
- pyalgotrading-2023.7.1.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
29
- pyalgotrading-2023.7.1.dist-info/top_level.txt,sha256=A12PTnbXqO3gsZ0D0Gkyzf_OYRQxjJvtg3MqN-Ur2zY,14
30
- pyalgotrading-2023.7.1.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.34.2)
2
+ Generator: bdist_wheel (0.41.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5