Coverage for GuiSessionViewer.py: 0%

343 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-27 18:50 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3 

4#Copyright 2008-2011 Steffen Schaumburg 

5#This program is free software: you can redistribute it and/or modify 

6#it under the terms of the GNU Affero General Public License as published by 

7#the Free Software Foundation, version 3 of the License. 

8# 

9#This program is distributed in the hope that it will be useful, 

10#but WITHOUT ANY WARRANTY; without even the implied warranty of 

11#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12#GNU General Public License for more details. 

13# 

14#You should have received a copy of the GNU Affero General Public License 

15#along with this program. If not, see <http://www.gnu.org/licenses/>. 

16#In the "official" distribution you can find the license in agpl-3.0.txt. 

17 

18from __future__ import print_function 

19from __future__ import division 

20 

21from past.utils import old_div 

22#import L10n 

23#_ = L10n.get_translation() 

24 

25import sys 

26import os 

27import traceback 

28from time import time, strftime, localtime, gmtime 

29 

30from PyQt5.QtCore import Qt 

31from PyQt5.QtGui import (QStandardItem, QStandardItemModel) 

32from PyQt5.QtWidgets import (QFrame, QHBoxLayout, QLabel, QScrollArea, 

33 QSplitter, QTableView, QVBoxLayout, QWidget) 

34 

35import matplotlib 

36from matplotlib.figure import Figure 

37from matplotlib.backends.backend_qt5agg import FigureCanvas 

38from mplfinance.original_flavor import candlestick_ochl 

39from numpy import diff, nonzero, sum, cumsum, max, min, append 

40 

41try: 

42 calluse = not 'matplotlib' in sys.modules 

43 import matplotlib 

44 if calluse: 

45 try: 

46 matplotlib.use('qt5agg') 

47 except ValueError as e: 

48 print(e) 

49 from matplotlib.figure import Figure 

50 from matplotlib.backends.backend_qt5agg import FigureCanvas 

51 from mplfinance.original_flavor import candlestick_ochl 

52 

53 from numpy import diff, nonzero, sum, cumsum, max, min, append 

54 

55except ImportError as inst: 

56 print(("""Failed to load numpy and/or matplotlib in Session Viewer""")) 

57 print("ImportError: %s" % inst.args) 

58 

59import Card 

60import Database 

61import Filters 

62import Charset 

63 

64import GuiHandViewer 

65 

66DEBUG = False 

67 

68class GuiSessionViewer(QSplitter): 

69 def __init__(self, config, querylist, mainwin, owner, colors, debug=True): 

70 QSplitter.__init__(self, mainwin) 

71 self.debug = debug 

72 self.conf = config 

73 self.sql = querylist 

74 self.window = mainwin 

75 self.owner = owner 

76 self.colors = colors 

77 

78 self.liststore = None 

79 

80 self.MYSQL_INNODB = 2 

81 self.PGSQL = 3 

82 self.SQLITE = 4 

83 

84 self.fig = None 

85 self.canvas = None 

86 self.ax = None 

87 self.graphBox = None 

88 

89 # create new db connection to avoid conflicts with other threads 

90 self.db = Database.Database(self.conf, sql=self.sql) 

91 self.cursor = self.db.cursor 

92 

93 settings = {} 

94 settings.update(self.conf.get_db_parameters()) 

95 settings.update(self.conf.get_import_parameters()) 

96 settings.update(self.conf.get_default_paths()) 

97 

98 # text used on screen stored here so that it can be configured 

99 self.filterText = {'handhead': ('Hand Breakdown for all levels listed above')} 

100 

101 filters_display = {"Heroes": True, 

102 "Sites": True, 

103 "Games": True, 

104 "Currencies": True, 

105 "Limits": True, 

106 "LimitSep": True, 

107 "LimitType": True, 

108 "Type": True, 

109 "UseType": 'ring', 

110 "Seats": True, 

111 "SeatSep": False, 

112 "Dates": True, 

113 "Groups": False, 

114 "GroupsAll": False, 

115 "Button1": True, 

116 "Button2": False 

117 } 

118 

