diff --git a/pyalgotrading/algobulls/api.py b/pyalgotrading/algobulls/api.py index 4a7ba301..7eee0592 100644 --- a/pyalgotrading/algobulls/api.py +++ b/pyalgotrading/algobulls/api.py @@ -22,6 +22,9 @@ def __init__(self): Init method that is used while creating an object of this class """ self.headers = None + self.__key_backtesting = None # cstc id + self.__key_papertrading = None # cstc id + self.__key_realtrading = None # cstc id def set_access_token(self, access_token: str): """ @@ -75,6 +78,30 @@ def _send_request(self, method: str = 'get', endpoint: str = '', base_url: str = response.raw.decode_content = True raise AlgoBullsAPIBaseException(f'Unknown non-200 status code --> Method: {method} | URL: {url} | Response: {response_json} | Response code: {response.status_code}') + def __fetch_key(self, strategy_code, trading_type): + # Add strategy to backtesting + endpoint = f'v2/portfolio/strategy' + json_data = {'strategyId': strategy_code, 'tradingType': trading_type.value} + response = self._send_request(method='options', endpoint=endpoint, json_data=json_data) + key = response.get('key') + return key + + def __get_key(self, strategy_code, trading_type): + if trading_type is TradingType.BACKTESTING: + if self.__key_backtesting is None: + self.__key_backtesting = self.__fetch_key(strategy_code=strategy_code, trading_type=TradingType.BACKTESTING) + return self.__key_backtesting + elif trading_type is TradingType.PAPERTRADING: + if self.__key_papertrading is None: + self.__key_papertrading = self.__fetch_key(strategy_code=strategy_code, trading_type=TradingType.PAPERTRADING) + return self.__key_papertrading + elif trading_type is TradingType.REALTRADING: + if self.__key_realtrading is None: + self.__key_realtrading = self.__fetch_key(strategy_code=strategy_code, trading_type=TradingType.REALTRADING) + return self.__key_realtrading + else: + raise NotImplementedError + def create_strategy(self, strategy_name: str, strategy_details: str, abc_version: str) -> dict: """ Create a new strategy for the user on the AlgoBulls platform. @@ -181,20 +208,15 @@ def set_strategy_config(self, strategy_code: str, strategy_config: dict, trading Info: ENDPOINT PATCH v2/portfolio/strategy """ - # Add strategy to backtesting - endpoint = f'v2/portfolio/strategy' - json_data = {'strategyId': strategy_code, 'tradingType': trading_type.value} - response1 = self._send_request(method='options', endpoint=endpoint, json_data=json_data) - key = response1.get('key') - # TODO: Check response to know if request was successful # Configure the params json_data = strategy_config + key = self.__get_key(strategy_code=strategy_code, trading_type=trading_type) endpoint = f'v2/user/strategy/{key}/tweak' response2 = self._send_request(method='patch', endpoint=endpoint, json_data=json_data) return key, response2 - def start_strategy_algotrading(self, key: str, trading_type: TradingType, broker: str = '') -> dict: + def start_strategy_algotrading(self, strategy_code: str, trading_type: TradingType, broker: str = '') -> dict: """ Submit Backtesting / Paper Trading / Real Trading job for strategy with code strategy_code & return the job ID. @@ -210,11 +232,12 @@ def start_strategy_algotrading(self, key: str, trading_type: TradingType, broker else: raise NotImplementedError + key = self.__get_key(strategy_code=strategy_code, trading_type=trading_type) json_data = {'method': 'update', 'newVal': 1, 'key': key, 'record': {'status': 0}} response = self._send_request(method='post', endpoint=endpoint, json_data=json_data) return response - def stop_strategy_algotrading(self, key: str, trading_type: str, broker: str = '') -> dict: + def stop_strategy_algotrading(self, strategy_code: str, trading_type: str, broker: str = '') -> dict: """ Stop Backtesting / Paper Trading / Real Trading job for strategy with code strategy_code & return the job ID. @@ -230,11 +253,12 @@ def stop_strategy_algotrading(self, key: str, trading_type: str, broker: str = ' else: raise NotImplementedError + key = self.__get_key(strategy_code=strategy_code, trading_type=trading_type) json_data = {'method': 'update', 'newVal': 0, 'key': key, 'record': {'status': 2}} response = self._send_request(method='post', endpoint=endpoint, json_data=json_data) return response - def get_job_status(self, strategy_code: str, trading_type: str, broker: str = '') -> dict: + def get_job_status(self, strategy_code: str, trading_type: TradingType) -> dict: """ @@ -243,16 +267,16 @@ def get_job_status(self, strategy_code: str, trading_type: str, broker: str = '' Args: strategy_code: Strategy code trading_type: Trading type - broker: Name of the broker Returns: Job status Info: ENDPOINT - `GET` v1/customer_strategy_algotrading + `GET` v2/user/strategy/status """ - params = {'strategyCode': strategy_code, 'strategyType': StrategyType.PYTHON.value, 'tradingType': trading_type.value, 'broker': broker} - endpoint = f'v1/customer_strategy_algotrading' + key = self.__get_key(strategy_code=strategy_code, trading_type=trading_type) + params = {'key': key} + endpoint = f'v2/user/strategy/status' response = self._send_request(endpoint=endpoint, params=params) return response diff --git a/pyalgotrading/algobulls/connection.py b/pyalgotrading/algobulls/connection.py index ade25e8b..57335a90 100644 --- a/pyalgotrading/algobulls/connection.py +++ b/pyalgotrading/algobulls/connection.py @@ -9,7 +9,7 @@ from .api import AlgoBullsAPI from .exceptions import AlgoBullsAPIBadRequest -from ..constants import StrategyMode, TradingType, TradingReportType, AlgoBullsJobStatus, AlgoBullsJobSubmissionResponse, CandleInterval, AlgoBullsSupportedBrokers +from ..constants import StrategyMode, TradingType, TradingReportType, AlgoBullsJobSubmissionResponse, CandleInterval, AlgoBullsSupportedBrokers from ..strategy.strategy_base import StrategyBase @@ -17,6 +17,7 @@ class AlgoBullsConnection: """ Class for Algobulls connection """ + def __init__(self): """ Init method that is used while creating an object of this class @@ -122,25 +123,21 @@ def search_instrument(self, instrument): True or False """ assert (isinstance(instrument, str) is True), f'Argument instrument should be a string' - response = self.api.search_instrument(instrument).get('data') return response - def get_job_status(self, strategy_code, trading_type, broker=None): + def get_job_status(self, strategy_code, trading_type): """ Gets job status for given strategy_code and trading_type """ assert (isinstance(strategy_code, str) is True), f'Argument strategy_code should be a string' assert (isinstance(trading_type, TradingType) is True), f'Argument trading_type should be an enum of type {TradingType.__name__}' - assert (broker is None or isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be None or an enum of type {AlgoBullsSupportedBrokers.__name__}' + # assert (broker is None or isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be None or an enum of type {AlgoBullsSupportedBrokers.__name__}' - response = self.api.get_job_status(strategy_code=strategy_code, trading_type=trading_type, broker=broker.value) - if response.get('success') is True: - return AlgoBullsJobStatus(response['data'].upper()) - else: - return AlgoBullsJobStatus.JOB_STATUS_UNKNOWN, response + response = self.api.get_job_status(strategy_code=strategy_code, trading_type=trading_type) + return response - def stop_job(self, strategy_code, trading_type, broker=None): + def stop_job(self, strategy_code, trading_type): """ Stops a job Args: @@ -153,13 +150,11 @@ def stop_job(self, strategy_code, trading_type, broker=None): """ assert (isinstance(strategy_code, str) is True), f'Argument strategy_code should be a string' assert (isinstance(trading_type, TradingType) is True), f'Argument trading_type should be an enum of type {TradingType.__name__}' - assert (broker is None or isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be None or an enum of type {AlgoBullsSupportedBrokers.__name__}' + # assert (broker is None or isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be None or an enum of type {AlgoBullsSupportedBrokers.__name__}' - response = self.api.stop_strategy_algotrading(strategy_code=strategy_code, trading_type=trading_type, broker=broker.value) - if response.get('success') is True: - return AlgoBullsJobSubmissionResponse(response['data'].upper()) - else: - return AlgoBullsJobSubmissionResponse.ERROR, response + print(f'Stopping {trading_type.name} job...', end=' ') + response = self.api.stop_strategy_algotrading(strategy_code=strategy_code, trading_type=trading_type) + print('Success.') def get_report(self, strategy_code, trading_type, report_type, render_as_dataframe=False, show_all_rows=False, broker=None): """ @@ -194,7 +189,7 @@ def get_report(self, strategy_code, trading_type, report_type, render_as_datafra else: return AlgoBullsJobSubmissionResponse.ERROR, response - def backtest(self, strategy_code, start_timestamp, end_timestamp, instrument_id, strategy_parameters, candle_interval, strategy_mode=StrategyMode.INTRADAY): + def backtest(self, strategy_code, start_timestamp, end_timestamp, instrument, strategy_parameters, candle_interval, strategy_mode=StrategyMode.INTRADAY): """ Submit a backtesting job for a strategy on the AlgoBulls Platform @@ -202,7 +197,7 @@ def backtest(self, strategy_code, start_timestamp, end_timestamp, instrument_id, strategy_code: strategy code start_timestamp: start date/time end_timestamp: end date/time - instrument_id: instrument key + instrument: instrument key strategy_parameters: parameters candle_interval: candle interval strategy_mode: intraday or delivery @@ -214,24 +209,24 @@ def backtest(self, strategy_code, start_timestamp, end_timestamp, instrument_id, assert (isinstance(strategy_code, str) is True), f'Argument strategy_code should be a string' assert (isinstance(start_timestamp, dt) is True), f'Argument start_timestamp should be an instance of type datetime.datetime' assert (isinstance(end_timestamp, dt) is True), f'Argument start_timestamp should be an instance of type datetime.datetime' - # assert (isinstance(instrument_id, int) is True), f'Argument instrument_id should be a integer. You can find the right id using the \'get_instrument()\' method of AlgoBullsConnection class' + assert (isinstance(instrument, str) is True), f'Argument instrument should be a string. You can find the right id using the \'get_instrument()\' method of AlgoBullsConnection class' assert (isinstance(strategy_parameters, dict) is True), f'Argument strategy_parameters should be a dict' assert (isinstance(strategy_mode, StrategyMode) is True), f'Argument strategy_mode should be enum of type StrategyMode' assert (isinstance(candle_interval, CandleInterval)), f'Argument candle_interval should be an enum of type CandleInterval' # Setup config for Backtesting strategy_config = {'tradingTime': [start_timestamp.strftime('%d-%m-%Y %H:%M'), end_timestamp.strftime('%d-%m-%Y %H:%M')], - 'instruments': [instrument_id], + 'instruments': [instrument], 'parameters': strategy_parameters, 'candle': candle_interval.value, 'strategyMode': strategy_mode.value} print('Setting Strategy Config...', end=' ') - key, _ = self.api.set_strategy_config(strategy_code=strategy_code, strategy_config=strategy_config, trading_type=TradingType.BACKTESTING) + self.api.set_strategy_config(strategy_code=strategy_code, strategy_config=strategy_config, trading_type=TradingType.BACKTESTING) print('Success.') # Submit Backtesting job - print('Submitting Backtesting Job...', end=' ') - response = self.api.start_strategy_algotrading(key=key, trading_type=TradingType.BACKTESTING) + print(f'Submitting {TradingType.BACKTESTING.name} Job...', end=' ') + response = self.api.start_strategy_algotrading(strategy_code=strategy_code, trading_type=TradingType.BACKTESTING) print('Success.') # print(response) @@ -327,7 +322,7 @@ def papertrade(self, strategy_code, start_time, end_time, instrument_id, strateg assert (isinstance(strategy_code, str) is True), f'Argument strategy_code should be a string' assert (isinstance(start_time, time) is True), f'Argument start_timestamp should be an instance of type datetime.datetime' assert (isinstance(end_time, time) is True), f'Argument start_timestamp should be an instance of type datetime.datetime' - assert (isinstance(instrument_id, int) is True), f'Argument instrument_id should be a integer. You can find the right id using the \'get_instrument()\' method of AlgoBullsConnection class' + assert (isinstance(instrument_id, int) is True), f'Argument instrument should be a integer. You can find the right id using the \'get_instrument()\' method of AlgoBullsConnection class' assert (isinstance(strategy_parameters, dict) is True), f'Argument strategy_parameters should be a dict' assert (isinstance(strategy_mode, StrategyMode) is True), f'Argument strategy_mode should be enum of type StrategyMode' assert (isinstance(candle_interval, CandleInterval)), f'Argument candle_interval should be an enum of type CandleInterval' @@ -340,12 +335,12 @@ def papertrade(self, strategy_code, start_time, end_time, instrument_id, strateg 'candle_interval': candle_interval.value, 'strategy_mode': strategy_mode.value} print('Setting Strategy Config...', end=' ') - key, _ = self.api.set_strategy_config(strategy_code=strategy_code, strategy_config=strategy_config, trading_type=TradingType.PAPERTRADING) + self.api.set_strategy_config(strategy_code=strategy_code, strategy_config=strategy_config, trading_type=TradingType.PAPERTRADING) print('Success.') # Submit Paper Trading job - print('Submitting Paper Trading Job...', end=' ') - response = self.api.start_strategy_algotrading(key=key, trading_type=TradingType.PAPERTRADING) + print(f'Submitting {TradingType.PAPERTRADING.name} Job...', end=' ') + response = self.api.start_strategy_algotrading(strategy_code=strategy_code, trading_type=TradingType.PAPERTRADING) print('Success.') # if response.get('success') is True: @@ -447,7 +442,7 @@ def realtrade(self, broker, strategy_code, start_time, end_time, instrument_id, assert (isinstance(strategy_code, str) is True), f'Argument strategy_code should be a string' assert (isinstance(start_time, time) is True), f'Argument start_time should be an instance of type datetime.time' assert (isinstance(end_time, time) is True), f'Argument end_time should be an instance of type datetime.time' - assert (isinstance(instrument_id, int) is True), f'Argument instrument_id should be a integer. You can find the right id using the \'get_instrument()\' method of AlgoBullsConnection class' + assert (isinstance(instrument_id, int) is True), f'Argument instrument should be a integer. You can find the right id using the \'get_instrument()\' method of AlgoBullsConnection class' assert (isinstance(strategy_parameters, dict) is True), f'Argument strategy_parameters should be a dict' assert (isinstance(strategy_mode, StrategyMode) is True), f'Argument strategy_mode should be enum of type StrategyMode' assert (isinstance(candle_interval, CandleInterval)), f'Argument candle_interval should be an enum of type CandleInterval' @@ -460,12 +455,12 @@ def realtrade(self, broker, strategy_code, start_time, end_time, instrument_id, 'candle_interval': candle_interval.value, 'strategy_mode': strategy_mode.value} print('Setting Strategy Config...', end=' ') - key, _ = self.api.set_strategy_config(strategy_code=strategy_code, strategy_config=strategy_config, trading_type=TradingType.REALTRADING) + self.api.set_strategy_config(strategy_code=strategy_code, strategy_config=strategy_config, trading_type=TradingType.REALTRADING) print('Success.') # Submit Real Trading job - print('Submitting Real Trading Job...', end=' ') - response = self.api.start_strategy_algotrading(key=key, trading_type=TradingType.REALTRADING, broker=broker.value) + print(f'Submitting {TradingType.REALTRADING.name} Job...', end=' ') + response = self.api.start_strategy_algotrading(strategy_code=strategy_code, trading_type=TradingType.REALTRADING, broker=broker.value) print('Success.') # if response.get('success') is True: @@ -486,9 +481,9 @@ def get_realtrading_job_status(self, broker, strategy_code): assert (isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be an enum of type {AlgoBullsSupportedBrokers.__name__}' assert (isinstance(strategy_code, str) is True), f'Argument strategy_code should be a string' - return self.get_job_status(strategy_code, TradingType.REALTRADING, broker=broker) + return self.get_job_status(strategy_code, TradingType.REALTRADING) - def stop_realtrading_job(self, broker, strategy_code): + def stop_realtrading_job(self, strategy_code): """ Stop the realtrading session Args: @@ -498,10 +493,9 @@ def stop_realtrading_job(self, broker, strategy_code): Returns: None """ - assert (isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be an enum of type {AlgoBullsSupportedBrokers.__name__}' + # assert (isinstance(broker, AlgoBullsSupportedBrokers) is True), f'Argument broker should be an enum of type {AlgoBullsSupportedBrokers.__name__}' assert (isinstance(strategy_code, str) is True), f'Argument strategy_code should be a string' - - return self.stop_job(strategy_code=strategy_code, trading_type=TradingType.REALTRADING, broker=broker) + return self.stop_job(strategy_code=strategy_code, trading_type=TradingType.REALTRADING) def get_realtrading_logs(self, broker, strategy_code): """