pyalgotrading 2023.7.2__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, country: 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
 
@@ -449,14 +443,13 @@ class AlgoBullsAPI:
449
443
  trading_type: Value of TradingType Enum
450
444
  report_type: Value of TradingReportType Enum
451
445
  country: Country of the exchange
452
-
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)
@@ -465,8 +458,8 @@ class AlgoBullsAPI:
465
458
  endpoint = f'v4/book/pl/data'
466
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, Country, ExecutionStatus, 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:
@@ -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 = ''
303
+ sleep_time = 1
304
+ total_seconds = 1
297
305
 
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
304
-
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
383
+
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)
351
388
 
352
- # iterate in reverse order
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,23 +408,25 @@ 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, country=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
432
  country: country of the Exchange
@@ -394,23 +437,75 @@ 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, country=country)
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, country):
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
 
@@ -418,6 +513,10 @@ class AlgoBullsConnection:
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
 
428
- # Fetch the data
527
+ # Set the country code
429
528
  if country is None:
430
529
  country = self.strategy_country_map[trading_type].get(strategy_code, Country.DEFAULT.value)
431
530
 
432
- data = self.get_report(strategy_code=strategy_code, trading_type=trading_type, report_type=TradingReportType.PNL_TABLE, country=country)
531
+ # Fetch the data
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")
534
+
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
@@ -602,6 +746,8 @@ class AlgoBullsConnection:
602
746
  candle_interval = CandleInterval[_]
603
747
  if isinstance(instruments, str):
604
748
  instruments = [instruments]
749
+ if strategy_parameters == {} or strategy_parameters is None:
750
+ strategy_parameters = dict()
605
751
 
606
752
  # Sanity checks - Validate config parameters
607
753
  assert isinstance(strategy_code, str), f'Argument "strategy" should be a valid string'
@@ -730,7 +876,7 @@ class AlgoBullsConnection:
730
876
  """
731
877
 
732
878
  # start backtesting job
733
- response = self.start_job(
879
+ _ = self.start_job(
734
880
  strategy_code=strategy, start_timestamp=start, end_timestamp=end, instruments=instruments, lots=lots, strategy_parameters=parameters, candle_interval=candle, strategy_mode=mode,
735
881
  initial_funds_virtual=initial_funds_virtual, delete_previous_trades=delete_previous_trades, trading_type=TradingType.BACKTESTING, broking_details=vendor_details, **kwargs
736
882
  )
@@ -768,50 +914,53 @@ class AlgoBullsConnection:
768
914
 
769
915
  return self.stop_job(strategy_code=strategy_code, trading_type=TradingType.BACKTESTING)
770
916
 
771
- 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):
772
918
  """
773
919
  Fetch Back Testing logs
774
920
 
775
921
  Args:
776
922
  strategy_code: Strategy code
777
- 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
778
- 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
779
925
 
780
926
  Returns:
781
927
  Report details
782
928
  """
783
929
 
784
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'
785
933
 
786
- 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)
787
935
 
788
- def get_backtesting_report_pnl_table(self, strategy_code, country=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):
789
937
  """
790
938
  Fetch Back Testing Profit & Loss details
791
939
 
792
940
  Args:
793
941
  strategy_code: strategy code
794
942
  country: country of Exchange
795
- show_all_rows: True or False
796
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
797
947
 
798
948
  Returns:
799
949
  Report details
800
950
  """
801
951
 
802
952
  if self.backtesting_pnl_data is None or country is not None or force_fetch:
803
- self.backtesting_pnl_data = self.get_pnl_report_table(strategy_code, TradingType.BACKTESTING, country)
953
+ self.backtesting_pnl_data = self.get_report_pnl_table(strategy_code, TradingType.BACKTESTING, country, broker_commission_percentage, broker_commission_price, slippage_percent)
804
954
 
805
955
  return self.backtesting_pnl_data
806
956
 
807
- 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):
808
958
  """
809
959
  Fetch Back Testing report statistics
810
960
 
811
961
  Args:
812
962
  strategy_code: strategy code
813
963
  initial_funds: initial funds that were set before backtesting
814
- mode: extension used to generate statistics
815
964
  report: format and content of the report
816
965
  html_dump: save it as a html file
817
966
 
@@ -830,20 +979,21 @@ class AlgoBullsConnection:
830
979
 
831
980
  return order_report
832
981
 
