From 7b636f03b42e21642a254aad5c8aafb6d224781d Mon Sep 17 00:00:00 2001 From: elkd Date: Wed, 6 Apr 2022 19:11:40 +0800 Subject: [PATCH 1/4] improved the handling of edge case when there is no win or loss --- session.py | 31 ++++++++++++++++--------------- trade.py | 22 ++++++++++++++++------ 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/session.py b/session.py index f577311..8196e59 100644 --- a/session.py +++ b/session.py @@ -15,7 +15,8 @@ def __init__(self): self.mtng = self.start_balance = 0.1 #self.ldp must always be an integer - self.ldp = 0 + #play_reg is a register to avoid infinity loops + self.ldp = self.play_reg = 0 self.stake = self.init_stake = 0.35 self.loop = self.paused = False @@ -133,6 +134,7 @@ async def tight_play(self, spot_balance_span, stake_input): ''' bid_spot = prev_spot = '' + self.play_reg = 0 self.loop = True xbtn_visible = await self.close_btn_handle.is_visible() @@ -149,10 +151,8 @@ async def tight_play(self, spot_balance_span, stake_input): bid_spot = await spot_balance_span.inner_text() if int(bid_spot[-1]) is self.ldp: #Play Check - #Once LDP is correct, purchase immediately, - #Deriv will schedule for the next price spot try: - await self.page.locator("#purchase_button_top").click(timeout=100) + await self.page.locator("#purchase_button_top").click(timeout=500) sleep(0.6) bid_spot_purchase = await spot_balance_span.inner_text() @@ -163,39 +163,41 @@ async def tight_play(self, spot_balance_span, stake_input): while next_price == bid_spot_purchase: next_price = await spot_balance_span.inner_text() - logging.info(f"**PRICES: {bid_spot}, {bid_spot_purchase}, {next_price}, **") + logging.debug(f"**PRICES: {bid_spot}, {bid_spot_purchase}, {next_price}, **") if bid_spot == bid_spot_purchase: if int(next_price[-1]) is self.ldp: - logging.info('--FAST WON--') + logging.debug('--FAST WON--') self.stake = self.init_stake else: - logging.info('FAST LOST!!!') + logging.debug('FAST LOST!!!') self.stake = round(self.stake * self.mtng + self.stake, 2) else: - #This sleep is to wait for the results, #don't give control back to the event loop sleep(0.5) result_str = await self.page.locator( "#contract_purchase_heading" ).inner_text() - while result_str == 'Contract Confirmation': + while self.play_reg < 8 and result_str == 'Contract Confirmation': result_str = await self.page.locator( "#contract_purchase_heading" ).inner_text() + sleep(self.play_reg/10) + self.play_reg += 1 - logging.info(result_str) + self.play_reg = 0 if result_str == "This contract won": - logging.info('--WON--') + logging.debug('--SLOW WON--') self.stake = self.init_stake elif result_str == "This contract lost": - logging.info('LOST!!!') + logging.debug('SLOW LOST!!!') self.stake = round(self.stake * self.mtng + self.stake, 2) else: logging.info('@@The bot could not update stake@@') + raise PWTimeoutError - logging.info(f'The stake is updated to: {self.stake}') + logging.debug(f'The stake is updated to: {self.stake}') await stake_input.fill(str(self.stake)) pbtn_visible = await self.purchase_handle.is_visible() @@ -206,7 +208,7 @@ async def tight_play(self, spot_balance_span, stake_input): except PWTimeoutError as e: logging.error(e) logging.info('Purchase btn is disabled by Deriv, waiting for activation...') - await asyncio.sleep(5) + await asyncio.sleep(4) return await self.tight_play(spot_balance_span, stake_input) else: @@ -276,7 +278,6 @@ async def play(self, window, values): f"https://smarttrader.deriv.com/en/trading.html?currency=USD&market=synthetic_index&underlying=R_100&formname=matchdiff&date_start=now&duration_amount=1&duration_units=t&amount={self.stake}&amount_type=stake&expiry_type=duration&prediction={self.ldp}" ) except PWTimeoutError as e: - #traceback.format_exc() logging.info('The page is taking long to load please wait') await asyncio.sleep(8) diff --git a/trade.py b/trade.py index ca8d2f8..111ff1b 100644 --- a/trade.py +++ b/trade.py @@ -102,16 +102,27 @@ async def close_window(loop, signal=None): def exc_handler(loop, context): - #context["message"] always be present; context["exception"] is optional - msg = context.get("exception", context["message"]) - logging.error(f"DerivBot Caught Exception: {msg}") + '''Callable with arguments active loop and exception dict + + Context dict contains the following keys (highlight few) + ‘message’: Error message; Always be present + ‘exception’ (optional): Exception object; + ‘future’ (optional): asyncio.Future instance; + ‘task’ (optional): asyncio.Task instance; + ‘handle’ (optional): asyncio.Handle instance; + ''' + exc_info = context.get("exception", context["message"]) + #if isinstance(exc_info, Exception): + #logging.error(f"DerivBot Caught Exception: {exc_info}") + traceback.format_exc() + logging.error(f"DerivBot Caught Exception: {exc_info}") asyncio.create_task(close_window(loop)) def main(): logging.basicConfig( - level=logging.INFO, + level=logging.DEBUG, format="%(asctime)s,%(msecs)d %(levelname)s: %(message)s", datefmt="%H:%M:%S", ) @@ -160,8 +171,7 @@ def main(): #Use it in 2 different coros in the async loop trade_session = TradeSession() - uvloop.install() - loop = asyncio.get_event_loop() + loop = uvloop.new_event_loop() signals = (signal.SIGHUP, signal.SIGTERM, signal.SIGINT) for s in signals: loop.add_signal_handler( From e1ff94f34b8622ff74883e2fc092c7f822a3acb0 Mon Sep 17 00:00:00 2001 From: elkd Date: Wed, 6 Apr 2022 19:14:20 +0800 Subject: [PATCH 2/4] tightened the play loop by removing logging --- session.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/session.py b/session.py index 8196e59..7780ae7 100644 --- a/session.py +++ b/session.py @@ -163,14 +163,11 @@ async def tight_play(self, spot_balance_span, stake_input): while next_price == bid_spot_purchase: next_price = await spot_balance_span.inner_text() - logging.debug(f"**PRICES: {bid_spot}, {bid_spot_purchase}, {next_price}, **") if bid_spot == bid_spot_purchase: if int(next_price[-1]) is self.ldp: - logging.debug('--FAST WON--') self.stake = self.init_stake else: - logging.debug('FAST LOST!!!') self.stake = round(self.stake * self.mtng + self.stake, 2) else: #don't give control back to the event loop @@ -188,16 +185,13 @@ async def tight_play(self, spot_balance_span, stake_input): self.play_reg = 0 if result_str == "This contract won": - logging.debug('--SLOW WON--') self.stake = self.init_stake elif result_str == "This contract lost": - logging.debug('SLOW LOST!!!') self.stake = round(self.stake * self.mtng + self.stake, 2) else: - logging.info('@@The bot could not update stake@@') + logging.info('**The bot could not update stake**') raise PWTimeoutError - logging.debug(f'The stake is updated to: {self.stake}') await stake_input.fill(str(self.stake)) pbtn_visible = await self.purchase_handle.is_visible() From c810ea5686e6b347b178fa3f455729e94ba009ef Mon Sep 17 00:00:00 2001 From: elkd Date: Wed, 6 Apr 2022 21:38:04 +0800 Subject: [PATCH 3/4] implement 1st workaround for PW memory leak from context JS objects --- .gitignore | 2 +- session.py | 119 ++++++++++++++++++++++++++++++++++++----------------- trade.py | 4 +- 3 files changed, 84 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 2ffb8e0..5b64a15 100755 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,7 @@ media/ staticfiles/ prod-staticfiles/ .idea -state.json +.state.json CODEOWNERS diff --git a/session.py b/session.py index 7780ae7..911ed52 100644 --- a/session.py +++ b/session.py @@ -24,25 +24,25 @@ def __init__(self): self.playwright = self.browser = self.context = self.page = None - async def setup(self, window): - ''' - This was supposed to go to init method - but a quick way to avoid a return on the init - ''' + async def setup(self, window, flush=False): + '''This is an extended init method - window['_MG_'].update(self.mtng) - window['_SL_'].update(self.stop_loss) - window['_SP_'].update(self.stop_profit) - window['_SK_'].update(self.init_stake) + It creates the Playwright variables, + and attaches them to the session object. + ''' - window['_LDP_'].update(self.ldp) + state = ".state.json" + if not flush: + window['_MG_'].update(self.mtng) + window['_SL_'].update(self.stop_loss) + window['_SP_'].update(self.stop_profit) + window['_SK_'].update(self.init_stake) - self.playwright = await async_playwright().start() - self.browser = await self.playwright.chromium.launch(headless=False) + window['_LDP_'].update(self.ldp) - state = "state.json" - await asyncio.sleep(0) + self.playwright = await async_playwright().start() + self.browser = await self.playwright.chromium.launch(headless=False) if os.path.isfile(state): # Create a new context with the saved storage state. @@ -60,9 +60,48 @@ async def setup(self, window): await asyncio.sleep(10) + + async def flush_context(self, window, loop): + ''' A work around for the memory leak issue + + ''' + await asyncio.sleep(60 * 2) + + window['_MESSAGE_'].update( + 'THE BOT WILL RESTART IN 60 SECONDS, DONT CHANGE ANYTHING NOW' + ) + + #await asyncio.sleep(20) + window['_MESSAGE_'].update( + 'RESTARTING THE APP TO CLEAR THE MEMORY...' + ) + + is_playing = self.loop + is_paused = self.paused + + if is_playing: + self.loop = False + + await self.page.close() + await self.context.close() + #await self.browser.close() + + await self.setup(window, flush=True) + + if is_playing: + await self.play(window, {}, flush=True) + if is_paused: + await self.play(window, {}, flush=True) + self.pause(window, values) + + loop.create_task(self.flush_context(window, loop)) + window['_MESSAGE_'].update('') + return + + async def login(self, email, psword, window): ''' - Login if not logged in and then store the context into state.json + Login if not logged in and then store the context into .state.json ''' #Best way to go about this is to check if state.json exists in path @@ -110,7 +149,7 @@ async def login(self, email, psword, window): window['_MESSAGE_'].update('Logged in already!') await asyncio.sleep(3) # Save storage state into the file. - storage = await self.context.storage_state(path="state.json") + storage = await self.context.storage_state(path=".state.json") async def tight_play(self, spot_balance_span, stake_input): @@ -154,6 +193,7 @@ async def tight_play(self, spot_balance_span, stake_input): try: await self.page.locator("#purchase_button_top").click(timeout=500) + #self.page.wait_for_timeout(600) sleep(0.6) bid_spot_purchase = await spot_balance_span.inner_text() @@ -171,6 +211,7 @@ async def tight_play(self, spot_balance_span, stake_input): self.stake = round(self.stake * self.mtng + self.stake, 2) else: #don't give control back to the event loop + #self.page.wait_for_timeout(600) sleep(0.5) result_str = await self.page.locator( "#contract_purchase_heading" @@ -219,36 +260,38 @@ async def tight_play(self, spot_balance_span, stake_input): prev_spot = bid_spot - async def play(self, window, values): + async def play(self, window, values, flush=False): ''' Runs the smarttrader session with Volatility 100, Match/Differ option and Tick = 1 ''' - window['_PLAY_STATUS_'].update('PLAYING...') + if not flush: + window['_PLAY_STATUS_'].update('PLAYING...') - if not values['_SK_'] or not values['_MG_'] or not values['_LDP_']: - window['_MESSAGE_'].update( - 'Please provide LDP, Stake and initial Martingale' - ) - return + if not values['_SK_'] or not values['_MG_'] or not values['_LDP_']: + window['_MESSAGE_'].update( + 'Please provide LDP, Stake and initial Martingale' + ) + return + + if self.paused: + self.paused = False + xbtn_visible = await self.close_btn_handle.is_visible() + if xbtn_visible: + try: + await self.close_btn_handle.click(timeout=2000) + except PWTimeoutError as e: + pass + else: + #New Play session, take stake value from the user input + self.stake = self.init_stake = float(values['_SK_']) - if self.paused: - self.paused = False - xbtn_visible = await self.close_btn_handle.is_visible() - if xbtn_visible: - try: - await self.close_btn_handle.click(timeout=2000) - except PWTimeoutError as e: - pass - else: - #New Play session, take stake value from the user input - self.stake = self.init_stake = float(values['_SK_']) + self.mtng = float(values['_MG_']) + self.stop_loss = float(values['_SL_']) + self.stop_profit = float(values['_SP_']) + self.ldp = int(values['_LDP_']) - self.mtng = float(values['_MG_']) - self.stop_loss = float(values['_SL_']) - self.stop_profit = float(values['_SP_']) - self.ldp = int(values['_LDP_']) spot_balance_span = self.page.locator("#spot") stake_input = self.page.locator("#amount") diff --git a/trade.py b/trade.py index 111ff1b..419e729 100644 --- a/trade.py +++ b/trade.py @@ -85,7 +85,6 @@ async def ui(window, trade_session): window.close() - async def close_window(loop, signal=None): if signal: logging.info(f"Received exit signal {signal.name}...") @@ -130,7 +129,7 @@ def main(): sg.theme('DarkAmber') layout = [ - [sg.Text(size=(30,1), key='_MESSAGE_')], + [sg.Text(size=(60,1), key='_MESSAGE_')], [sg.Text('Email'), sg.Input(size=(24,1), k='_EMAIL_'), sg.Text(size=(7,1)), sg.Text('Password'), sg.Input(size=(24,1), k='_PWORD_')], @@ -181,6 +180,7 @@ def main(): try: loop.create_task(ui(window, trade_session)) loop.create_task(bg(window, trade_session)) + loop.create_task(trade_session.flush_context(window, loop)) loop.run_forever() finally: loop.close() From c6e8719a94c71819aec5c7d86e89db6280f78815 Mon Sep 17 00:00:00 2001 From: elkd Date: Fri, 8 Apr 2022 17:24:35 +0800 Subject: [PATCH 4/4] improved the restarting of the browser context --- session.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/session.py b/session.py index 911ed52..42696c2 100644 --- a/session.py +++ b/session.py @@ -44,16 +44,16 @@ async def setup(self, window, flush=False): self.playwright = await async_playwright().start() self.browser = await self.playwright.chromium.launch(headless=False) - if os.path.isfile(state): - # Create a new context with the saved storage state. - self.context = await self.browser.new_context(storage_state=state) - self.page = await self.context.new_page() - else: + if not os.path.isfile(state): window['_MESSAGE_'].update('Please Login First!') self.context = await self.browser.new_context() - self.page = await self.context.new_page() + else: + _context = await self.browser.new_context(storage_state=state) + if flush: await self.context.close() + self.context = _context try: + self.page = await self.context.new_page() await self.page.goto("https://smarttrader.deriv.com/") except PWTimeoutError as e: logging.info('The page is taking long to load please wait') @@ -83,8 +83,6 @@ async def flush_context(self, window, loop): self.loop = False await self.page.close() - await self.context.close() - #await self.browser.close() await self.setup(window, flush=True)