119 self.filters = Filters.Filters(self.db, display=filters_display) 

120 self.filters.registerButton1Name("_Refresh") 

121 self.filters.registerButton1Callback(self.refreshStats) 

122 

123 scroll = QScrollArea() 

124 scroll.setWidget(self.filters) 

125 

126 self.columns = [(1.0, "SID"), 

127 (1.0, "Hands"), 

128 (0.5, "Start"), 

129 (0.5, "End"), 

130 (1.0, "Rate"), 

131 (1.0, "Open"), 

132 (1.0, "Close"), 

133 (1.0, "Low"), 

134 (1.0, "High"), 

135 (1.0, "Range"), 

136 (1.0, "Profit")] 

137 

138 self.detailFilters = [] 

139 

140 self.stats_frame = QFrame() 

141 self.stats_frame.setLayout(QVBoxLayout()) 

142 self.view = None 

143 heading = QLabel(self.filterText['handhead']) 

144 heading.setAlignment(Qt.AlignCenter) 

145 self.stats_frame.layout().addWidget(heading) 

146 

147 self.main_vbox = QSplitter(Qt.Vertical) 

148 

149 self.graphBox = QFrame() 

150 self.graphBox.setStyleSheet(f'background-color: {self.colors["background"]}') 

151 self.graphBox.setLayout(QVBoxLayout()) 

152 

153 self.addWidget(scroll) 

154 self.addWidget(self.main_vbox) 

155 self.setStretchFactor(0, 0) 

156 self.setStretchFactor(1, 1) 

157 self.main_vbox.addWidget(self.graphBox) 

158 self.main_vbox.addWidget(self.stats_frame) 

159 

160 def refreshStats(self, checkState): 

161 if self.view: 

162 self.stats_frame.layout().removeWidget(self.view) 

163 self.view.setParent(None) 

164 self.fillStatsFrame(self.stats_frame) 

165 

166 def fillStatsFrame(self, frame): 

167 sites = self.filters.getSites() 

168 heroes = self.filters.getHeroes() 

169 siteids = self.filters.getSiteIds() 

170 games = self.filters.getGames() 

171 currencies = self.filters.getCurrencies() 

172 limits = self.filters.getLimits() 

173 seats = self.filters.getSeats() 

174 sitenos = [] 

175 playerids = [] 

176 

177 for site in sites: 

178 sitenos.append(siteids[site]) 

179 _hname = Charset.to_utf8(heroes[site]) 

180 result = self.db.get_player_id(self.conf, site, _hname) 

181 if result is not None: 

182 playerids.append(result) 

183 

184 if not sitenos: 

185 print(("No sites selected - defaulting to PokerStars")) 

186 sitenos = [2] 

187 if not games: 

188 print(("No games found")) 

189 return 

190 if not currencies: 

191 print(("No currencies found")) 

192 return 

193 if not playerids: 

194 print(("No player ids found")) 

195 return 

196 if not limits: 

197 print(("No limits found")) 

198 return 

199 

200 self.createStatsPane(frame, playerids, sitenos, games, currencies, limits, seats) 

201 

202 def createStatsPane(self, frame, playerids, sitenos, games, currencies, limits, seats): 

203 starttime = time() 

204 

205 (results, quotes) = self.generateDatasets(playerids, sitenos, games, currencies, limits, seats) 

206 

207 if DEBUG: 

208 for x in quotes: 

209 print("start %s\tend %s \thigh %s\tlow %s" % (x[1], x[2], x[3], x[4])) 

210 

211 self.generateGraph(quotes) 

212 

213 self.addTable(frame, results) 

214 

215 self.db.rollback() 

216 print(("Stats page displayed in %4.2f seconds") % (time() - starttime)) 

217 

218 def generateDatasets(self, playerids, sitenos, games, currencies, limits, seats): 

219 if (DEBUG): print("DEBUG: Starting generateDatasets") 

220 THRESHOLD = 1800 # Min # of secs between consecutive hands before being considered a new session 

221 PADDING = 5 # Additional time in minutes to add to a session, session startup, shutdown etc 

222 

