-2

I have a Python-Binance flask app running on Heroku. I use it to receive trading alerts from TradingView and execute trades on Binance via the API.

I have a websocket setup to monitor current market prices, and I use a function to monitor "open trades" in the spot market and close them once a take profit has been reached.

The app works fine, however I get notifications from Heroku saying:

2023-03-13T00:51:10.044146+00:00 heroku[web.1]: Error R14 (Memory quota exceeded) 2023-03-13T00:51:32.620715+00:00 heroku[web.1]: Process running mem=632M(123.5%)

I have isolated the problem down to the check_tp() function (when disabled I don't have the memory leak).

Does anyone have insight into what could be causing the leak and how to resolve it? I attached the code for the check_tp function below:

def process_stream_data(binance_websocket_api_manager):
    while True:
        try:
            if binance_websocket_api_manager.is_manager_stopping():
                exit(0)
            oldest_data = binance_websocket_api_manager.pop_stream_data_from_stream_buffer()
            is_empty = is_empty_message(oldest_data)
            if is_empty:
                time.sleep(0.01)
            else:
                oldest_data_dict = json.loads(oldest_data)
                data = oldest_data_dict['data']
                #  Handle price change
                handle_price_change(symbol=data['s'], timestamp=data['T'], price=data['p'])
        except Exception as e:
            print(f"Error processing websocket stream data: {e}")
            # Attempt to reconnect to the websocket server
            print("Attempting to reconnect to websocket server...")
            binance_websocket_api_manager.stop_stream(oldest_data)
            binance_websocket_api_manager.start()
            time.sleep(5)  # Wait for 5 seconds before attempting to process stream data again

cache = redis.from_url(os.environ.get("REDIS_URL"))
def handle_price_change(symbol, timestamp, price):
    for s in the_pairs:
        if symbol==s:
            global_prices_write(symbol,price)
    last_execution_time = cache.get('last_execution_time')
    last_stream_check = cache.get('last_stream_check')
    current_time = time.time()
    if not last_execution_time or current_time - float(last_execution_time) >= 15:
        cache.set('last_execution_time', current_time)
        check_tp()
    if not last_stream_check or current_time - float(last_stream_check) >= 300:
        cache.set('last_stream_check', current_time)
        print(global_prices["BTCBUSD"],"STREAM CHECK OK")

def check_tp():
    #print(f"Initial memory usage: {process.memory_info().rss / 1024 / 1024:.2f} MB")
    #print("Checking TPs")
    opt = get_google_open_trades()
    #print(f"Memory usage after get_google_open_trades(): {process.memory_info().rss / 1024 / 1024:.2f} MB")
    sets = get_google_settings()
    #print(f"Memory usage after get_google_settings(): {process.memory_info().rss / 1024 / 1024:.2f} MB")
    for td in opt:
        if td[0]=="Account":
            continue
        perc = 0.0
        sym = td[4]
        order_id = td[2]
        acc = td[0].lower()
        price = td[5]

        for s in sets:
            if s[0].lower() == acc:
                take_profit = s[3]
                break
        
        price_float = 0.0
        price_float=float(price)
        TP = 0.0
        TP = float(take_profit)
        target = float(price_float) + ((float(price_float) * TP) / 100)

        if float(TP) > 0 and float(global_prices_read(sym)) >= target:
            bnan = Client(API_KEYS[acc], API_SECRETS[acc], tld='com')
            inf = bnan.get_my_trades(symbol=sym, orderId=order_id)
            trades = []
            for ele in inf:
                trades.append(ele)
            sell_commission = 0.0
            for td in trades:
                if td["orderId"] == order_id:
                    sell_commission += float(td["commission"])

            newquant = float(td['executedQty']) - float(sell_commission)
            rnd_quantity = format_quantity(sym, newquant, global_prices[sym],bnan)
            order_response = order(binance, "SELL", rnd_quantity, sym, td['clientOrderId'])
            if order_response['side'].upper() == "SELL":
                remove_google_open_trade(order_response['clientOrderId'])
            print(order_response)
    del opt
    del sets
    gc.collect()

enter image description here

The function also references my calls to the google sheets api:

def get_google_open_trades():
    # Get all data from the sheet
    result = service.spreadsheets().values().get(
        spreadsheetId=spreadsheet_id,
        range=f'{open_trades_sheet_name}!A:F',  # specify range A:F to include all columns up to F
        valueRenderOption='UNFORMATTED_VALUE',
        dateTimeRenderOption='FORMATTED_STRING'
    ).execute()

    data = result.get('values', [])

    # Find the last row containing data
    last_row = len(data)

    while last_row > 0:
        row_data = data[last_row-1]
        if any(row_data):  # check if the row contains any non-empty cells
            break
        last_row -= 1

    # Trim any empty rows after the last row containing data
    data = data[:last_row]

    # Print the data to the console
    if not data:
        print('No data found.')
    else:
        return data
    
def get_google_settings():
    # Get the last row and column of data in the sheet
    result = service.spreadsheets().values().get(
        spreadsheetId=spreadsheet_id,
        range=f'{sheet_name}!E:E',
        valueRenderOption='UNFORMATTED_VALUE',
        dateTimeRenderOption='FORMATTED_STRING'
    ).execute()
    last_row = len(result.get('values', []))

    result = service.spreadsheets().values().get(
        spreadsheetId=spreadsheet_id,
        range=f'{sheet_name}!1:1',
        valueRenderOption='UNFORMATTED_VALUE',
        dateTimeRenderOption='FORMATTED_STRING'
    ).execute()
    last_col = len(result.get('values', [])[0])

    # Update the range to fetch all data up to the last row and column
    range_name = f"{sheet_name}!A1:F{last_row}"

    # Get all data from the sheet
    result = service.spreadsheets().values().get(
        spreadsheetId=spreadsheet_id,
        range=range_name,
        valueRenderOption='UNFORMATTED_VALUE',
        dateTimeRenderOption='FORMATTED_STRING'
    ).execute()

    data = result.get('values', [])

    # Print the data to the console
    if not data:
        print('No data found.')
    else:
        return data

I have tried using gc.collect() to force garbage collection, and well as limit the number of times the function runs.

1 Answers1

0

https://devcenter.heroku.com/articles/limits#dynos

heroku has a memory limit

There may already be relatively high memory in the existing program. When check_tp exceeds its load during execution, there are a lot of temporary variables in check_tp, and there is high-frequency memory. You can properly optimize the program. Of course more likely because of opt = get_google_open_trades() and sets = get_google_settings() It is recommended to check, when the execution reaches here, the memory capacity before and after these two.

This is just some suggestions hope it can help you

chrisfang
  • 309
  • 5