833
- 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):
834
983
  """
835
984
  Fetch Back Testing order history
836
985
 
837
986
  Args:
838
987
  strategy_code: strategy code
839
-
988
+ country: country of the segment
989
+ render_as_dataframe: return order history as dataframe or pretty string
840
990
  Returns:
841
991
  Report details
842
992
  """
843
993
 
844
994
  assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
845
995
 
846
- 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)
847
997
 
848
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):
849
999
  """
@@ -876,7 +1026,7 @@ class AlgoBullsConnection:
876
1026
  """
877
1027
 
878
1028
  # start papertrading job
879
- response = self.start_job(
1029
+ _ = self.start_job(
880
1030
  strategy_code=strategy, start_timestamp=start, end_timestamp=end, instruments=instruments, lots=lots, strategy_parameters=parameters, candle_interval=candle, strategy_mode=mode,
881
1031
  initial_funds_virtual=initial_funds_virtual, delete_previous_trades=delete_previous_trades, trading_type=TradingType.PAPERTRADING, broking_details=vendor_details, **kwargs
882
1032
  )
@@ -914,50 +1064,53 @@ class AlgoBullsConnection:
914
1064
 
915
1065
  return self.stop_job(strategy_code=strategy_code, trading_type=TradingType.PAPERTRADING)
916
1066
 
917
- 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):
918
1068
  """
919
1069
  Fetch Paper Trading logs
920
1070
 
921
1071
  Args:
922
1072
  strategy_code: Strategy code
923
- 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
924
- 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
925
1075
 
926
1076
  Returns:
927
1077
  Report details
928
1078
  """
929
1079
 
930
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'
931
1083
 
932
- 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)
933
1085
 
934
- def get_papertrading_report_pnl_table(self, strategy_code, country=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):
935
1087
  """
936
1088
  Fetch Paper Trading Profit & Loss details
937
1089
 
938
1090
  Args:
939
1091
  strategy_code: strategy code
940
1092
  country: country of the exchange
941
- show_all_rows: True or False
942
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
943
1097
 
944
1098
  Returns:
945
1099
  Report details
946
1100
  """
947
1101
 
948
1102
  if self.papertrade_pnl_data is None or country is not None or force_fetch:
949
- self.papertrade_pnl_data = self.get_pnl_report_table(strategy_code, TradingType.PAPERTRADING, country)
1103
+ self.papertrade_pnl_data = self.get_report_pnl_table(strategy_code, TradingType.PAPERTRADING, country, broker_commission_percentage, broker_commission_price, slippage_percent)
950
1104
 
951
1105
  return self.papertrade_pnl_data
952
1106
 
953
- 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):
954
1108
  """
955
1109
  Fetch Paper Trading report statistics
956
1110
 
957
1111
  Args:
958
1112
  strategy_code: strategy code
959
1113
  initial_funds: initial funds allotted before papertrading
960
- mode: extension used to generate statistics
961
1114
  report: format and content of the report
962
1115
  html_dump: save it as a html file
963
1116
 
@@ -976,12 +1129,14 @@ class AlgoBullsConnection:
976
1129
 
977
1130
  return order_report
978
1131
 
979
- 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):
980
1133
  """
981
1134
  Fetch Paper Trading order history
982
1135
 
983
1136
  Args:
984
- 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
985
1140
 
986
1141
  Returns:
987
1142
  Report details
@@ -989,7 +1144,7 @@ class AlgoBullsConnection:
989
1144
 
990
1145
  assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
991
1146
 
992
- 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)
993
1148
 
994
1149
  def realtrade(self, strategy=None, start=None, end=None, instruments=None, lots=None, parameters=None, candle=None, mode=None, broking_details=None, **kwargs):
995
1150
  """
@@ -1023,9 +1178,8 @@ class AlgoBullsConnection:
1023
1178
  """
1024
1179
 
1025
1180
  # start realtrading job
1026
- 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,
1027
- broking_details=broking_details,
1028
- **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)
1029
1183
 
1030
1184
  # Update previously saved pnl data and exchange location
1031
1185
  self.realtrade_pnl_data = None
@@ -1063,50 +1217,52 @@ class AlgoBullsConnection:
1063
1217
 
1064
1218
  return self.stop_job(strategy_code=strategy_code, trading_type=TradingType.REALTRADING)
1065
1219
 
1066
- 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):
1067
1221
  """
1068
1222
  Fetch Real Trading logs
1069
1223
 
1070
1224
  Args:
1071
1225
  strategy_code: Strategy code
1072
- 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
1073
- 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
1074
1228
 
1075
1229
  Returns:
1076
1230
  Report details
1077
1231
  """
