@@ -939,6 +939,29 @@ async def telegram_handle_message(update: Update, context: ContextTypes.DEFAULT_
939939 else :
940940 await send_with_keyboard (update , f"```\n Symbol Debug Info:\n { debug_text } \n ```" , parse_mode = 'Markdown' )
941941
942+ elif text == "⚡ Fast Check" :
943+ await send_with_keyboard (update , "⚡ Running fast momentum check for immediate opportunities..." )
944+
945+ try :
946+ fetch_usdc_balance ()
947+ investable = balance ['usd' ] - sum (float (tr .get ('Tax' , 0 )) for tr in trade_log [- 20 :] if float (tr .get ('Tax' , 0 )) > 0 )
948+
949+ if investable < 10 :
950+ await send_with_keyboard (update , "❌ Not enough USDC for fast investment check" )
951+ return
952+
953+ # Run fast momentum check
954+ start_time = time .time ()
955+ fast_momentum_invest (investable )
956+ end_time = time .time ()
957+
958+ processing_time = end_time - start_time
959+ await send_with_keyboard (update ,
960+ f"✅ Fast momentum check completed in { processing_time :.2f} seconds\n "
961+ f"💰 Checked ${ investable :.2f} USDC for immediate opportunities" )
962+ except Exception as e :
963+ await send_with_keyboard (update , f"❌ Fast check failed: { str (e )} " )
964+
942965 else :
943966 await send_with_keyboard (update , "Unknown action." )
944967
@@ -982,7 +1005,8 @@ def is_paused():
9821005 ["⏸ Pause Trading" , "▶️ Resume Trading" ],
9831006 ["📝 Trade Log" , "🔍 Diagnose" ],
9841007 ["🔄 WebSocket Status" , "⚡ Check Momentum" ],
985- ["🔍 Debug Symbols" , "📈 Status" ]
1008+ ["🔍 Debug Symbols" , "⚡ Fast Check" ],
1009+ ["📈 Status" ]
9861010]
9871011
9881012async def start_handler (update : Update , context : ContextTypes .DEFAULT_TYPE ):
@@ -1012,11 +1036,11 @@ def telegram_main():
10121036 application .add_handler (CommandHandler ('start' , start_handler ))
10131037 application .add_handler (MessageHandler (filters .TEXT & ~ filters .COMMAND , telegram_handle_message ))
10141038
1015- # Add periodic alarm job with more conservative timing to avoid trading loop conflicts
1039+ # Add periodic alarm job with faster momentum checking
10161040 application .job_queue .run_repeating (
10171041 alarm_job ,
1018- interval = 360 , # 6 minutes instead of 5 to reduce conflicts
1019- first = 30 , # First run after 30 seconds to let trading loop start
1042+ interval = 120 , # 2 minutes instead of 6 for faster response
1043+ first = 15 , # First run after 15 seconds for quicker startup
10201044 name = "momentum_checker"
10211045 )
10221046
@@ -1446,15 +1470,81 @@ def position_profit(sym):
14461470 print ("[TAXES] Not enough USDC to invest after reserving for taxes." )
14471471 return
14481472
1449- invest_momentum_with_usdc_limit (investable_usdc )
1473+ # FAST CHECK: Look for immediate momentum opportunities
1474+ fast_momentum_invest (investable_usdc )
1475+
1476+ def fast_momentum_invest (usdc_limit ):
1477+ """
1478+ Fast momentum check that runs investment logic immediately when strong signals are detected.
1479+ This bypasses the normal periodic checks for ultra-fast response.
1480+ """
1481+ print (f"[FAST CHECK] Looking for immediate momentum opportunities with ${ usdc_limit :.2f} " )
1482+
1483+ symbols = get_yaml_ranked_momentum (limit = 5 ) # Check top 5 instead of 10 for speed
1484+
1485+ for symbol in symbols :
1486+ try :
1487+ # Quick momentum check
1488+ d1 , d5 , d15 = dynamic_momentum_set (symbol )
1489+ if not has_recent_momentum (symbol , d1 , d5 , d15 ):
1490+ continue
1491+
1492+ # Fast tradability check
1493+ ok , reason , diag = guard_tradability (symbol , side = "BUY" )
1494+ if not ok :
1495+ print (f"[FAST SKIP] { symbol } : { reason } " )
1496+ continue
1497+
1498+ # Check if we have enough funds
1499+ min_notional = min_notional_for (symbol )
1500+ if usdc_limit < min_notional :
1501+ print (f"[FAST SKIP] { symbol } : Need ${ min_notional :.2f} , have ${ usdc_limit :.2f} " )
1502+ break
1503+
1504+ # Execute immediate buy
1505+ print (f"[FAST BUY] { symbol } : Strong momentum detected, buying immediately!" )
1506+ result = buy (symbol , amount = min_notional )
1507+
1508+ if result :
1509+ usdc_limit -= min_notional
1510+ print (f"[FAST SUCCESS] { symbol } : Bought ${ min_notional :.2f} , remaining: ${ usdc_limit :.2f} " )
1511+
1512+ # Send fast alert
1513+ try :
1514+ asyncio .create_task (send_alarm_message (
1515+ f"⚡ FAST MOMENTUM BUY ⚡\n \n "
1516+ f"📊 { symbol } \n "
1517+ f"💵 Amount: ${ min_notional :.2f} \n "
1518+ f"🚀 Reason: Strong 3-timeframe momentum\n "
1519+ f"⏱️ Response: Ultra-fast execution"
1520+ ))
1521+ except Exception as e :
1522+ print (f"[FAST ALERT ERROR] { e } " )
1523+ else :
1524+ print (f"[FAST FAIL] { symbol } : Buy failed" )
1525+
1526+ except Exception as e :
1527+ print (f"[FAST ERROR] { symbol } : { e } " )
1528+ continue
1529+
1530+ # If we still have funds, run normal investment logic
1531+ if usdc_limit >= 10 :
1532+ invest_momentum_with_usdc_limit (usdc_limit )
14501533
14511534def load_symbol_stats ():
1535+ """Load symbol statistics from YAML file, filtered to allowed symbols only."""
14521536 try :
14531537 with open (YAML_SYMBOLS_FILE , "r" ) as f :
1454- return yaml .safe_load (f )
1538+ all_stats = yaml .safe_load (f )
1539+ # Filter to only allowed symbols
1540+ filtered_stats = {k : v for k , v in all_stats .items () if k in ALLOWED_SYMBOLS }
1541+ print (f"[YAML] Loaded { len (filtered_stats )} allowed symbols from { len (all_stats )} total" )
1542+ return filtered_stats
14551543 except Exception as e :
14561544 print (f"[YAML ERROR] Could not read { YAML_SYMBOLS_FILE } : { e } " )
1457- return {}
1545+ # Return minimal stats for allowed symbols if YAML fails
1546+ return {symbol : {"market_cap" : 1000000 , "volume_1d" : 1000000 , "volatility" : {"1d" : 0.01 }}
1547+ for symbol in ALLOWED_SYMBOLS }
14581548
14591549def pct_change (klines ):
14601550 if len (klines ) < 2 : return 0
@@ -1653,17 +1743,17 @@ def dynamic_momentum_threshold(symbol, interval='1m', lookback=60,
16531743
16541744def dynamic_momentum_set (symbol ):
16551745 """
1656- Produce per-interval dynamic thresholds tuned for micro-scalping.
1657- We make shorter TFs a bit more sensitive (lower k), longer TFs stricter (higher k) .
1746+ Produce per-interval dynamic thresholds tuned for faster micro-scalping.
1747+ We make shorter TFs more sensitive (lower k), longer TFs slightly stricter .
16581748 """
1659- thr_1m = dynamic_momentum_threshold (symbol , '1m' , lookback = 60 , k = 0.55 , floor_ = 0.0007 , cap_ = 0.015 )
1660- thr_5m = dynamic_momentum_threshold (symbol , '5m' , lookback = 48 , k = 0.70 , floor_ = 0.0010 , cap_ = 0.018 )
1661- thr_15m = dynamic_momentum_threshold (symbol , '15m' , lookback = 48 , k = 0.85 , floor_ = 0.0015 , cap_ = 0.020 )
1749+ thr_1m = dynamic_momentum_threshold (symbol , '1m' , lookback = 60 , k = 0.35 , floor_ = 0.0005 , cap_ = 0.012 ) # More sensitive
1750+ thr_5m = dynamic_momentum_threshold (symbol , '5m' , lookback = 48 , k = 0.50 , floor_ = 0.0008 , cap_ = 0.015 ) # More sensitive
1751+ thr_15m = dynamic_momentum_threshold (symbol , '15m' , lookback = 48 , k = 0.65 , floor_ = 0.0012 , cap_ = 0.018 ) # More sensitive
16621752 return thr_1m , thr_5m , thr_15m
16631753
16641754
16651755def has_recent_momentum (symbol , min_1m = None , min_5m = None , min_15m = None ):
1666- """Enhanced momentum detection using multiple indicators with dynamic thresholds ."""
1756+ """Enhanced momentum detection using multiple indicators with faster scoring system ."""
16671757 try :
16681758 # derive dynamic thresholds if not provided
16691759 if min_1m is None or min_5m is None or min_15m is None :
@@ -1685,8 +1775,39 @@ def has_recent_momentum(symbol, min_1m=None, min_5m=None, min_15m=None):
16851775 f"RSI: { indicators_15m .get ('rsi' , 0 ):.1f} Vol: { indicators_15m .get ('volume_spike' , False )} "
16861776 f"MA: { indicators_15m .get ('ma_cross' , False )} " )
16871777
1688- # Keep your strong confirmation rule (all TFs) for now; you can relax if fills are sparse
1689- return momentum_1m and momentum_5m and momentum_15m
1778+ # NEW: Weighted scoring system for faster entry
1779+ # Score each timeframe (0-3 points each)
1780+ score_1m = sum ([
1781+ indicators_1m .get ('price_momentum' , False ), # 1 point
1782+ indicators_1m .get ('volume_spike' , False ), # 1 point
1783+ indicators_1m .get ('rsi_momentum' , False ) or indicators_1m .get ('ma_cross' , False ) # 1 point
1784+ ])
1785+
1786+ score_5m = sum ([
1787+ indicators_5m .get ('price_momentum' , False ), # 1 point
1788+ indicators_5m .get ('volume_spike' , False ), # 1 point
1789+ indicators_5m .get ('rsi_momentum' , False ) or indicators_5m .get ('ma_cross' , False ) # 1 point
1790+ ])
1791+
1792+ score_15m = sum ([
1793+ indicators_15m .get ('price_momentum' , False ), # 1 point
1794+ indicators_15m .get ('volume_spike' , False ), # 1 point
1795+ indicators_15m .get ('rsi_momentum' , False ) or indicators_15m .get ('ma_cross' , False ) # 1 point
1796+ ])
1797+
1798+ total_score = score_1m + score_5m + score_15m
1799+
1800+ print (f"[MOMENTUM SCORE] { symbol } : 1m={ score_1m } /3, 5m={ score_5m } /3, 15m={ score_15m } /3, Total={ total_score } /9" )
1801+
1802+ # FAST MODE: Require at least 6/9 points with 1m showing strong signal
1803+ # This allows investment when momentum is building, not just when perfect
1804+ fast_entry = (total_score >= 6 and score_1m >= 2 )
1805+
1806+ # CONSERVATIVE MODE: Original requirement (all timeframes perfect)
1807+ conservative_entry = momentum_1m and momentum_5m and momentum_15m
1808+
1809+ # Use FAST MODE for quicker entries
1810+ return fast_entry
16901811
16911812 except Exception as e :
16921813 print (f"[MOMENTUM ERROR] { symbol } : { e } " )
@@ -1777,17 +1898,13 @@ def get_yaml_ranked_momentum(
17771898 )
17781899 return [x ["symbol" ] for x in ranked [:limit ]]
17791900
1780- def refresh_symbols ():
1781- global SYMBOLS
1782- SYMBOLS = get_yaml_ranked_momentum (limit = 10 )
1783-
17841901def invest_momentum_with_usdc_limit (usdc_limit ):
17851902 """
17861903 Invest in as many eligible momentum symbols as possible, always using the min_notional per symbol,
17871904 never all-or-nothing. Any remaining funds are left in USDC.
17881905 This version treats coins you own (including dust) and coins you don't equally.
17891906 """
1790- refresh_symbols ()
1907+ # Get symbols directly without redundant refresh_symbols() call
17911908 symbols = get_yaml_ranked_momentum (limit = 10 )
17921909 print (f"[DEBUG] Momentum symbols eligible for investment: { symbols } " )
17931910 if not symbols :
@@ -2045,8 +2162,8 @@ def process_actions():
20452162def trading_loop ():
20462163 last_sync = time .time ()
20472164 last_websocket_update = time .time ()
2048- SYNC_INTERVAL = 240 # Increased from 180 to 240 seconds (4 minutes)
2049- WEBSOCKET_UPDATE_INTERVAL = 600 # Increased to 10 minutes
2165+ SYNC_INTERVAL = 60 # Reduced from 240 to 60 seconds (1 minute) for faster response
2166+ WEBSOCKET_UPDATE_INTERVAL = 300 # Reduced to 5 minutes
20502167
20512168 # Initialize WebSocket monitoring
20522169 initialize_websocket_monitoring ()
@@ -2410,7 +2527,8 @@ async def send_quick_alerts():
24102527 # Add performance monitoring to reduce scheduler warning noise
24112528 add_performance_monitoring ()
24122529
2413- refresh_symbols ()
2530+ # Load trade history and rebuild positions - no need for refresh_symbols()
2531+ # since get_yaml_ranked_momentum() loads symbols dynamically when needed
24142532 trade_log = load_trade_history ()
24152533 positions .clear ()
24162534 positions .update (rebuild_cost_basis (trade_log ))
0 commit comments