diff --git a/api/Pipfile b/api/Pipfile index 2d6ab65fb..e97fe154f 100644 --- a/api/Pipfile +++ b/api/Pipfile @@ -28,6 +28,7 @@ mypy = "*" pytest = "*" unittest = "*" httpx = "*" +mongomock = "*" [requires] python_version = "3.10.8" diff --git a/api/account/assets.py b/api/account/assets.py index d0504ebf8..92903610b 100644 --- a/api/account/assets.py +++ b/api/account/assets.py @@ -444,7 +444,7 @@ def get_market_domination(self, size=7): { "$query": {}, "$orderby": { "_id" : -1 } } ).limit(size)) market_domination_series = MarketDominationSeries() - + for item in data: gainers_percent = 0 losers_percent = 0 diff --git a/api/account/test_assets.py b/api/account/test_assets.py deleted file mode 100644 index e3fa763fb..000000000 --- a/api/account/test_assets.py +++ /dev/null @@ -1,56 +0,0 @@ -from unittest.mock import MagicMock -from account.assets import AccountAssets - -def test_get_market_domination(): - # Mock the database and its find method - db_mock = MagicMock() - db_mock.market_domination.find.return_value = [ - { - "data": [ - {"priceChangePercent": "0.5"}, - {"priceChangePercent": "-0.3"}, - {"priceChangePercent": "0.2"}, - {"priceChangePercent": "-0.1"}, - ], - "time": "2022-01-01", - }, - { - "data": [ - {"priceChangePercent": "0.1"}, - {"priceChangePercent": "-0.2"}, - {"priceChangePercent": "0.3"}, - {"priceChangePercent": "-0.4"}, - ], - "time": "2022-01-02", - }, - { - "data": [ - {"priceChangePercent": "0.4"}, - {"priceChangePercent": "-0.5"}, - {"priceChangePercent": "0.6"}, - {"priceChangePercent": "-0.7"}, - ], - "time": "2022-01-03", - }, - ] - - # Create an instance of AccountAssets and set the mock database - assets = AccountAssets() - assets.db = db_mock - - # Call the get_market_domination method - result = assets.get_market_domination(size=3) - - # Assert the expected result - expected_result = { - "data": { - "dates": ["2022-01-01", "2022-01-02", "2022-01-03"], - "gainers_percent": [0.5, 0.1, 0.4], - "losers_percent": [0.3, 0.2, 0.5], - "gainers_count": 3, - "losers_count": 3, - }, - "message": "Successfully retrieved market domination data.", - "error": 0, - } - assert result == expected_result diff --git a/api/streaming/streaming_controller.py b/api/streaming/streaming_controller.py index bb5e2507a..a30a1ee9c 100644 --- a/api/streaming/streaming_controller.py +++ b/api/streaming/streaming_controller.py @@ -167,54 +167,50 @@ def get_klines(self): logging.info(f"Streaming updates: {markets}") self.client.klines(markets=markets, interval=interval) - def close_trailling_orders(self, result, db_collection: str = "bots"): + def update_order_data(self, result, db_collection: str = "bots"): """ - This database query closes any orders found that are trailling orders i.e. - stop_loss, take_profit, trailling_profit, margin_short_stop_loss, margin_short_trailling_profit - the two latter also denoted as stop_loss and take_profit for simplification purposes + Keep order data up to date - If no order is found with the given order_id, then try with the paper_trading collection - as it could be a test bot + When buy_order or sell_order is executed, they are often in + status NEW, and it takes time to update to FILLED. + This keeps order data up to date as they are executed + throught the executionReport websocket + + Args: + result (dict): executionReport websocket result + db_collection (str, optional): Defaults to "bots". - Finally, if paper_trading doesn't match that order_id either, then try any order in the DB """ order_id = result["i"] - # Close successful take_profit + update = { + "$set": { + "orders.$.status": result["X"], + "orders.$.qty": result["q"], + "orders.$.order_side": result["S"], + "orders.$.order_type": result["o"], + "orders.$.timestamp": result["T"], + }, + "$inc": {"total_commission": float(result["n"])}, + "$push": {"errors": "Order status updated"}, + } + if float(result["p"]) > 0: + update["$set"]["orders.$.price"] = float(result["p"]) + else: + total_qty = 0 + weighted_avg = 0 + for item in result["fills"]: + weighted_avg += float(item["price"]) * float(item["qty"]) + total_qty += float(item["qty"]) + + weighted_avg_price = weighted_avg / total_qty + result["p"] = weighted_avg_price + query = self.streaming_db[db_collection].update_one( {"orders": {"$elemMatch": {"order_id": order_id}}}, - { - "$set": { - "orders.$.status": result["X"], - "orders.$.price": result["p"] if result["p"] else result["p"], - "orders.$.qty": result["q"], - "orders.$.order_side": result["S"], - "orders.$.order_type": result["o"], - "orders.$.timestamp": result["T"], - }, - "$inc": {"total_commission": float(result["n"])}, - "$push": {"errors": "Bot completed!"}, - }, + update, ) return query - def process_user_data(self, result): - # Parse result. Print result for raw result from Binance - order_id = result["i"] - # Example of real update - # {'e': 'executionReport', 'E': 1676750256695, 's': 'UNFIUSDT', 'c': 'web_86e55fed9bad494fba5e213dbe5b2cfc', 'S': 'SELL', 'o': 'LIMIT', 'f': 'GTC', 'q': '8.20000000', 'p': '6.23700000', 'P': '0.00000000', 'F': '0.00000000', 'g': -1, 'C': 'KrHPY4jWdWwFBHUMtBBfJl', 'x': 'CANCELED', ...} - query = self.close_trailling_orders(result) - logging.debug(f'Order updates modified: {query.raw_result["nModified"]}') - if query.raw_result["nModified"] == 0: - # Order not found in bots, so try paper_trading collection - query = self.close_trailling_orders(result, db_collection="paper_trading") - logging.debug(f'Order updates modified: {query.raw_result["nModified"]}') - if query.raw_result["nModified"] == 0: - logging.debug( - f"No bot found with order client order id: {order_id}. Order status: {result['X']}" - ) - return - return - def get_user_data(self): listen_key = self.get_listen_key() self.user_data_client = SpotWebsocketStreamClient( @@ -226,20 +222,16 @@ def on_user_data_message(self, socket, message): logging.info("Streaming user data") res = json.loads(message) - if "result" in res and res["result"]: - logging.info(f'Subscriptions: {res["result"]}') - - # if "data" in res: - # if "e" in res["data"] and res["data"]["e"] == "kline": - # self.process_klines(res["data"]) - # self.process_user_data(res["data"]) - # else: - # logging.error(f'Error: {res["data"]}') - # self.client.stop() if "e" in res: if "executionReport" in res["e"]: logging.info(f'executionReport {res}') - self.process_user_data(res) + query = self.update_order_data(res) + if query.raw_result["nModified"] == 0: + logging.debug( + f'No bot found with order client order id: {res["i"]}. Order status: {res["X"]}' + ) + return + elif "outboundAccountPosition" in res["e"]: logging.info(f'Assets changed {res["e"]}') elif "balanceUpdate" in res["e"]: diff --git a/api/tests/test_assets.py b/api/tests/test_assets.py new file mode 100644 index 000000000..a33543f31 --- /dev/null +++ b/api/tests/test_assets.py @@ -0,0 +1,72 @@ +from account.assets import Assets +from main import app +from db import setup_db + +import mongomock + +test_client = mongomock.MongoClient() + +async def get_test_client(): + return test_client + +app.dependency_overrides[setup_db] = get_test_client + +class TestAccount: + + def __init__(self) -> None: + self.db = test_client["bots"]["market_domination"] + pass + + def test_get_market_domination(self): + # Mock the database and its find method + data = [ + { + "data": [ + {"priceChangePercent": "0.5"}, + {"priceChangePercent": "-0.3"}, + {"priceChangePercent": "0.2"}, + {"priceChangePercent": "-0.1"}, + ], + "time": "2022-01-01", + }, + { + "data": [ + {"priceChangePercent": "0.1"}, + {"priceChangePercent": "-0.2"}, + {"priceChangePercent": "0.3"}, + {"priceChangePercent": "-0.4"}, + ], + "time": "2022-01-02", + }, + { + "data": [ + {"priceChangePercent": "0.4"}, + {"priceChangePercent": "-0.5"}, + {"priceChangePercent": "0.6"}, + {"priceChangePercent": "-0.7"}, + ], + "time": "2022-01-03", + }, + ] + + self.db.insert_one(data) + + # Create an instance of AccountAssets and set the mock database + assets = Assets() + + # Call the get_market_domination method + result = assets.get_market_domination() + + # Assert the expected result + expected_result = { + "data": { + "dates": ["2022-01-01", "2022-01-02", "2022-01-03"], + "gainers_percent": [0.5, 0.1, 0.4], + "losers_percent": [0.3, 0.2, 0.5], + "gainers_count": 3, + "losers_count": 3, + }, + "message": "Successfully retrieved market domination data.", + "error": 0, + } + assert result == expected_result diff --git a/web/src/pages/dashboard/Dashboard.jsx b/web/src/pages/dashboard/Dashboard.jsx index a993dc374..6dd498937 100644 --- a/web/src/pages/dashboard/Dashboard.jsx +++ b/web/src/pages/dashboard/Dashboard.jsx @@ -338,7 +338,7 @@ class Dashboard extends React.Component { - {this.props.benchmarkData.dates?.btc && ( + {this.props.benchmarkData?.dates && ( { const { data: balance_raw } = s.balanceRawReducer; const { data: gainersLosersData } = s.gainersLosersReducer; const { data: gainersLosersSeries } = s.gainersLosersSeriesReducer; - const { data: benchmarkData, btcPrices, usdtBalanceSeries, dates, } = s.btcBenchmarkReducer; + let percentageRevenue = 0; let revenue = 0;