Repository navigation
Expand file tree
/
Copy pathlive_bot.py
More file actions
209 lines (176 loc) · 8.69 KB
/
Copy pathlive_bot.py
File metadata and controls
209 lines (176 loc) · 8.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import time
import pandas as pd
import threading # We need threading
import os
from dotenv import load_dotenv
from alpha_vantage.timeseries import TimeSeries
import alpaca_trade_api as tradeapi
# Import our Alpaca functions
import alpaca_trader as trader
# --- Load API Keys ---
# (Keep the key loading part the same)
load_dotenv()
av_api_key = os.getenv('ALPHA_VANTAGE_KEY')
apca_api_key = os.getenv('APCA_API_KEY_ID')
apca_secret_key = os.getenv('APCA_API_SECRET_KEY')
if not all([av_api_key, apca_api_key, apca_secret_key]):
print("Error: Missing one or more API keys in .env file.")
# In a real app, handle this more gracefully than exit()
# For now, we'll let the api.py handle the check.
# --- Trading Bot Class ---
class TradingBot:
def __init__(self, ticker, short_window=20, long_window=50, check_interval=60):
self.ticker = ticker.upper()
self.short_window = short_window
self.long_window = long_window
self.check_interval = check_interval
self._is_running = False
self._thread = None
self.status = "Idle" # Give the bot an initial status
# Initialize API clients within the class
try:
self.ts = TimeSeries(key=av_api_key, output_format='pandas')
# Use the already initialized api object from alpaca_trader
self.alpaca_api = trader.api
if not self.alpaca_api:
raise ConnectionError("Alpaca API not initialized in alpaca_trader.")
self.status = "Initialized"
print(f"TradingBot initialized for {self.ticker}")
except Exception as e:
self.status = f"Error initializing APIs: {e}"
print(self.status)
# Prevent starting if APIs fail
self.ts = None
self.alpaca_api = None
def _fetch_latest_data(self):
"""Internal method to fetch data."""
if not self.ts: return None # Don't try if API init failed
print(f"[{self.ticker}] Fetching latest data...")
try:
# Using daily data as a more reliable free source
data, _ = self.ts.get_daily(symbol=self.ticker, outputsize='compact')
data.sort_index(ascending=True, inplace=True)
data.rename(columns={'1. open': 'open', '2. high': 'high', '3. low': 'low', '4. close': 'close', '5. volume': 'volume'}, inplace=True)
if len(data) < self.long_window:
print(f"[{self.ticker}] Warning: Not enough data ({len(data)} points) for {self.long_window}-day SMA. Fetching more...")
data, _ = self.ts.get_daily(symbol=self.ticker, outputsize='full')
data.sort_index(ascending=True, inplace=True)
data.rename(columns={'1. open': 'open', '2. high': 'high', '3. low': 'low', '4. close': 'close', '5. volume': 'volume'}, inplace=True)
if len(data) < self.long_window:
self.status = f"Error: Still not enough data ({len(data)} points)."
print(f"[{self.ticker}] {self.status}")
return None
return data.tail(self.long_window + 5) # Return recent data
except Exception as e:
self.status = f"Error fetching AV data: {e}"
print(f"[{self.ticker}] {self.status}")
return None
def _run_strategy_check(self):
"""Internal method containing the core strategy logic."""
if not self.alpaca_api:
self.status = "Alpaca API unavailable. Stopping."
print(f"[{self.ticker}] {self.status}")
self._is_running = False # Stop the loop if API is bad
return
self.status = "Running strategy check..."
print(f"\n[{self.ticker}] --- Running Strategy Check ---")
df = self._fetch_latest_data()
if df is None or df.empty:
self.status = "Could not get data. Skipping check."
print(f"[{self.ticker}] {self.status}")
return
# Calculate Indicators
df['SMA_Short'] = df['close'].rolling(window=self.short_window).mean()
df['SMA_Long'] = df['close'].rolling(window=self.long_window).mean()
# Check if enough data for SMAs
if df['SMA_Short'].isna().iloc[-2] or df['SMA_Long'].isna().iloc[-2]:
self.status = "Not enough data for SMA calculation yet. Waiting..."
print(f"[{self.ticker}] {self.status}")
return
last_short = df['SMA_Short'].iloc[-1]
last_long = df['SMA_Long'].iloc[-1]
prev_short = df['SMA_Short'].iloc[-2]
prev_long = df['SMA_Long'].iloc[-2]
print(f"[{self.ticker}] Latest SMAs - Short: {last_short:.2f}, Long: {last_long:.2f}")
# Determine Signal
signal = 'HOLD'
if prev_short <= prev_long and last_short > last_long:
signal = 'BUY'
elif prev_short >= prev_long and last_short < last_long:
signal = 'SELL'
print(f"[{self.ticker}] Signal: {signal}")
self.status = f"Signal: {signal}"
# Get Position & Execute Trade
current_shares = trader.get_current_position(self.ticker) # Use function from trader module
if signal == 'BUY' and current_shares == 0.0:
account_info = trader.get_account_info()
if account_info and float(account_info.cash) > 100:
cash_to_use = float(account_info.cash) * 0.90
last_price = df['close'].iloc[-1]
qty_to_buy = int(cash_to_use // last_price)
if qty_to_buy > 0:
self.status = f"Placing BUY order for {qty_to_buy} shares..."
trader.place_order(self.ticker, qty_to_buy, 'buy')
else: self.status = "Not enough cash for 1 share."; print(f"[{self.ticker}] {self.status}")
else: self.status = "Not enough cash to buy."; print(f"[{self.ticker}] {self.status}")
elif signal == 'SELL' and current_shares > 0.0:
self.status = f"Placing SELL order for {int(current_shares)} shares..."
trader.place_order(self.ticker, int(current_shares), 'sell')
else:
self.status = f"Holding position ({current_shares} shares)."
print(f"[{self.ticker}] {self.status}")
# Update status after potential trade placement attempt
# (A more robust version might check order status here)
def _loop(self):
"""The main loop that runs in a separate thread."""
print(f"[{self.ticker}] Bot loop started.")
while self._is_running:
try:
# Check market hours here in a real bot
self._run_strategy_check()
# Wait for the next interval, checking periodically if we need to stop
for _ in range(self.check_interval):
if not self._is_running: break
time.sleep(1)
except Exception as e:
self.status = f"Error in loop: {e}"
print(f"[{self.ticker}] {self.status}")
# Decide if error is critical and should stop the bot
# For now, we continue after a pause
time.sleep(self.check_interval)
print(f"[{self.ticker}] Bot loop stopped.")
self.status = "Stopped"
def start(self):
"""Starts the bot loop in a new thread."""
if self._is_running:
print(f"[{self.ticker}] Bot is already running.")
return False
if not self.alpaca_api or not self.ts:
print(f"[{self.ticker}] Cannot start bot due to API initialization error.")
return False
self._is_running = True
self.status = "Running"
self._thread = threading.Thread(target=self._loop)
self._thread.start()
print(f"[{self.ticker}] Bot thread started.")
return True
def stop(self):
"""Signals the bot loop to stop and waits for the thread to finish."""
if not self._is_running:
print(f"[{self.ticker}] Bot is not running.")
return False
print(f"[{self.ticker}] Attempting to stop bot thread...")
self._is_running = False # Signal the loop to exit
if self._thread:
self._thread.join() # Wait for the thread to complete its current cycle
print(f"[{self.ticker}] Bot thread stopped.")
self.status = "Stopped"
return True
# --- Example Usage (Not needed when run via api.py) ---
# if __name__ == "__main__":
# bot = TradingBot('AAPL')
# bot.start()
# try:
# while True: time.sleep(1) # Keep main thread alive
# except KeyboardInterrupt:
# bot.stop()