223 q = self.sql.query['sessionStats'] 

224 start_date, end_date = self.filters.getDates() 

225 q = q.replace("<datestest>", " BETWEEN '" + start_date + "' AND '" + end_date + "'") 

226 

227 for m in list(self.filters.display.items()): 

228 if m[0] == 'Games' and m[1]: 

229 if len(games) > 0: 

230 gametest = str(tuple(games)) 

231 gametest = gametest.replace("L", "") 

232 gametest = gametest.replace(",)", ")") 

233 gametest = gametest.replace("u'", "'") 

234 gametest = "AND gt.category in %s" % gametest 

235 else: 

236 gametest = "AND gt.category IS NULL" 

237 q = q.replace("<game_test>", gametest) 

238 

239 limittest = self.filters.get_limits_where_clause(limits) 

240 q = q.replace("<limit_test>", limittest) 

241 

242 currencytest = str(tuple(currencies)) 

243 currencytest = currencytest.replace(",)", ")") 

244 currencytest = currencytest.replace("u'", "'") 

245 currencytest = "AND gt.currency in %s" % currencytest 

246 q = q.replace("<currency_test>", currencytest) 

247 

248 if seats: 

249 q = q.replace('<seats_test>', 

250 'AND h.seats BETWEEN ' + str(seats['from']) + 

251 ' AND ' + str(seats['to'])) 

252 else: 

253 q = q.replace('<seats_test>', 'AND h.seats BETWEEN 0 AND 100') 

254 

255 nametest = str(tuple(playerids)) 

256 nametest = nametest.replace("L", "") 

257 nametest = nametest.replace(",)", ")") 

258 q = q.replace("<player_test>", nametest) 

259 q = q.replace("<ampersand_s>", "%s") 

260 

261 if DEBUG: 

262 hands = [ 

263 (u'10000', 10), (u'10000', 20), (u'10000', 30), 

264 (u'20000', -10), (u'20000', -20), (u'20000', -30), 

265 (u'30000', 40), 

266 (u'40000', 0), 

267 (u'50000', -40), 

268 (u'60000', 10), (u'60000', 30), (u'60000', -20), 

269 (u'70000', -20), (u'70000', 10), (u'70000', 30), 

270 (u'80000', -10), (u'80000', -30), (u'80000', 20), 

271 (u'90000', 20), (u'90000', -10), (u'90000', -30), 

272 (u'100000', 30), (u'100000', -50), (u'100000', 30), 

273 (u'110000', -20), (u'110000', 50), (u'110000', -20), 

274 (u'120000', -30), (u'120000', 50), (u'120000', -30), 

275 (u'130000', 20), (u'130000', -50), (u'130000', 20), 

276 (u'140000', 40), (u'140000', -40), 

277 (u'150000', -40), (u'150000', 40), 

278 (u'160000', -40), (u'160000', 80), (u'160000', -40), 

279 ] 

280 else: 

281 self.db.cursor.execute(q) 

282 hands = self.db.cursor.fetchall() 

283 

284 hands = list(hands) 

285 

286 if (not hands): 

287 return ([], []) 

288 

289 hands.insert(0, (hands[0][0], 0)) 

290 

291 times = [int(x[0]) for x in hands] 

292 profits = [float(x[1]) for x in hands] 

293 diffs = diff(times) 

294 diffs2 = append(diffs, THRESHOLD + 1) 

295 index = nonzero(diffs2 > THRESHOLD) 

296 if len(index[0]) > 0: 

297 pass 

298 else: 

299 index = [[0]] 

300 pass 

301 

302 first_idx = 1 

303 quotes = [] 

304 results = [] 

305 cum_sum = old_div(cumsum(profits), 100) 

306 sid = 1 

307 

308 total_hands = 0 

309 total_time = 0 

310 global_open = None 

311 global_lwm = None 

312 global_hwm = None 

313 

314 self.times = [] 

315 for i in range(len(index[0])): 

316 last_idx = index[0][i] 

317 hds = last_idx - first_idx + 1 

318 if hds > 0: 

319 stime = strftime("%d/%m/%Y %H:%M", localtime(times[first_idx])) 

