def initialize_lower_interval_trading(self, caller, interval: str): """ Initializes lower interval trading data object. :param caller: Caller that determines whether lower interval is for simulation or live bot. :param interval: Current interval for simulation or live bot. """ sortedIntervals = ('1m', '3m', '5m', '15m', '30m', '1h', '2h', '12h', '4h', '6h', '8h', '1d', '3d') gui = self.gui symbol = self.trader.symbol if interval != '1m': lowerInterval = sortedIntervals[sortedIntervals.index(interval) - 1] intervalString = helpers.convert_small_interval(lowerInterval) self.lowerIntervalNotification = True self.signals.activity.emit(caller, f'Retrieving {symbol} data for {intervalString.lower()} intervals...') if caller == LIVE: gui.lowerIntervalData = Data(interval=lowerInterval, symbol=symbol, updateData=False) gui.lowerIntervalData.custom_get_new_data(progress_callback=self.signals.progress, removeFirst=True, caller=LIVE) elif caller == SIMULATION: gui.simulationLowerIntervalData = Data(interval=lowerInterval, symbol=symbol, updateData=False) gui.simulationLowerIntervalData.custom_get_new_data(progress_callback=self.signals.progress, removeFirst=True, caller=SIMULATION) else: raise TypeError("Invalid type of caller specified.") lowerData = gui.lowerIntervalData if caller == LIVE else gui.simulationLowerIntervalData if not lowerData or not lowerData.downloadCompleted: raise RuntimeError("Lower interval download failed.") self.signals.activity.emit(caller, "Retrieved lower interval data successfully.") else: self.signals.activity.emit(caller, "There is no lower interval than 1 minute intervals.")
def test_create_csv_file(data_object: Data, descending, army_time, start_date, expected_file_to_match): """ Test create CSV file functionality. :param data_object: Data object to leverage to test this function. :param descending: Boolean that decides whether values in CSV are in descending format or not. :param army_time: Boolean that dictates whether dates will be written in army-time format or not. :param start_date: Date to have CSV data from. """ remove_test_data() data_object.create_table() insert_test_data_to_database() data_object.data = data_object.get_data_from_database() start_date = parser.parse(start_date).date() path = data_object.create_csv_file(descending=descending, army_time=army_time, start_date=start_date) path_to_equal = os.path.join(ROOT_DIR, 'tests', 'data', 'test_create_csv_file_data', expected_file_to_match) assert filecmp.cmp(path, path_to_equal)
def test_dump_to_table(data_object: Data): """ Testing dumping to table functionality. :param data_object: Data object to leverage to test this function. """ remove_test_data() data_object.create_table() normalized_csv_data = get_normalized_csv_data() result = data_object.dump_to_table(normalized_csv_data) assert result is True, "Expected all data to dump successfully." def get_rows(): with closing(sqlite3.connect(DATABASE_FILE_PATH)) as connection: with closing(connection.cursor()) as cursor: db_rows = cursor.execute( f"SELECT * FROM {DATABASE_TABLE} ORDER BY date_utc" ).fetchall() return [ get_normalized_data(row, parse_date=True) for row in db_rows ] rows = get_rows() assert normalized_csv_data == rows, "Values entered are not equal." # Dumping several, duplicate values multiple times. Nothing should be dumped due to duplicates. data_object.dump_to_table(normalized_csv_data) data_object.dump_to_table(normalized_csv_data) assert len(rows) == len( get_rows()), "Expected count of data to be the same."
def test_create_table(data_object: Data): """ Test to ensure create table works as intended. """ remove_test_data() assert os.path.exists( DATABASE_FILE_PATH ) is False, f"Expected {DATABASE_FILE_PATH} to not exist for testing." data_object.create_table() with closing(sqlite3.connect(data_object.database_file)) as connection: with closing(connection.cursor()) as cursor: table_info = cursor.execute( f'PRAGMA TABLE_INFO({data_object.database_table})').fetchall() # Each tuple in table_info contains one column's information like this: (0, 'date_utc', 'TEXT', 0, None, 1) expected_columns = { 'date_utc', 'open_price', 'high_price', 'low_price', 'close_price', 'volume', 'quote_asset_volume', 'number_of_trades', 'taker_buy_base_asset', 'taker_buy_quote_asset' } table_columns = {col[1] for col in table_info} assert table_columns == expected_columns, f"Expected: {expected_columns}. Got: {table_columns}" assert all(col[2] == 'TEXT' for col in table_info), "Expected all columns to have the TEXT data type."
def run(self): """ Initialise the runner function with passed args, kwargs. """ self.signals.started.emit() try: self.client = Data(interval=self.interval, symbol=self.symbol, updateData=False) data = self.client.custom_get_new_data( progress_callback=self.signals.progress, locked=self.signals.locked) if data: if self.descending is None and self.armyTime is None: self.signals.finished.emit(data) else: # This means the CSV generator called this thread. self.signals.progress.emit(100, "Creating CSV file...", -1) savedPath = self.client.create_csv_file( descending=self.descending, armyTime=self.armyTime, startDate=self.startDate) self.signals.csv_finished.emit(savedPath) except Exception as e: print(f'Error: {e}') traceback.print_exc() self.signals.error.emit(str(e)) finally: self.signals.restore.emit()
def run(self): """ Initialise the runner function with passed args, kwargs. """ self.signals.started.emit() try: self.client = Data(interval=self.interval, symbol=self.symbol, update=False) data = self.client.custom_get_new_data( progress_callback=self.signals.progress, locked=self.signals.locked, caller=self.caller) if data: if self.descending is None and self.armyTime is None: self.signals.finished.emit(data, self.caller) else: # This means the CSV generator called this thread. self.signals.progress.emit(100, "Creating CSV file...", '') savedPath = self.client.create_csv_file( descending=self.descending, army_time=self.armyTime, start_date=self.startDate) self.signals.csv_finished.emit(savedPath) except Exception as e: algobot.MAIN_LOGGER.exception(repr(e)) self.signals.error.emit(str(e), self.caller) finally: self.signals.restore.emit(self.caller)
class DownloadThread(QRunnable): """ Thread to use for downloads. """ def __init__(self, interval, symbol, descending=None, armyTime=None, startDate=None, caller=None, logger=None): super(DownloadThread, self).__init__() self.caller = caller self.signals = DownloadSignals() self.symbol = symbol self.interval = interval self.descending = descending self.armyTime = armyTime self.startDate = startDate self.logger = logger self.client: Data or None = None @pyqtSlot() def run(self): """ Initialise the runner function with passed args, kwargs. """ self.signals.started.emit() try: self.client = Data(interval=self.interval, symbol=self.symbol, update=False) data = self.client.custom_get_new_data( progress_callback=self.signals.progress, locked=self.signals.locked, caller=self.caller) if data: if self.descending is None and self.armyTime is None: self.signals.finished.emit(data, self.caller) else: # This means the CSV generator called this thread. self.signals.progress.emit(100, "Creating CSV file...", '') savedPath = self.client.create_csv_file( descending=self.descending, army_time=self.armyTime, start_date=self.startDate) self.signals.csv_finished.emit(savedPath) except Exception as e: algobot.MAIN_LOGGER.exception(repr(e)) self.signals.error.emit(str(e), self.caller) finally: self.signals.restore.emit(self.caller) def stop(self): """ Stop the download loop if it's running. """ if self.client is not None: self.client.download_loop = False
def test_validate_interval(data_object: Data, interval: str, expectation: Callable): """ Test validate interval function. :param data_object: Data object to leverage to check interval validation. :param interval: Interval to check if function handles correctly. :param expectation: Expectation of function. """ with expectation: data_object.validate_interval(interval)
def test_validate_symbol(data_object: Data, symbol: str, expectation: Callable): """ Test validate symbol function. :param data_object: Data object to leverage to check symbol validation. :param symbol: Symbol to check if function handles correctly. :param expectation: Expectation of function. """ with expectation: data_object.validate_symbol(symbol)
class DownloadThread(QRunnable): def __init__(self, interval, symbol, descending=None, armyTime=None, startDate=None): super(DownloadThread, self).__init__() self.signals = DownloadSignals() self.symbol = symbol self.interval = interval self.descending = descending self.armyTime = armyTime self.startDate = startDate self.client: Data or None = None @pyqtSlot() def run(self): """ Initialise the runner function with passed args, kwargs. """ self.signals.started.emit() try: self.client = Data(interval=self.interval, symbol=self.symbol, updateData=False) data = self.client.custom_get_new_data( progress_callback=self.signals.progress, locked=self.signals.locked) if data: if self.descending is None and self.armyTime is None: self.signals.finished.emit(data) else: # This means the CSV generator called this thread. self.signals.progress.emit(100, "Creating CSV file...", -1) savedPath = self.client.create_csv_file( descending=self.descending, armyTime=self.armyTime, startDate=self.startDate) self.signals.csv_finished.emit(savedPath) except Exception as e: print(f'Error: {e}') traceback.print_exc() self.signals.error.emit(str(e)) finally: self.signals.restore.emit() def stop(self): """ Stop the download loop if it's running. """ if self.client is not None: self.client.downloadLoop = False
def test_get_data_from_database(data_object: Data): """ Test to ensure get data from database works appropriately. :param data_object: Data object to leverage to test this function. """ normalized_csv_data = get_normalized_csv_data() remove_test_data() data_object.create_table() data_object.dump_to_table(normalized_csv_data) result = data_object.get_data_from_database() # Reverse because data is in ascending order whereas CSV data is not. assert normalized_csv_data == result, "Expected data to equal."
def test_get_database_file(data_object: Data): """ Test to ensure get database file works as intended. :param data_object: Data object to leverage to test get database file. """ result = data_object.get_database_file() assert result == DATABASE_FILE_PATH, f"Expected: {DATABASE_FILE_PATH}. Got: {result}"
def get_data_object() -> Data: """ Fixture to get a data object with a mocked Binance client. :return: Data object. """ with mock.patch('binance.client.Client', BinanceMockClient): return Data(interval=INTERVAL, symbol=TICKER, load_data=False)
def test_is_valid_symbol(data_object: Data, symbol: str, expected: bool): """ Test to ensure is_valid_symbol works as intended. :param data_object: Data object to leverage to check symbol validation. :param symbol: Symbol value. :param expected: Expected value. """ result = data_object.is_valid_symbol(symbol) assert result is expected, f"Expected: {expected} | Got: {result}"
def __init__(self, startingBalance: float = 1000, interval: str = '1h', symbol: str = 'BTCUSDT', loadData: bool = True, updateData: bool = True, logFile: str = 'simulation', precision: int = 2, addTradeCallback=None): """ SimulationTrader object that will mimic real live market trades. :param startingBalance: Balance to start simulation trader with. :param interval: Interval to start trading on. :param symbol: Symbol to start trading with. :param loadData: Boolean whether we load data from data object or not. :param updateData: Boolean for whether data will be updated if it is loaded. :param logFile: Filename that logger will log to. :param precision: Precision to round data to. :param addTradeCallback: Callback signal to emit to (if provided) to reflect a new transaction. """ super().__init__(precision=precision, symbol=symbol, startingBalance=startingBalance) self.logger = get_logger(logFile=logFile, loggerName=logFile) # Get logger. self.dataView: Data = Data(interval=interval, symbol=symbol, loadData=loadData, updateData=updateData, logObject=self.logger, precision=precision) self.binanceClient = self.dataView.binanceClient # Retrieve Binance client. self.symbol = self.dataView.symbol # Retrieve symbol from data-view object. self.previousNet = self.balance # Our previous net will just be the starting balance in the beginning. self.coinName = self.get_coin_name() # Retrieve primary coin to trade. self.commissionPaid = 0 # Total commission paid to broker. self.dailyChangeNets = [ ] # Daily change net list. Will contain list of all nets. self.completedLoop = True # Loop that'll keep track of bot. We wait for this to turn False before some action. self.lock = Lock( ) # Lock to ensure a transaction doesn't occur when another one is taking place. self.addTradeCallback = addTradeCallback self.customStopLoss = None # Custom stop loss to use if we want to exit trade before trailing or stop loss. self.stopLoss = None # Price at which bot will exit trade due to stop loss limits. self.smartStopLossEnter = False # Boolean that'll determine whether current position is from a smart stop loss. self.scheduledSafetyTimer = None # Next time to check if it's a true stop loss. self.inHumanControl = False # Boolean that keeps track of whether human or bot controls transactions. self.trend = None self.optionDetails = [ ] # Current option values. Holds most recent option values. self.lowerOptionDetails = [ ] # Lower option values. Holds lower interval option values (if exist).
def test_verify_integrity(data_object: Data, data, expected: List[Dict[str, float]]): """ Test verify integrity functionality. :param data_object: Data object to leverage to test this function. :param data: Data to use to check integrity. :param expected: Errored data. """ result = data_object.verify_integrity(data) assert result == expected, f"Expected: {expected}. Got: {result}."
def get_start_date_for_csv(self): symbol = self.csvGenerationTicker.currentText() interval = helpers.convert_long_interval(self.csvGenerationDataInterval.currentText()) ts = Data(loadData=False, log=False).binanceClient._get_earliest_valid_timestamp(symbol, interval) startDate = datetime.fromtimestamp(int(ts) / 1000, tz=timezone.utc) qStart = QDate(startDate.year, startDate.month, startDate.day) endDate = datetime.now(tz=timezone.utc) qEnd = QDate(endDate.year, endDate.month, endDate.day) return [qStart, qEnd]
def get_start_date_for_csv(self) -> List[QDate]: """ Find start date by instantiating a Data object and fetching the Binance API. """ symbol = self.csvGenerationTicker.text() interval = helpers.convert_long_interval(self.csvGenerationDataInterval.currentText()) ts = Data(loadData=False, log=False).binanceClient._get_earliest_valid_timestamp(symbol, interval) startDate = datetime.fromtimestamp(int(ts) / 1000, tz=timezone.utc) qStart = QDate(startDate.year, startDate.month, startDate.day) endDate = datetime.now(tz=timezone.utc) qEnd = QDate(endDate.year, endDate.month, endDate.day) return [qStart, qEnd]
def get_average(self, movingAverage: str, parameter: str, value: int, dataObject: Data = None, update: bool = True, round_value=False) -> float: """ Returns the moving average with parameter and value provided :param round_value: Boolean for whether returned value should be rounded or not. :param update: Boolean for whether average will call the API to get latest values or not. :param dataObject: Data object to be used to get moving averages. :param movingAverage: Moving average to get the average from the data view. :param parameter: Parameter for the data view to use in the moving average. :param value: Value for the moving average to use in the moving average. :return: A float value representing the moving average. """ if not dataObject: dataObject = self.dataView if movingAverage == 'SMA': return dataObject.get_sma(value, parameter, update=update, round_value=round_value) elif movingAverage == 'WMA': return dataObject.get_wma(value, parameter, update=update, round_value=round_value) elif movingAverage == 'EMA': return dataObject.get_ema(value, parameter, update=update, round_value=round_value) else: raise ValueError(f'Unknown moving average {movingAverage}.')
def __init__(self, startingBalance: float = 1000, interval: str = '1h', symbol: str = 'BTCUSDT', loadData: bool = True, updateData: bool = True, isSpot: bool = False, inSpot: bool = False, logFile: str = 'simulation', precision: int = 2, addTradeCallback=None): """ SimulationTrader object that will mimic real live market trades. :param startingBalance: Balance to start simulation trader with. :param interval: Interval to start trading on. :param symbol: Symbol to start trading with. :param loadData: Boolean whether we load data from data object or not. :param updateData: Boolean for whether data will be updated if it is loaded. :param logFile: Filename that logger will log to. :param precision: Precision to round data to. :param addTradeCallback: Callback signal to emit to (if provided) to reflect a new transaction. """ super().__init__(precision=precision, symbol=symbol, startingBalance=startingBalance) self.logger = get_logger(log_file=logFile, logger_name=logFile) # Get logger. self.dataView: Data = Data(interval=interval, symbol=symbol, loadData=loadData, updateData=updateData, logObject=self.logger, precision=precision) self.binanceClient = self.dataView.binanceClient # Retrieve Binance client. self.symbol = self.dataView.symbol # Retrieve symbol from data-view object. self.coinName = self.get_coin_name() # Retrieve primary coin to trade. self.commissionPaid = 0 # Total commission paid to broker. self.completedLoop = True # Loop that'll keep track of bot. We wait for this to turn False before some action. self.inHumanControl = False # Boolean that keeps track of whether human or bot controls transactions. self.lock = Lock( ) # Lock to ensure a transaction doesn't occur when another one is taking place. self.addTradeCallback = addTradeCallback # Callback for GUI to add trades. self.dailyChangeNets = [ ] # Daily change net list. Will contain list of all nets. self.optionDetails = [ ] # Current option values. Holds most recent option values. self.lowerOptionDetails = [ ] # Lower option values. Holds lower interval option values (if exist). self.spot = isSpot self.inSpot = inSpot
def test_get_latest_database_row(data_object: Data): """ Test get latest database row functionality. :param data_object: Data object to leverage to test this function. """ data_object.create_table() result = data_object.get_latest_database_row() assert result == {}, "Expected an empty dictionary." insert_test_data_to_database() result = data_object.get_latest_database_row() expected = { 'close': 3.7763, 'date_utc': convert_str_to_utc_datetime('03/06/2021 01:43 AM'), 'high': 3.7763, 'low': 3.7729, 'number_of_trades': 6192.345082, 'open': 3.7729, 'quote_asset_volume': 1614995039999.0, 'taker_buy_base_asset': 25.0, 'taker_buy_quote_asset': 1635.85, 'volume': 1640.75 } assert result == expected, f'Expected: {expected}. Got: {result}'
class MyTestCase(unittest.TestCase): dataObject = Data(interval='1h', symbol='YFIUSDT', loadData=True) def test_initialization(self): self.assertTrue(self.dataObject.data, 'Data initialization test.') def test_validate_interval(self): self.dataObject.validate_interval('15m') self.dataObject.validate_interval('30m') self.assertRaises(ValueError, self.dataObject.validate_interval, '51m') def test_validate_symbol(self): self.dataObject.validate_symbol('BTCUSDT') self.dataObject.validate_symbol('YFIUSDT') self.assertRaises(ValueError, self.dataObject.validate_symbol, 'BAD') def test_get_latest_row(self): self.assertTrue(self.dataObject.get_latest_database_row())