From 59dadeb8c854b7adfd3daae58a86e16cdc464533 Mon Sep 17 00:00:00 2001 From: Carlos Wu Fei Date: Tue, 21 Apr 2026 20:58:09 +0100 Subject: [PATCH 1/4] Exclude bank transfer deposits from balance --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 774c11087..79c18e39c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ binbot-research/ binquant/ *.log +binquant_telegram_user.session # Editor directories and files !.vscode/extensions.json From 8ef69e7482226bfe1dce7892c44635c7ce2251a1 Mon Sep 17 00:00:00 2001 From: Carlos Wu Fei Date: Tue, 21 Apr 2026 21:36:08 +0100 Subject: [PATCH 2/4] Front-end balance percentage fixes --- .../kucoin/futures/position_deal.py | 2 +- api/portfolio/controller.py | 26 +++++++++++++ api/tests/test_portfolio.py | 39 +++++++++++++------ terminal/src/app/pages/Dashboard.tsx | 17 ++++++-- .../src/app/pages/tests/Dashboard.test.tsx | 2 +- 5 files changed, 69 insertions(+), 17 deletions(-) diff --git a/api/exchange_apis/kucoin/futures/position_deal.py b/api/exchange_apis/kucoin/futures/position_deal.py index 9f372e68e..39803bc07 100644 --- a/api/exchange_apis/kucoin/futures/position_deal.py +++ b/api/exchange_apis/kucoin/futures/position_deal.py @@ -442,7 +442,7 @@ def reverse_position(self) -> BotModel: current_position.mark_price, current_contracts ) - if flip_contracts < current_contracts: + if flip_contracts <= current_contracts: self.active_bot.add_log( "Insufficient balance to reverse position after hitting stop loss, closing position with stop loss order." ) diff --git a/api/portfolio/controller.py b/api/portfolio/controller.py index 27cf7786f..cf93ab002 100644 --- a/api/portfolio/controller.py +++ b/api/portfolio/controller.py @@ -1,6 +1,7 @@ from datetime import datetime from math import sqrt from typing import Union +from account.controller import ConsolidatedAccounts from databases.crud.autotrade_crud import AutotradeCrud from databases.crud.balances_crud import BalancesCrud from pybinbot import ( @@ -55,6 +56,25 @@ def benchmark_interval(self) -> str: return KucoinKlineIntervals.ONE_DAY.value return BinanceKlineIntervals.one_day.value + def _append_live_benchmark_point( + self, + fiat_series: list[float], + btc_series: list[float], + dates: list[int], + balances: list[float], + ) -> None: + current_balance = ConsolidatedAccounts(session=self.session).get_balance() + live_net_balance = round_numbers( + current_balance.estimated_total_fiat - current_balance.total_deposit, 4 + ) + live_btc_price = float(self.api.get_ticker_price(self.benchmark_symbol)) + live_timestamp = int(datetime.now().timestamp() * 1000) + + fiat_series.append(live_net_balance) + btc_series.append(live_btc_price) + dates.append(live_timestamp) + balances.append(live_net_balance) + def _consolidate_dates(self, klines: list[list], balance_date: int) -> int | None: balance_date_day = ts_to_day(balance_date) @@ -143,6 +163,12 @@ def map_balance_with_benchmark( btc_series.reverse() dates.reverse() balances.reverse() + self._append_live_benchmark_point( + fiat_series=fiat_series, + btc_series=btc_series, + dates=dates, + balances=balances, + ) pnl = 0.0 if len(balances) >= 2: diff --git a/api/tests/test_portfolio.py b/api/tests/test_portfolio.py index 79f507ef3..66643a8f7 100644 --- a/api/tests/test_portfolio.py +++ b/api/tests/test_portfolio.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from types import SimpleNamespace from unittest.mock import patch from fastapi.testclient import TestClient @@ -34,9 +35,22 @@ def test_get_benchmark_series(client: TestClient, create_test_tables) -> None: [balance_points[2][0] - 1, "0", "0", "0", "97000", "0", balance_points[2][0]], ] - with patch( - "portfolio.controller.BinanceApi.get_ui_klines", - return_value=klines, + with ( + patch( + "portfolio.controller.BinanceApi.get_ui_klines", + return_value=klines, + ), + patch( + "portfolio.controller.BinanceApi.get_ticker_price", + return_value=98000.0, + ), + patch( + "portfolio.controller.ConsolidatedAccounts.get_balance", + return_value=SimpleNamespace( + estimated_total_fiat=130.0, + total_deposit=5.0, + ), + ), ): response = client.get("/portfolio/benchmark-series") @@ -44,11 +58,14 @@ def test_get_benchmark_series(client: TestClient, create_test_tables) -> None: content = response.json() assert content["message"] == "Successfully retrieved benchmark series." - assert content["data"]["series"] == { - "fiat": [100.0, 110.0, 105.0], - "btc": [95000.0, 96000.0, 97000.0], - "dates": [balance_points[0][0], balance_points[1][0], balance_points[2][0]], - } - assert content["data"]["stats"]["pnl"] == -0.0477 - assert content["data"]["stats"]["sharpe"] == 7.1643 - assert content["data"]["stats"]["btc_sharpe"] == 3649.0498 + assert content["data"]["series"]["fiat"] == [100.0, 110.0, 105.0, 125.0] + assert content["data"]["series"]["btc"] == [95000.0, 96000.0, 97000.0, 98000.0] + assert content["data"]["series"]["dates"][:3] == [ + balance_points[0][0], + balance_points[1][0], + balance_points[2][0], + ] + assert len(content["data"]["series"]["dates"]) == 4 + assert content["data"]["stats"]["pnl"] == 0.16 + assert content["data"]["stats"]["sharpe"] == 16.0555 + assert content["data"]["stats"]["btc_sharpe"] == 2246.155 diff --git a/terminal/src/app/pages/Dashboard.tsx b/terminal/src/app/pages/Dashboard.tsx index 466dcc1b2..5906cf2fb 100644 --- a/terminal/src/app/pages/Dashboard.tsx +++ b/terminal/src/app/pages/Dashboard.tsx @@ -36,18 +36,28 @@ const usePortfolioPnlDetails = ( ): PortfolioPnlDetails => { const benchmarkSeries = benchmark?.benchmarkData?.fiat ?? benchmark?.benchmarkData?.fiat; - const previousPortfolioValue = benchmarkSeries?.[benchmarkSeries.length - 1]; const latestPortfolioValue = accountData?.estimated_total_fiat !== undefined ? accountData.estimated_total_fiat - (accountData?.total_deposit ?? 0) : undefined; + const lastBenchmarkValue = benchmarkSeries?.[benchmarkSeries.length - 1]; + const previousStoredPortfolioValue = + benchmarkSeries && benchmarkSeries.length > 1 + ? benchmarkSeries[benchmarkSeries.length - 2] + : lastBenchmarkValue; + const previousPortfolioValue = + latestPortfolioValue !== undefined && + lastBenchmarkValue !== undefined && + Math.abs(lastBenchmarkValue - latestPortfolioValue) < 0.0001 + ? previousStoredPortfolioValue + : lastBenchmarkValue; const portfolioPnlValue = latestPortfolioValue !== undefined && previousPortfolioValue !== undefined ? latestPortfolioValue - previousPortfolioValue : undefined; const portfolioPnlPercentage = portfolioPnlValue !== undefined && latestPortfolioValue - ? (portfolioPnlValue / (latestPortfolioValue - (accountData?.total_deposit ?? 0))) * 100 + ? (portfolioPnlValue / latestPortfolioValue) * 100 : undefined; const portfolioPnlClass = portfolioPnlValue === undefined @@ -97,8 +107,7 @@ export const DashboardPage: FC<{}> = () => { const { portfolioPnlValue, portfolioPnlPercentage, portfolioPnlClass } = usePortfolioPnlDetails(benchmark, accountData); const portfolioSharpe = benchmark?.portfolioStats?.sharpe; - const netTotalBalance = - (accountData?.estimated_total_fiat ?? 0) - (accountData?.total_deposit ?? 0); + const netTotalBalance = accountData?.estimated_total_fiat ?? 0; const btcSharpe = benchmark?.portfolioStats?.btc_sharpe; const topAlgoCounts = new Set( algoRanking diff --git a/terminal/src/app/pages/tests/Dashboard.test.tsx b/terminal/src/app/pages/tests/Dashboard.test.tsx index 1c775a013..e9809890a 100644 --- a/terminal/src/app/pages/tests/Dashboard.test.tsx +++ b/terminal/src/app/pages/tests/Dashboard.test.tsx @@ -99,7 +99,7 @@ describe("Dashboard page", () => { expect( rtlScreen.getByText("(How efficient are we with risk?)"), ).toBeInTheDocument(); - expect(rtlScreen.getByText("105 USDC")).toBeInTheDocument(); + expect(rtlScreen.getByText("110 USDC")).toBeInTheDocument(); expect(rtlScreen.getByText("1.27 BTC")).toBeInTheDocument(); }); }); From ec25de79f059ababf5622c7c2e5ae9d07e08e4f9 Mon Sep 17 00:00:00 2001 From: Carlos Wu Fei Date: Thu, 23 Apr 2026 13:25:31 +0100 Subject: [PATCH 3/4] Skip auction assets from adp series data --- api/charts/controllers.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/api/charts/controllers.py b/api/charts/controllers.py index 47f450724..567c7ab70 100644 --- a/api/charts/controllers.py +++ b/api/charts/controllers.py @@ -100,28 +100,19 @@ def migrate_adrs(self) -> int: return migrated_count - def _get_market_breadth_tickers(self) -> tuple[Iterable[Any], datetime | None]: - if self.exchange == ExchangeId.KUCOIN: - response = self.kucoin_api.spot_api.get_all_tickers() - ticker = response.common_response.data["ticker"] - time = response.common_response.data["time"] - timestamp = datetime.fromtimestamp(time / 1000, tz=timezone.utc) - - return ticker or [], timestamp - - ticker_data = self.binance_api.ticker_24() - return ticker_data, None - def _normalize_market_breadth_ticker( self, item: GetSymbolResp, fallback_timestamp: datetime | None = None ) -> dict[str, Any] | None: if self.exchange == ExchangeId.KUCOIN: close_time = fallback_timestamp + if item["last"] is None: + # auction coin + return None return { "symbol": item["symbol"], - "last_price": float(item.get("last", 0)), + "last_price": float(item["last"]), "price_change_percent": float(item.get("changeRate", 0)) * 100, - "volume": float(item.get("vol", 0)), + "volume": float(item["vol"]), "close_time": close_time, } @@ -196,7 +187,19 @@ def ingest_adp_data(self): and calculate ADR + Strength Index """ self._ensure_market_breadth_collection() - market_tickers, fallback_timestamp = self._get_market_breadth_tickers() + if self.exchange == ExchangeId.KUCOIN: + response = self.kucoin_api.spot_api.get_all_tickers() + ticker = response.common_response.data["ticker"] + time = response.common_response.data["time"] + timestamp = datetime.fromtimestamp(time / 1000, tz=timezone.utc) + + market_tickers = ticker or [] + fallback_timestamp = timestamp + else: + ticker_data = self.binance_api.ticker_24() + market_tickers = ticker_data or [] + fallback_timestamp = None + adr_data = self._calculate_adr_series_data(market_tickers, fallback_timestamp) response = self.kafka_db.market_breadth.insert_one(adr_data.model_dump()) return response From cd14fc6e2d5984d5a67b91bc180fe933ac2e5c67 Mon Sep 17 00:00:00 2001 From: Carlos Wu Fei Date: Thu, 23 Apr 2026 17:56:15 +0100 Subject: [PATCH 4/4] Decrease adp data cronjob interval so we can see the transitions better --- api/cronjobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/cronjobs.py b/api/cronjobs.py index 9e06a9ee5..b1d7c0d25 100644 --- a/api/cronjobs.py +++ b/api/cronjobs.py @@ -61,7 +61,7 @@ def main(): func=market_domination.ingest_adp_data, trigger="interval", timezone=config.timezone, - hours=1, + minutes=30, id="ingest_adp_data", ) scheduler.start()