def make_trade_decision(): session = create_session() max_retries = 1 # Set the maximum number of retries for attempt in range(max_retries): try: logger.info("Starting trade decision process...") async_log('SYSTEM', "Starting trade decision process...") # Copy order_decision to previous_decision at the beginning of the function decisions_to_update = session.query(AlgoDecision).all() for decision in decisions_to_update: decision.previous_decision = decision.order_decision # Commit the changes to save the previous decisions logger.info("Trade decisions copied successfully.") # Calculate and update profit or loss for active positions active_positions = session.query(ActivePosition).all() for position in active_positions: if position.pos_side == 'long': profit = position.current_price - position.open_avg_px else: # 'short' profit = position.open_avg_px - position.current_price profit_percentage = (profit / position.open_avg_px) * 100 position.current_profit_percentage = profit_percentage position.profit_loss = 'profit' if profit > 0 else 'loss' async_log(position.id, f"Updated position {position.id}: Profit/Loss = {position.profit_loss}, Profit Percentage = {position.current_profit_percentage}%") # Calculate the general trend of the market total_decisions = session.query(func.count(AlgoDecision.id)).scalar() long_decisions = session.query(func.count(AlgoDecision.id)).filter(AlgoDecision.ctf_bull == True).scalar() short_decisions = session.query(func.count(AlgoDecision.id)).filter(AlgoDecision.ctf_bull == False).scalar() if total_decisions > 0: long_percentage = (long_decisions / total_decisions) * 100 short_percentage = (short_decisions / total_decisions) * 100 async_log('SYSTEM', f"Market trend: {long_percentage}% LONG, {short_percentage}% SHORT") gen_trend_algo = session.query(Algo).filter_by(algo_name='gen_trend', active=True).first() if gen_trend_algo: if long_percentage > 63: gen_trend_algo.value = 'BULL' elif short_percentage > 63: gen_trend_algo.value = 'BEAR' else: gen_trend_algo.value = 'INDETERMINATE' trend_status = gen_trend_algo.value async_log('DECISION', f"General trend updated to {trend_status}") # Check the active algos from the algo table active_algos = session.query(Algo).filter_by(active=True).all() active_algo_names = {algo.algo_name for algo in active_algos} # Apply ctf and htf checks first and get a preliminary decision of LONG or SHORT for decision in decisions_to_update: # Store the original order_decision to compare later original_order_decision = decision.order_decision async_log('SYSTEM', f"Making preliminary decision for instId {decision.instId}...") preliminary_decision = None # This will hold the preliminary decision if 'htf_check' in active_algo_names: if decision.htf_bull and not decision.htf_bear: preliminary_decision = 'LONG' elif not decision.htf_bull and decision.htf_bear: preliminary_decision = 'SHORT' if 'ctf_check' in active_algo_names: if decision.ctf_bull and not decision.ctf_bear: preliminary_decision = 'LONG' elif not decision.ctf_bull and decision.ctf_bear: preliminary_decision = 'SHORT' decision.preliminary_decision = preliminary_decision async_log(decision.instId, f"Preliminary decision for instId {decision.instId} is {preliminary_decision}") # Process the preliminary decision through other algorithms to get the processed_decision processed_decision = preliminary_decision # Start with the preliminary decision thirty_minutes_ago = datetime.datetime.utcnow() - datetime.timedelta(minutes=30) last_active_sltp_order = session.query(SLTPOrders).filter( SLTPOrders.instId == decision.instId, SLTPOrders.active == True, SLTPOrders.timestamp >= thirty_minutes_ago ).order_by(SLTPOrders.timestamp.desc()).first() if last_active_sltp_order: if last_active_sltp_order.closure_method in ['sl', 'tp']: if last_active_sltp_order.order_side == decision.preliminary_decision.lower(): async_log(decision.instId, f"Last active order for {decision.instId} was closed with {last_active_sltp_order.closure_method.upper()}. Skipping.") processed_decision = 'SKIP' else: # New decision is to the opposite side, mark the SL/TP order as inactive last_active_sltp_order.active = False session.add(last_active_sltp_order) async_log(decision.instId, f"New decision is opposite to the last active SL/TP order. Marking the SL/TP order as inactive for {decision.instId}.") processed_decision = decision.preliminary_decision else: processed_decision = decision.preliminary_decision else: processed_decision = decision.preliminary_decision # If gen_trend is active, check the decision against that rule gen_trend_algo = session.query(Algo).filter_by(algo_name='gen_trend').first() if gen_trend_algo and gen_trend_algo.active: if gen_trend_algo.value == 'BULL' and decision.ctf_bear: async_log(decision.instId, f"Decision for {decision.instId} goes against the general trend. Skipping.") processed_decision = 'SKIP' elif gen_trend_algo.value == 'BEAR' and decision.ctf_bull: async_log(decision.instId, f"Decision for {decision.instId} goes against the general trend. Skipping.") processed_decision = 'SKIP' elif gen_trend_algo.value == 'INDETERMINATE': # General trend is INDETERMINATE, use the preliminary decision async_log(decision.instId, f"General trend is INDETERMINATE. Keeping the preliminary decision for {decision.instId}.") processed_decision = preliminary_decision # Query the skipnext_loss setting from the Algo table skipnext_loss_setting = session.query(Algo).filter_by(algo_name='skipnext_if_loss', active=True).first() # Check if skipnext_loss is active before proceeding if skipnext_loss_setting and skipnext_loss_setting.active: last_position = session.query(ActivePosition).filter_by(inst_id=decision.instId).order_by( ActivePosition.open_time.desc()).first() if last_position: if last_position.profit_loss == 'loss' and ( (last_position.pos_side == 'long' and processed_decision == 'SHORT') or ( last_position.pos_side == 'short' and processed_decision == 'LONG')): async_log(decision.instId, f"Current trade for {decision.instId} is at a loss and the new decision is opposite. Issuing a CLOSE decision.") processed_decision = 'CLOSE' elif last_position.profit_loss == 'profit' and ( (last_position.pos_side == 'long' and processed_decision == 'SHORT') or ( last_position.pos_side == 'short' and processed_decision == 'LONG')): async_log(decision.instId, f"Current trade for {decision.instId} is in profit and the new decision is opposite. Issuing a CLOSEREOPEN decision.") processed_decision = 'ORDERCLOSESHORT' if last_position.pos_side == 'long' else 'ORDERCLOSELONG' if processed_decision: existing_position_same_side = session.query(ActivePosition).filter_by( inst_id=decision.instId, pos_side=processed_decision.lower() ).first() if existing_position_same_side: async_log(decision.instId, f"Active position for {decision.instId} already exists on the same side as the decision. Skipping.") processed_decision = 'SKIP' else: # Handle the case where processed_decision is None async_log(decision.instId, "Processed decision is None. Cannot determine position side.") # You might want to set processed_decision to a default value or take some other action # Get the current time in UTC+3 tz_utc_plus_3 = timezone('Europe/Istanbul') current_time_utc_plus_3 = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone(tz_utc_plus_3) async_log('SYSTEM', f"Current UTC+3 time: {current_time_utc_plus_3}") # Check for trades to reopen reopen_pos_settings = session.query(Algo).filter_by(algo_name='reopen_pos', active=True).first() if reopen_pos_settings and reopen_pos_settings.min is not None and reopen_pos_settings.max is not None: min_time = current_time_utc_plus_3 - datetime.timedelta(hours=reopen_pos_settings.min) max_time = current_time_utc_plus_3 - datetime.timedelta(hours=reopen_pos_settings.max) async_log('SYSTEM', f"Checking for trades to reopen between {min_time} and {max_time}") for decision in decisions_to_update: # Query the last four CurrentTimeframeTrailerTrend values for the instId last_four_rtdata = session.query(RTData).filter_by(instId=decision.instId).order_by( RTData.time.desc()).limit(4).all() # Check if all four CurrentTimeframeTrailerTrend values are the same if len(last_four_rtdata) == 4 and len( set(r.CurrentTimeframeTrailerTrend for r in last_four_rtdata)) == 1: async_log('SYSTEM', f"Skipping reopening trade for {decision.instId} as the last four CurrentTimeframeTrailerTrend values are identical.") continue # Skip to the next decision # Check if there's no active position and the decision is not to SKIP active_position_exists = session.query(ActivePosition).filter_by( inst_id=decision.instId).first() if not active_position_exists and decision.order_decision not in ['SKIP', 'CLOSE', 'ORDERCLOSESHORT', 'ORDERCLOSELONG']: trade_exists = session.query(TradeLog).filter( TradeLog.symbol == decision.instId, TradeLog.close_time >= min_time, TradeLog.close_time <= max_time ).first() if not trade_exists: async_log('SYSTEM', f"Trade for {decision.instId} is eligible to be reopened.") decision.order_pending = True else: async_log('SYSTEM', f"Trade for {decision.instId} is not eligible to be reopened or already exists.") decision.order_pending = False # Implement the shutdown logic as the final step gen_shutdown_active = session.query(Algo).filter_by(algo_name='gen_shutdown', active=True).first() if gen_shutdown_active: # Check for the last position, but decision is SKIP regardless of whether it exists last_position = session.query(ActivePosition).filter_by(inst_id=decision.instId).order_by( ActivePosition.open_time.desc()).first() if last_position and last_position.pos_side != decision.preliminary_decision.lower(): # If there's a last position and it's on the opposite side, set decision to CLOSE decision.order_decision = 'CLOSE' decision.order_pending = 1 else: # In all other cases when gen_shutdown is active, set decision to SKIP decision.order_decision = 'SKIP' decision.order_pending = 0 else: # If gen_shutdown is not active, use the processed decision stored_processed_decision = decision.processed_decision if processed_decision == stored_processed_decision: # If the new decision is the same as the one stored in the database, issue a SKIP order async_log(decision.instId, f"Processed decision for instId {decision.instId} is the same as the stored decision. Setting order_decision to 'SKIP'.") decision.order_decision = 'SKIP' decision.order_pending = False else: # If the decision is different, update it in the database and set the decision async_log(decision.instId, f"New processed decision for instId {decision.instId} is '{processed_decision}', different from the stored decision '{stored_processed_decision}'. Updating decision.") decision.order_decision = processed_decision decision.order_pending = False if processed_decision == 'SKIP' else True sys_restart_algo = session.query(Algo).filter_by(algo_name='sys_restart').first() sys_restart_active = sys_restart_algo.active if sys_restart_algo else False if sys_restart_active: # If sys_restart is active, skip setting orders to pending decision.order_pending = False else: # If sys_restart is not active, proceed with setting orders to pending decision.order_pending = True if decision.order_decision != 'SKIP' else False # Update the processed_decision in the database regardless of the decision outcome decision.processed_decision = processed_decision decision.order_sent = False # You can add logic here if you want to handle this case differently if sys_restart_active: sys_restart_algo.active = False session.add(sys_restart_algo) async_log('SYSTEM', "System restart flag reset.") # Commit the changes session.commit() async_log('SYSTEM', "Trade decision process completed successfully.") break # Exit the loop if commit is successful except OperationalError as e: logger.error(f"An error occurred: {e}") session.rollback() if attempt < max_retries - 1: logger.info(f"Retrying transaction (Attempt {attempt + 1} of {max_retries})...") else: logger.error("Max retries reached. Giving up.") finally: session.close()