1078
1232
 
1079
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'
1080
1236
 
1081
- 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)
1082
1238
 
1083
- def get_realtrading_report_pnl_table(self, strategy_code, country=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):
1084
1240
  """
1085
1241
  Fetch Real Trading Profit & Loss details
1086
1242
 
1087
1243
  Args:
1088
1244
  strategy_code: strategy code
1089
1245
  country: country of the Exchange
1090
- show_all_rows: True or False
1091
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
1092
1249
 
1093
1250
  Returns:
1094
1251
  Report details
1095
1252
  """
1096
1253
 
1097
1254
  if self.realtrade_pnl_data is None or country is not None or force_fetch:
1098
- self.realtrade_pnl_data = self.get_pnl_report_table(strategy_code, TradingType.REALTRADING, country)
1255
+ self.realtrade_pnl_data = self.get_report_pnl_table(strategy_code, TradingType.REALTRADING, country, broker_commission_percentage, broker_commission_price)
1099
1256
 
1100
1257
  return self.realtrade_pnl_data
1101
1258
 
1102
- 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):
1103
1260
  """
1104
1261
  Fetch Real Trading report statistics
1105
1262
 
1106
1263
  Args:
1107
1264
  strategy_code: strategy code
1108
1265
  initial_funds: initial funds allotted before realtrading
1109
- mode: extension used to generate statistics
1110
1266
  report: format and content of the report
1111
1267
  html_dump: save it as a html file
1112
1268
 
@@ -1125,11 +1281,13 @@ class AlgoBullsConnection:
1125
1281
 
1126
1282
  return order_report
1127
1283
 
1128
- 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):
1129
1285
  """
1130
1286
  Fetch Real Trading order history
1131
1287
  Args:
1132
- 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
1133
1291
 
1134
1292
  Returns:
1135
1293
  Report details
@@ -1137,7 +1295,7 @@ class AlgoBullsConnection:
1137
1295
  # assert (isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be an enum of type {AlgoBullsSupportedBrokers.__name__}'
1138
1296
  assert isinstance(strategy_code, str), f'Argument "strategy_code" should be a string'
1139
1297
 
1140
- 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)
1141
1299
 
1142
1300
 
1143
1301
  def pandas_dataframe_all_rows():
@@ -274,6 +274,41 @@ class ExecutionStatus(Enum):
274
274
  STOPPED = 'STOPPED'
275
275
 
276
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
+
277
312
  KEY_DT_FORMAT_WITH_TIMEZONE = 0
278
313
  KEY_DT_FORMAT_WITHOUT_TIMEZONE = 1
279
314
 
@@ -289,3 +324,4 @@ EXCHANGE_LOCALE_MAP = {
289
324
  'NASDAQ': Locale.USA.value,
290
325
  'NYSE': Locale.USA.value,
291
326
  }
327
+
@@ -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.2
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=mrNA15Ob-WNp94jxQNbV1eQGeWgCE7bmNtR3UAoYHJg,6663
2
+ pyalgotrading/constants.py,sha256=gAzDcdmOqYGFC2GYC9w1i_aSCUF03eFhCLNZNOYCwLM,7309
3
3
  pyalgotrading/algobulls/__init__.py,sha256=IRkbWtJfgQnPH7gikjqpa6QsYnmk_NQ1lwtx7LPIC6c,133
4
- pyalgotrading/algobulls/api.py,sha256=jXf3rm1fYc2vLqGowV5CGPrM4NPx7GYkIf-KmxTAJGs,19580
5
- pyalgotrading/algobulls/connection.py,sha256=uz_JUsNdSlru1hvAy9eLkFHSMrWNM-Wpi4j10UNERs8,51016
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.2.dist-info/LICENSE,sha256=-LLEprvixKS-LwHef99YQSOFon_tWeTwJRAWuUwwr1g,1066
27
- pyalgotrading-2023.7.2.dist-info/METADATA,sha256=JtDkZYNTXp7ZiCgUFTOebGbvGxpv06nNPITtT4U36kU,5123
28
- pyalgotrading-2023.7.2.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
29
- pyalgotrading-2023.7.2.dist-info/top_level.txt,sha256=A12PTnbXqO3gsZ0D0Gkyzf_OYRQxjJvtg3MqN-Ur2zY,14
30
- pyalgotrading-2023.7.2.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