Skip to content

Commit ef60371

Browse files
authored
Create substack_bot.py
1 parent 29d77f7 commit ef60371

1 file changed

Lines changed: 215 additions & 0 deletions

File tree

substack_bot.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Substack Bot - Automated trading analysis and chat posting
4+
Scrapes ES and NQ support/resistance levels from TradingView and posts to Substack Chat
5+
"""
6+
7+
import asyncio
8+
import os
9+
import logging
10+
import re
11+
from datetime import datetime, timezone
12+
from playwright.async_api import async_playwright
13+
14+
# Configure logging
15+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16+
logger = logging.getLogger(__name__)
17+
18+
class SubstackBot:
19+
def __init__(self):
20+
self.substack_email = os.getenv('SUBSTACK_EMAIL')
21+
self.substack_password = os.getenv('SUBSTACK_PASSWORD')
22+
23+
if not self.substack_email or not self.substack_password:
24+
raise ValueError("SUBSTACK_EMAIL and SUBSTACK_PASSWORD environment variables must be set")
25+
26+
async def scrape_tradingview_levels(self, symbol):
27+
"""
28+
Scrape support and resistance levels from TradingView for ES or NQ
29+
"""
30+
async with async_playwright() as p:
31+
browser = await p.chromium.launch(headless=True)
32+
page = await browser.new_page()
33+
34+
try:
35+
# Navigate to TradingView chart
36+
url = f"https://www.tradingview.com/chart/?symbol={symbol}"
37+
await page.goto(url, wait_until='networkidle')
38+
39+
# Wait for chart to load
40+
await page.wait_for_timeout(5000)
41+
42+
# Get current price
43+
current_price = None
44+
try:
45+
price_selector = '[data-name="legend-source-item"] .js-symbol-last'
46+
await page.wait_for_selector(price_selector, timeout=10000)
47+
price_element = await page.query_selector(price_selector)
48+
if price_element:
49+
current_price = await price_element.inner_text()
50+
current_price = re.sub(r'[^0-9.]', '', current_price)
51+
except Exception as e:
52+
logger.warning(f"Could not get current price: {e}")
53+
54+
# Look for support/resistance levels in the chart
55+
# This is a simplified approach - you may need to adjust selectors
56+
levels = {
57+
'current_price': current_price,
58+
'support_levels': [],
59+
'resistance_levels': []
60+
}
61+
62+
# For demo purposes, we'll use approximate levels
63+
# In a real implementation, you'd need to identify actual chart elements
64+
if symbol == 'ES1!':
65+
# Example ES levels (you'd need to implement actual scraping)
66+
if current_price:
67+
price_float = float(current_price)
68+
levels['support_levels'] = [
69+
round(price_float - 25, 1),
70+
round(price_float - 50, 1)
71+
]
72+
levels['resistance_levels'] = [
73+
round(price_float + 25, 1),
74+
round(price_float + 50, 1)
75+
]
76+
elif symbol == 'NQ1!':
77+
# Example NQ levels
78+
if current_price:
79+
price_float = float(current_price)
80+
levels['support_levels'] = [
81+
round(price_float - 100, 1),
82+
round(price_float - 200, 1)
83+
]
84+
levels['resistance_levels'] = [
85+
round(price_float + 100, 1),
86+
round(price_float + 200, 1)
87+
]
88+
89+
return levels
90+
91+
except Exception as e:
92+
logger.error(f"Error scraping TradingView: {e}")
93+
return None
94+
finally:
95+
await browser.close()
96+
97+
async def post_to_substack_chat(self, message):
98+
"""
99+
Post message to Substack Chat
100+
"""
101+
async with async_playwright() as p:
102+
browser = await p.chromium.launch(headless=True)
103+
page = await browser.new_page()
104+
105+
try:
106+
# Navigate to Substack login
107+
await page.goto('https://substack.com/sign-in', wait_until='networkidle')
108+
109+
# Fill login form
110+
await page.fill('input[type="email"]', self.substack_email)
111+
await page.click('button[type="submit"]')
112+
113+
# Wait for password field and fill it
114+
await page.wait_for_selector('input[type="password"]', timeout=10000)
115+
await page.fill('input[type="password"]', self.substack_password)
116+
await page.click('button[type="submit"]')
117+
118+
# Wait for login to complete
119+
await page.wait_for_timeout(3000)
120+
121+
# Navigate to chat (adjust URL to your specific Substack chat)
122+
# You'll need to replace this with your actual chat URL
123+
await page.goto('https://substack.com/chat', wait_until='networkidle')
124+
125+
# Find and click the message input area
126+
message_input = await page.wait_for_selector('[data-testid="chat-input"], .chat-input, textarea, [contenteditable="true"]', timeout=10000)
127+
await message_input.click()
128+
await message_input.fill(message)
129+
130+
# Send the message
131+
send_button = await page.query_selector('[data-testid="send-button"], button[type="submit"], .send-button')
132+
if send_button:
133+
await send_button.click()
134+
else:
135+
# Fallback: try pressing Enter
136+
await page.keyboard.press('Enter')
137+
138+
logger.info("Message posted to Substack Chat successfully")
139+
await page.wait_for_timeout(2000)
140+
141+
except Exception as e:
142+
logger.error(f"Error posting to Substack Chat: {e}")
143+
raise
144+
finally:
145+
await browser.close()
146+
147+
def format_market_analysis(self, es_levels, nq_levels):
148+
"""
149+
Format market analysis message in Substack Chat style
150+
"""
151+
timestamp = datetime.now(timezone.utc).strftime('%H:%M UTC')
152+
153+
message = f"📊 Market Update - {timestamp}\n\n"
154+
155+
if es_levels and es_levels['current_price']:
156+
message += f"🔹 ES (S&P 500 Futures)\n"
157+
message += f"Current: {es_levels['current_price']}\n"
158+
if es_levels['resistance_levels']:
159+
message += f"Resistance: {', '.join(map(str, es_levels['resistance_levels']))}\n"
160+
if es_levels['support_levels']:
161+
message += f"Support: {', '.join(map(str, es_levels['support_levels']))}\n"
162+
message += "\n"
163+
164+
if nq_levels and nq_levels['current_price']:
165+
message += f"🔹 NQ (Nasdaq Futures)\n"
166+
message += f"Current: {nq_levels['current_price']}\n"
167+
if nq_levels['resistance_levels']:
168+
message += f"Resistance: {', '.join(map(str, nq_levels['resistance_levels']))}\n"
169+
if nq_levels['support_levels']:
170+
message += f"Support: {', '.join(map(str, nq_levels['support_levels']))}\n"
171+
message += "\n"
172+
173+
message += "⚡ Key levels to watch for intraday moves\n"
174+
message += "#Trading #Futures #ES #NQ"
175+
176+
return message
177+
178+
async def run_analysis(self):
179+
"""
180+
Main function to run the complete analysis and posting workflow
181+
"""
182+
try:
183+
logger.info("Starting market analysis...")
184+
185+
# Scrape data from TradingView
186+
es_task = self.scrape_tradingview_levels('ES1!')
187+
nq_task = self.scrape_tradingview_levels('NQ1!')
188+
189+
es_levels, nq_levels = await asyncio.gather(es_task, nq_task)
190+
191+
if not es_levels and not nq_levels:
192+
logger.error("Failed to scrape any market data")
193+
return
194+
195+
# Format the message
196+
message = self.format_market_analysis(es_levels, nq_levels)
197+
logger.info(f"Formatted message: {message[:100]}...")
198+
199+
# Post to Substack Chat
200+
await self.post_to_substack_chat(message)
201+
logger.info("Analysis complete and posted!")
202+
203+
except Exception as e:
204+
logger.error(f"Error in analysis workflow: {e}")
205+
raise
206+
207+
async def main():
208+
"""
209+
Entry point for the bot
210+
"""
211+
bot = SubstackBot()
212+
await bot.run_analysis()
213+
214+
if __name__ == "__main__":
215+
asyncio.run(main())

0 commit comments

Comments
 (0)