320 etime = strftime("%d/%m/%Y %H:%M", localtime(times[last_idx])) 

321 self.times.append((times[first_idx] - PADDING * 60, times[last_idx] + PADDING * 60)) 

322 minutesplayed = old_div((times[last_idx] - times[first_idx]), 60) 

323 minutesplayed = minutesplayed + PADDING 

324 if minutesplayed == 0: 

325 minutesplayed = 1 

326 hph = hds * 60 / minutesplayed 

327 end_idx = last_idx + 1 

328 won = old_div(sum(profits[first_idx:end_idx]), 100.0) 

329 hwm = max(cum_sum[first_idx - 1:end_idx]) 

330 lwm = min(cum_sum[first_idx - 1:end_idx]) 

331 open = old_div((sum(profits[:first_idx])), 100) 

332 close = old_div((sum(profits[:end_idx])), 100) 

333 

334 total_hands = total_hands + hds 

335 total_time = total_time + minutesplayed 

336 if (global_lwm == None or global_lwm > lwm): 

337 global_lwm = lwm 

338 if (global_hwm == None or global_hwm < hwm): 

339 global_hwm = hwm 

340 if (global_open == None): 

341 global_open = open 

342 global_stime = stime 

343 

344 results.append([sid, hds, stime, etime, hph, 

345 "%.2f" % open, 

346 "%.2f" % close, 

347 "%.2f" % lwm, 

348 "%.2f" % hwm, 

349 "%.2f" % (hwm - lwm), 

350 "%.2f" % won]) 

351 quotes.append((sid, open, close, hwm, lwm)) 

352 first_idx = end_idx 

353 sid = sid + 1 

354 else: 

355 print("hds <= 0") 

356 global_close = close 

357 global_etime = etime 

358 results.append([''] * 11) 

359 results.append([("all"), total_hands, global_stime, global_etime, 

360 total_hands * 60 // total_time, 

361 "%.2f" % global_open, 

362 "%.2f" % global_close, 

363 "%.2f" % global_lwm, 

364 "%.2f" % global_hwm, 

365 "%.2f" % (global_hwm - global_lwm), 

366 "%.2f" % (global_close - global_open)]) 

367 

368 return (results, quotes) 

369 

370 def clearGraphData(self): 

371 

372 try: 

373 try: 

374 if self.canvas: 

375 self.graphBox.layout().removeWidget(self.canvas) 

376 self.canvas.setParent(None) 

377 except: 

378 pass 

379 

380 if self.fig is not None: 

381 self.fig.clear() 

382 self.fig = Figure(figsize=(5, 4), dpi=100) 

383 self.fig.patch.set_facecolor(self.colors['background']) 

384 if self.canvas is not None: 

385 self.canvas.destroy() 

386 

387 self.canvas = FigureCanvas(self.fig) 

388 self.canvas.setParent(self) 

389 except: 

390 err = traceback.extract_tb(sys.exc_info()[2])[-1] 

391 print(("Error:") + " " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])) 

392 raise 

393 

394 def generateGraph(self, quotes): 

395 self.clearGraphData() 

396 sitenos = [] 

397 playerids = [] 

398 

399 sites = self.filters.getSites() 

400 heroes = self.filters.getHeroes() 

401 siteids = self.filters.getSiteIds() 

402 limits = self.filters.getLimits() 

403 

404 graphops = self.filters.getGraphOps() 

405 

406 names = "" 

407 

408 for site in sites: 

409 sitenos.append(siteids[site]) 

410 _hname = Charset.to_utf8(heroes[site]) 

411 result = self.db.get_player_id(self.conf, site, _hname) 

412 if result is not None: 

413 playerids.append(int(result)) 

414 names = names + "\n" + _hname + " on " + site 

415 

416 if not sitenos: 

417 print(("No sites selected - defaulting to PokerStars")) 

418 self.db.rollback() 

419 return 

420 

421 if not playerids: 

422 print(("No player ids found")) 

423 self.db.rollback() 

424 return 

425 

426 if not limits: 

427 print(("No limits found")) 

