diff --git a/requirements.txt b/requirements.txt index 2e70e2e1..9ba2bc20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,10 +21,10 @@ eth-account==0.11.0 web3==6.20.1 # ========== Data & Analysis ========== -pandas==2.1.4 -numpy==1.26.2 -pandas-ta==0.3.14b0 -TA-Lib==0.4.32 # Technical indicators +pandas>=2.3.2 +numpy==2.2.6 +pandas-ta==0.4.71b0 +# TA-Lib==0.4.32 # Technical indicators Backtesting==0.3.3 # Backtesting framework yfinance==0.2.43 # For fetching Yahoo Finance data diff --git a/spec/constitution.md b/spec/constitution.md new file mode 100644 index 00000000..776a88b9 --- /dev/null +++ b/spec/constitution.md @@ -0,0 +1,8 @@ +# Constitution for Low-Latency Spec-Driven Trading Agents + +## Core Principles + +1. **Speed Over Sentiment:** All agent decisions must prioritize signal latency over sentiment analysis for trade horizons under 1-hour. +2. **Quantitative Rigor:** Specifications must be defined as "Market Micro-Structure Exploits" rather than "User Stories." +3. **Specialized Roles:** Agents will have narrowly defined roles to ensure low-latency, specialized processing. +4. **Traceability:** All trading decisions must be traceable back to a specific specification. diff --git a/src/data/tick_collector.py b/src/data/tick_collector.py new file mode 100644 index 00000000..3253b24d --- /dev/null +++ b/src/data/tick_collector.py @@ -0,0 +1,73 @@ +""" +Tick Data Collector + +This script connects to the HyperLiquid WebSocket API to collect real-time tick data. +""" + +import websocket +import json +import threading + +class TickCollector: + def __init__(self, symbol): + self.symbol = symbol + self.ws = None + self.latest_tick = None + self.is_connected = False + websocket.enableTrace(True) + + def on_message(self, ws, message): + self.latest_tick = json.loads(message) + print(f"New tick received: {self.latest_tick}") + + def on_error(self, ws, error): + print(f"WebSocket Error: {error}") + self.is_connected = False + + def on_close(self, ws, close_status_code, close_msg): + print("### WebSocket closed ###") + self.is_connected = False + + def on_open(self, ws): + print("### WebSocket opened ###") + self.is_connected = True + self.ws.send(json.dumps({ + "method": "subscribe", + "subscription": {"type": "trades", "coin": self.symbol} + })) + + def start(self): + """Starts the WebSocket connection in a new thread.""" + self.ws = websocket.WebSocketApp("wss://api.hyperliquid.xyz/ws", + on_message=self.on_message, + on_error=self.on_error, + on_close=self.on_close) + self.ws.on_open = self.on_open + + # Run the WebSocket in a separate thread to avoid blocking + wst = threading.Thread(target=self.ws.run_forever) + wst.daemon = True + wst.start() + + def get_latest_tick(self): + """Returns the latest tick received from the WebSocket.""" + return self.latest_tick + + def stop(self): + """Closes the WebSocket connection.""" + if self.ws: + self.ws.close() + self.is_connected = False + print("WebSocket connection closed.") + +if __name__ == "__main__": + # Example usage + collector = TickCollector(symbol='BTC') + collector.start() + + # Keep the main thread alive to see the ticks coming in + try: + while True: + pass + except KeyboardInterrupt: + collector.stop() diff --git a/src/spec_driven_agents/async_swarm_agent.py b/src/spec_driven_agents/async_swarm_agent.py new file mode 100644 index 00000000..973a3102 --- /dev/null +++ b/src/spec_driven_agents/async_swarm_agent.py @@ -0,0 +1,28 @@ +""" +Asynchronous Swarm Agent + +This agent is responsible for querying multiple AI models asynchronously. +""" + +import asyncio +import aiohttp + +class AsyncSwarmAgent: + def __init__(self, model_urls): + self.model_urls = model_urls + + async def fetch(self, session, url, prompt): + """ + Fetches a response from a single AI model. + """ + async with session.post(url, json={'prompt': prompt}) as response: + return await response.json() + + async def query(self, prompt): + """ + Queries all AI models asynchronously and returns their responses. + """ + async with aiohttp.ClientSession() as session: + tasks = [self.fetch(session, url, prompt) for url in self.model_urls] + responses = await asyncio.gather(*tasks) + return responses diff --git a/src/spec_driven_agents/decision_agent.py b/src/spec_driven_agents/decision_agent.py new file mode 100644 index 00000000..19e1c486 --- /dev/null +++ b/src/spec_driven_agents/decision_agent.py @@ -0,0 +1,28 @@ +""" +Decision Agent + +This agent is responsible for aggregating signals from other specialized agents +and making a final trading decision. +""" + +from src.models.model_factory import model_factory + +class DecisionAgent: + def __init__(self, model_type='gemini', model_name='gemini-flash'): + self.model = model_factory.get_model(model_type, model_name) + + def make_decision(self, indicator_signals, pattern_analysis): + """ + Makes a trading decision based on the provided signals. + """ + prompt = f""" + Given the following signals, make a trading decision (BUY, SELL, or HOLD). + + Indicator Signals: + {indicator_signals} + + Pattern Analysis: + {pattern_analysis} + """ + response = self.model.generate_response(prompt) + return response diff --git a/src/spec_driven_agents/drl_agent.py b/src/spec_driven_agents/drl_agent.py new file mode 100644 index 00000000..d4ecd759 --- /dev/null +++ b/src/spec_driven_agents/drl_agent.py @@ -0,0 +1,22 @@ +""" +Deep Reinforcement Learning (DRL) Agent + +This agent will be responsible for learning and executing trading strategies using DRL. +This is a placeholder for now and will be implemented in a future iteration. +""" + +class DRLAgent: + def __init__(self): + pass + + def train(self): + """ + Trains the DRL agent. + """ + pass + + def predict(self, state): + """ + Makes a prediction based on the current state. + """ + pass diff --git a/src/spec_driven_agents/indicator_agent.py b/src/spec_driven_agents/indicator_agent.py new file mode 100644 index 00000000..bc12eb1c --- /dev/null +++ b/src/spec_driven_agents/indicator_agent.py @@ -0,0 +1,29 @@ +""" +Indicator Agent + +This agent is responsible for calculating low-latency technical indicators. +""" + +import pandas as pd +import pandas_ta as ta + +class IndicatorAgent: + def __init__(self): + pass + + def calculate_rsi(self, data, period=14): + """Calculates the Relative Strength Index (RSI)""" + return ta.rsi(data['close'], length=period) + + def calculate_rate_of_change(self, data, period=14): + """Calculates the Rate of Change (ROC)""" + roc = ((data['close'] - data['close'].shift(period)) / data['close'].shift(period)) * 100 + return roc + + def run(self, data): + """Calculates all indicators and returns them as a dictionary""" + indicators = { + 'rsi': self.calculate_rsi(data), + 'roc': self.calculate_rate_of_change(data) + } + return indicators diff --git a/src/spec_driven_agents/lob_agent.py b/src/spec_driven_agents/lob_agent.py new file mode 100644 index 00000000..10b9c41f --- /dev/null +++ b/src/spec_driven_agents/lob_agent.py @@ -0,0 +1,42 @@ +""" +LOB (Limit Order Book) Agent + +This agent is responsible for managing trade-level risk by analyzing the Limit Order Book. +""" + +from src.nice_funcs_hyperliquid import ask_bid + +class LOBAgent: + def __init__(self): + pass + + def get_order_book(self, symbol): + """ + Fetches the Limit Order Book for a given symbol. + """ + ask, bid, l2_data = ask_bid(symbol) + return l2_data + + def analyze_order_book_imbalance(self, order_book): + """ + Analyzes the order book to identify imbalances. + """ + bids = order_book[0] + asks = order_book[1] + + total_bid_volume = sum([float(bid['n']) for bid in bids]) + total_ask_volume = sum([float(ask['n']) for ask in asks]) + + if total_bid_volume == 0: + return 0 + + imbalance = (total_bid_volume - total_ask_volume) / total_bid_volume + return imbalance + + def run(self, symbol): + """ + Fetches and analyzes the order book for a given symbol. + """ + order_book = self.get_order_book(symbol) + imbalance = self.analyze_order_book_imbalance(order_book) + return imbalance diff --git a/src/spec_driven_agents/pattern_agent.py b/src/spec_driven_agents/pattern_agent.py new file mode 100644 index 00000000..b042f91f --- /dev/null +++ b/src/spec_driven_agents/pattern_agent.py @@ -0,0 +1,27 @@ +""" +Pattern Agent + +This agent is responsible for analyzing short-term candlestick patterns. +""" + +import pandas as pd +from src.models.model_factory import model_factory + +class PatternAgent: + def __init__(self, model_type='openai', model_name=None): + self.model = model_factory.get_model(model_type, model_name) + + def analyze_patterns(self, data): + """ + Analyzes candlestick patterns using a multimodal LLM. + """ + # For now, we'll use a simplified approach. + # In the future, this will be replaced with a multimodal LLM. + prompt = f""" + Analyze the following candlestick data and identify any significant patterns. + Return a JSON object with the identified patterns and a confidence score. + + {data.to_string()} + """ + response = self.model.generate_response(system_prompt="", user_content=prompt) + return response diff --git a/src/spec_driven_agents/test_agents.py b/src/spec_driven_agents/test_agents.py new file mode 100644 index 00000000..d126f444 --- /dev/null +++ b/src/spec_driven_agents/test_agents.py @@ -0,0 +1,21 @@ +""" +Tests for the spec-driven agents. +""" + +import pandas as pd +from src.spec_driven_agents.indicator_agent import IndicatorAgent + +def test_rsi_calculation(): + """ + Tests that the RSI is calculated correctly. + """ + data = { + 'close': [ + 44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, + 45.89, 46.03, 45.61, 46.28, 46.28, 46.00, 46.03, 46.41, 46.22, 45.64 + ] + } + df = pd.DataFrame(data) + agent = IndicatorAgent() + rsi = agent.calculate_rsi(df, period=14) + assert round(rsi.iloc[-1], 2) == 43.20 diff --git a/src/spec_driven_main.py b/src/spec_driven_main.py new file mode 100644 index 00000000..cf10f5ce --- /dev/null +++ b/src/spec_driven_main.py @@ -0,0 +1,100 @@ +""" +Main entry point for the new Spec-Driven Trading Agent System. +""" +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import asyncio +from termcolor import cprint +import pandas as pd +import time + +# Import the new specialized agents +from src.spec_driven_agents.indicator_agent import IndicatorAgent +from src.spec_driven_agents.pattern_agent import PatternAgent +from src.spec_driven_agents.lob_agent import LOBAgent +from src.spec_driven_agents.decision_agent import DecisionAgent +from src.data.tick_collector import TickCollector + +class SpecDrivenTrader: + def __init__(self, symbol='BTC'): + self.symbol = symbol + # Instantiate agents + self.indicator_agent = IndicatorAgent() + self.pattern_agent = PatternAgent() + self.lob_agent = LOBAgent() + self.decision_agent = DecisionAgent() + self.tick_collector = TickCollector(self.symbol) + self.market_data = pd.DataFrame(columns=['open', 'high', 'low', 'close', 'volume']) + + cprint("🚀 Spec-Driven Trading System Initialized!", "green") + cprint(f"🎯 Trading Symbol: {self.symbol}", "cyan") + + async def run_cycle(self): + """ + Runs a single trading decision cycle. + """ + cprint("\n" + "="*50, "yellow") + cprint(f"🕯️ Starting new trading cycle for {self.symbol}...", "yellow") + + # 1. Get real-time data + cprint("1. Fetching Market Data...", "cyan") + tick = self.tick_collector.get_latest_tick() + + if tick and 'data' in tick and tick['data']: + # For now, let's just append the tick to our market data + # In a real system, we'd aggregate ticks into bars + trade = tick['data'][0] + new_row = {'open': float(trade['px']), 'high': float(trade['px']), 'low': float(trade['px']), 'close': float(trade['px']), 'volume': float(trade['sz'])} + self.market_data = self.market_data._append(new_row, ignore_index=True) + cprint(" ✅ Got market data.", "green") + else: + cprint(" ⚠️ No new market data.", "yellow") + return + + # 2. Run specialized agents to get signals + cprint("2. Analyzing signals from specialized agents...", "cyan") + if len(self.market_data) > 14: + indicator_signals = self.indicator_agent.run(self.market_data) + if indicator_signals and 'rsi' in indicator_signals and not indicator_signals['rsi'].empty: + cprint(f" - Indicator Agent Signals: {indicator_signals['rsi'].iloc[-1]:.2f} RSI", "white") + else: + cprint(" - Not enough data to calculate indicators.", "yellow") + indicator_signals = None + + pattern_analysis = self.pattern_agent.analyze_patterns(self.market_data) + cprint(f" - Pattern Agent Analysis: {pattern_analysis}", "white") + + lob_imbalance = self.lob_agent.run(self.symbol) + cprint(f" - LOB Agent Imbalance: {lob_imbalance:.2f}", "white") + + # 3. Aggregate signals and make a decision + cprint("3. Aggregating signals and making a decision...", "cyan") + final_decision = self.decision_agent.make_decision( + indicator_signals, + pattern_analysis + ) + cprint(f" ✅ Decision Agent's final call: {final_decision}", "green", attrs=['bold']) + + cprint("="*50 + "\n", "yellow") + + +async def main(): + trader = SpecDrivenTrader(symbol='BTC') + trader.tick_collector.start() + + # Wait for the WebSocket to connect + while not trader.tick_collector.is_connected: + await asyncio.sleep(1) + + # Run the trading cycle every 5 seconds + while True: + await trader.run_cycle() + await asyncio.sleep(5) + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("Shutting down...")