22import json
33import time
44import math
5+ from trade_stats import record_last_closed_trade
56from PyQt6 .QtWidgets import QMainWindow , QVBoxLayout , QWidget , QPushButton , QLineEdit , QLabel , QDoubleSpinBox , QSlider , QComboBox , QCheckBox , QMessageBox , QTabWidget , QToolButton
67from PyQt6 .QtCore import QTimer , Qt
78from PyQt6 .QtGui import QIcon
89from logic import get_account_balance , get_funding_data , get_current_price , get_next_funding_time , place_market_order , get_symbol_info , place_limit_close_order , update_ping , initialize_client , close_all_positions , get_optimal_limit_price , get_candle_open_price , place_stop_loss_order , get_order_execution_price
910from PyQt6 .QtWidgets import QTabBar
1011from PyQt6 .QtWebEngineWidgets import QWebEngineView
1112from PyQt6 .QtCore import QUrl
12- from PyQt6 .QtWidgets import QScrollArea , QHBoxLayout
13+ from PyQt6 .QtWidgets import QScrollArea , QHBoxLayout , QTableWidget , QTableWidgetItem
1314from PyQt6 .QtWebEngineCore import QWebEngineProfile , QWebEnginePage
1415
1516class FundingTraderApp (QMainWindow ):
@@ -91,7 +92,30 @@ class FundingTraderApp(QMainWindow):
9192 "price_validation_warning_text" : "Значна розбіжність цін для {symbol}: Вхід={entry_price:.6f}, Свічка={candle_price:.6f}, Перед фандингом={pre_funding_price:.6f}. Використовується {selected_price:.6f}."
9293 }
9394 }
94-
95+ #
96+ def update_stats_table (self ):
97+ import csv
98+ if not os .path .exists ("trade_stats.csv" ):
99+ self .stats_table .clear ()
100+ return
101+
102+ with open ("trade_stats.csv" , 'r' , encoding = 'utf-8' ) as file :
103+ reader = csv .reader (file )
104+ data = list (reader )
105+ if not data :
106+ return
107+
108+ headers = data [0 ]
109+ self .stats_table .setColumnCount (len (headers ))
110+ self .stats_table .setHorizontalHeaderLabels (headers )
111+ self .stats_table .setRowCount (len (data ) - 1 )
112+
113+ for row_idx , row in enumerate (data [1 :]):
114+ for col_idx , val in enumerate (row ):
115+ self .stats_table .setItem (row_idx , col_idx , QTableWidgetItem (val ))
116+
117+ self .stats_table .resizeColumnsToContents ()
118+ #
95119 def __init__ (self , session , testnet , exchange ):
96120 super ().__init__ ()
97121 self .language = "en"
@@ -175,7 +199,23 @@ def __init__(self, session, testnet, exchange):
175199 exchange = exchange ,
176200 settings = initial_settings
177201 )
202+ #
203+ # Add Statistics tab
204+ stats_tab = QWidget ()
205+ stats_layout = QVBoxLayout (stats_tab )
178206
207+ self .stats_table = QTableWidget ()
208+ self .stats_table .setEditTriggers (QTableWidget .EditTrigger .NoEditTriggers ) # Read-only
209+ stats_layout .addWidget (self .stats_table )
210+
211+ refresh_stats_button = QPushButton (self .translations [self .language ]["refresh_button" ]) # Reuse refresh translation or add new
212+ refresh_stats_button .clicked .connect (self .update_stats_table )
213+ stats_layout .addWidget (refresh_stats_button )
214+
215+ self .tab_widget .addTab (stats_tab , "Statistics" if self .language == "en" else "Статистика" )
216+
217+ self .update_stats_table ()
218+ #
179219 for tab_data in self .tab_data_list :
180220 self .update_tab_funding_data (tab_data )
181221
@@ -199,6 +239,8 @@ def add_new_tab(self, session=None, testnet=None, exchange=None, settings=None):
199239 def create_tab_ui (self , layout , session , testnet , exchange , settings = None ):
200240 default_settings = {
201241 "selected_symbol" : "HYPERUSDT" ,
242+ "position_open" : False ,
243+ "update_count" : 0 ,
202244 "funding_interval_hours" : 1.0 if exchange == "Bybit" else 8.0 ,
203245 "entry_time_seconds" : 5.0 ,
204246 "qty" : 45.0 ,
@@ -752,6 +794,8 @@ def capture_tab_funding_price(self, tab_data, symbol, side):
752794
753795 # Отримання трьох цін
754796 entry_price = get_order_execution_price (tab_data ["session" ], symbol , tab_data ["open_order_id" ], tab_data ["exchange" ])
797+ if entry_price :
798+ tab_data ["position_open" ] = True
755799 candle_price = get_candle_open_price (tab_data ["session" ], symbol , tab_data ["exchange" ])
756800 pre_funding_price = tab_data ["pre_funding_price" ]
757801
@@ -961,6 +1005,29 @@ def update_tab_funding_data(self, tab_data, retry_count=3, retry_delay=2):
9611005 tab_data ["ping_label" ].setText (f"{ self .translations [self .language ]['ping_label' ].split (':' )[0 ]} : Error" )
9621006 tab_data ["ping_label" ].setStyleSheet ("color: red;" )
9631007
1008+ tab_data ["update_count" ] = tab_data .get ("update_count" , 0 ) + 1
1009+ if tab_data .get ("position_open" , False ) and tab_data ["update_count" ] % 10 == 0 : # Check every 10 updates (~10s)
1010+ try :
1011+ symbol = tab_data ["selected_symbol" ]
1012+ if tab_data ["exchange" ] == "Bybit" :
1013+ pos_response = tab_data ["session" ].get_positions (category = "linear" , symbol = symbol )
1014+ if pos_response ["retCode" ] == 0 :
1015+ positions = pos_response ["result" ]["list" ]
1016+ position = next ((p for p in positions if p ["symbol" ] == symbol and float (p ["size" ]) > 0 ), None )
1017+ if not position :
1018+ record_last_closed_trade (tab_data ["session" ], tab_data ["exchange" ], symbol )
1019+ tab_data ["position_open" ] = False
1020+ self .update_stats_table () # Refresh stats table automatically
1021+ else : # Binance (partial support)
1022+ pos_response = tab_data ["session" ].get_position_information (symbol = symbol )
1023+ position = next ((p for p in pos_response if p ["symbol" ] == symbol and abs (float (p ["positionAmt" ])) > 0 ), None )
1024+ if not position :
1025+ record_last_closed_trade (tab_data ["session" ], tab_data ["exchange" ], symbol )
1026+ tab_data ["position_open" ] = False
1027+ self .update_stats_table ()
1028+ except Exception as e :
1029+ print (f"Error checking position status: { e } " )
1030+
9641031#
9651032 def update_tab_funding_web_view (self , tab_data ):
9661033 if tab_data not in self .tab_data_list :
0 commit comments