428 self.db.rollback() 

429 return 

430 

431 self.ax = self.fig.add_subplot(111) 

432 self.ax.tick_params(axis='x', colors=self.colors['foreground']) 

433 self.ax.tick_params(axis='y', colors=self.colors['foreground']) 

434 self.ax.spines['left'].set_color(self.colors['foreground']) 

435 self.ax.spines['right'].set_color(self.colors['foreground']) 

436 self.ax.spines['top'].set_color(self.colors['foreground']) 

437 self.ax.spines['bottom'].set_color(self.colors['foreground']) 

438 self.ax.set_title((("Session graph for ring games") + names), color=self.colors['foreground']) 

439 self.ax.set_facecolor(self.colors['background']) 

440 self.ax.set_xlabel(("Sessions"), fontsize=12, color=self.colors['foreground']) 

441 self.ax.set_ylabel("$", color=self.colors['foreground']) 

442 self.ax.grid(color=self.colors['grid'], linestyle=':', linewidth=0.2) 

443 

444 candlestick_ochl(self.ax, quotes, width=0.50, colordown=self.colors['line_down'], colorup=self.colors['line_up'], alpha=1.00) 

445 self.graphBox.layout().addWidget(self.canvas) 

446 self.canvas.draw() 

447 

448 def addTable(self, frame, results): 

449 colxalign, colheading = list(range(2)) 

450 

451 self.liststore = QStandardItemModel(0, len(self.columns)) 

452 self.liststore.setHorizontalHeaderLabels([column[colheading] for column in self.columns]) 

453 for row in results: 

454 listrow = [QStandardItem(str(r)) for r in row] 

455 for item in listrow: 

456 item.setEditable(False) 

457 self.liststore.appendRow(listrow) 

458 

459 self.view = QTableView() 

460 self.view.setModel(self.liststore) 

461 self.view.verticalHeader().hide() 

462 self.view.setSelectionBehavior(QTableView.SelectRows) 

463 frame.layout().addWidget(self.view) 

464 self.view.doubleClicked.connect(self.row_activated) 

465 

466 def row_activated(self, index): 

467 if index.row() < len(self.times): 

468 replayer = None 

469 for tabobject in self.owner.threads: 

470 if isinstance(tabobject, GuiHandViewer.GuiHandViewer): 

471 replayer = tabobject 

472 self.owner.tab_hand_viewer(None) 

473 break 

474 if replayer is None: 

475 self.owner.tab_hand_viewer(None) 

476 for tabobject in self.owner.threads: 

477 if isinstance(tabobject, GuiHandViewer.GuiHandViewer): 

478 replayer = tabobject 

479 break 

480 reformat = lambda t: strftime("%Y-%m-%d %H:%M:%S+00:00", gmtime(t)) 

481 handids = replayer.get_hand_ids_from_date_range(reformat(self.times[index.row()][0]), reformat(self.times[index.row()][1])) 

482 print('handids:', handids) 

483 replayer.reload_hands(handids) 

484 

485if __name__ == '__main__': 

486 import Configuration 

487 config = Configuration.Config() 

488 

489 settings = {} 

490 

491 settings.update(config.get_db_parameters()) 

492 settings.update(config.get_import_parameters()) 

493 settings.update(config.get_default_paths()) 

494 

495 from PyQt5.QtWidgets import QApplication, QMainWindow 

496 app = QApplication([]) 

497 import SQL 

498 sql = SQL.Sql(db_server=settings['db-server']) 

499 

500 colors = { 

501 'background': '#19232D', 

502 'foreground': '#9DA9B5', 

503 'grid': '#4D4D4D', 

504 'line_up': 'g', 

505 'line_down': 'r', 

506 'line_showdown': 'b', 

507 'line_nonshowdown': 'm', 

508 'line_ev': 'orange', 

509 'line_hands': 'c' 

510 } 

511 

512 i = GuiSessionViewer(config, sql, None, None, colors) 

513 main_window = QMainWindow() 

514 main_window.setCentralWidget(i) 

515 main_window.show() 

516 main_window.resize(1400, 800) 

517 app.exec_()