Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python scripts/ai_bug_triager.py
--repo "${{ github.repository }}"
--issue "${{ github.event.issue.number }}"
--github-token "$GITHUB_TOKEN"
python scripts/ai_bug_triager.py \
--repo "${{ github.repository }}" \
--issue "${{ github.event.issue.number }}" \
--github-token "$GITHUB_TOKEN" \
--gemini-api-key "$GEMINI_API_KEY"
12 changes: 8 additions & 4 deletions .github/workflows/pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
cache: 'pip'

- name: Install dependencies
run: |
Expand Down Expand Up @@ -136,7 +137,8 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
cache: 'pip'

- name: Run Trading Risk Guard
run: |
Expand All @@ -162,7 +164,8 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
cache: 'pip'

- name: Install dependencies
run: |
Expand Down Expand Up @@ -194,7 +197,8 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
cache: 'pip'

- name: Install dependencies
run: |
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,6 @@ jobs:

- name: Code formatting check (Black)
run: |
# Force pathspec version that works with current black install
pip install "pathspec<0.11"
black --check --diff .

- name: Import sorting check (isort)
Expand Down
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
web: bash start.sh
worker: python -m services.background_worker
ai-support-bot: python scripts/ai_support_bot_telegram.py
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ orjson==3.11.2
packaging==24.2
pandas==2.2.3
passlib==1.7.4
pathspec==0.12.1
pathspec>=0.12.1
peewee==3.17.9
pillow==11.1.0
platformdirs==4.3.6
Expand Down Expand Up @@ -209,3 +209,6 @@ wsproto==1.2.0
yarl==1.18.3
yfinance==0.2.52
zope.interface==7.2
pyTelegramBotAPI
python-telegram-bot
radon
35 changes: 20 additions & 15 deletions scripts/ai_pr_reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ def review_pr(repo_name, pr_number, github_token, gemini_api_key):
pr = repo.get_pull(int(pr_number))

genai.configure(api_key=gemini_api_key)
model = genai.GenerativeModel('gemini-1.5-pro')
model = genai.GenerativeModel('gemini-2.0-flash')

# 2. Get PR Diff
print(f"🔍 Analyzing PR #{pr_number}: {pr.title}")
files = pr.get_files()

overall_review = []

for file in files:
if file.status == "removed":
if file.status == "removed" or not file.patch:
continue

print(f"📄 Reviewing: {file.filename}")
Expand All @@ -42,24 +44,27 @@ def review_pr(repo_name, pr_number, github_token, gemini_api_key):
Diff:
{file.patch}

Return your review in the following format:
[LINE_NUMBER]: [CRITICAL/SUGGESTION] - [Observation]

If the code looks perfect, just say "LGTM".
Return your review as a concise list of observations.
Use the format: [LINE_NUMBER]: [CRITICAL/SUGGESTION] - [Observation]
If the file looks excellent, just say "LGTM".
"""

response = model.generate_content(prompt)
review_text = response.text.strip()

if "LGTM" in review_text or not review_text:
continue
try:
response = model.generate_content(prompt)
review_text = response.text.strip()

# 4. Parse AI Suggestions and post to GitHub
# (Simplified: Posting as a single summary comment per file for now)
summary = f"### 🤖 AI Code Review for `{file.filename}`
if "LGTM" not in review_text and review_text:
overall_review.append(f"#### 📄 `{file.filename}`\n\n{review_text}")
except Exception as e:
print(f"⚠️ Error reviewing {file.filename}: {e}")

{review_text}"
# 4. Post Consolidated Review to GitHub
if overall_review:
summary = "## 🤖 AI Code Review Summary\n\n" + "\n\n---\n\n".join(overall_review)
summary += "\n\n*This review was generated by Gemini 1.5 Pro.*"
pr.create_issue_comment(summary)
else:
pr.create_issue_comment("## 🤖 AI Code Review Summary\n\n✅ All files look good! LGTM. 🚀")

if __name__ == "__main__":
parser = argparse.ArgumentParser()
Expand Down
44 changes: 24 additions & 20 deletions scripts/ai_support_bot_telegram.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,61 @@
import os
import sys
import telebot
from services.ai_support_service import ai_support
from database.connection import SessionLocal
from database.models import User
from dotenv import load_dotenv

load_dotenv()

# Add project root to sys.path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from services.ai_support_service import ai_support
from database.connection import SessionLocal
from database.models import User

# --- Config ---
TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
GEMINI_KEY = os.getenv("GEMINI_API_KEY")

bot = telebot.TeleBot(TOKEN)


# --- Handlers ---
@bot.message_handler(commands=['start', 'help'])
@bot.message_handler(commands=["start", "help"])
def send_welcome(message):
welcome_text = (
"👋 Welcome to **GrowthQuantix AI Support**!

"
"I can help you with:
"
"1. 📈 **Strategies:** Ask about Fibonacci or EMA logic.
"
"2. 🛡️ **Trades:** 'Why did my last trade close?'
"
"3. 🔧 **Setup:** 'How do I link Upstox?'

"
"👋 Welcome to **GrowthQuantix AI Support**!\n\n"
"I can help you with:\n"
"1. 📈 **Strategies:** Ask about Fibonacci or EMA logic.\n"
"2. 🛡️ **Trades:** 'Why did my last trade close?'\n"
"3. 🔧 **Setup:** 'How do I link Upstox?'\n\n"
"Just type your question below!"
)
bot.reply_to(message, welcome_text, parse_mode="Markdown")


@bot.message_handler(func=lambda message: True)
def handle_support_query(message):
chat_id = str(message.chat.id)

# 1. Look up user by Telegram Chat ID
db = SessionLocal()
user = db.query(User).filter(User.telegram_chat_id == chat_id).first()
user_email = user.email if user else None
db.close()

# 2. Process query with AI
print(f"📬 Message from {user_email or 'Unknown'}: {message.text}")

try:
response = ai_support.answer_query(message.text, user_email)
bot.reply_to(message, response, parse_mode="Markdown")
except Exception as e:
print(f"❌ Error in Support Bot: {e}")
bot.reply_to(message, "⚠️ Sorry, I'm having trouble connecting to my brain right now. Please try again later.")
bot.reply_to(
message,
"⚠️ Sorry, I'm having trouble connecting to my brain right now. Please try again later.",
)


if __name__ == "__main__":
if not TOKEN:
Expand Down
9 changes: 7 additions & 2 deletions scripts/automated_backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ async def run_automated_backtest(repo_name, pr_number, github_token, upstox_toke
Runs a backtest on changed strategies and posts results to GitHub.
"""
# 1. Setup Token (Priority: Arg > Env > DB)
token = upstox_token or os.getenv("UPSTOX_ACCESS_TOKEN") or get_admin_upstox_token()
token = upstox_token or os.getenv("UPSTOX_ACCESS_TOKEN")

if not token:
print("❌ Error: No Upstox Access Token found in arguments, environment, or database.")
print("🔍 UPSTOX_ACCESS_TOKEN not found in environment. Attempting to fetch from Database...")
token = get_admin_upstox_token()

if not token:
print("⚠️ Warning: No valid Upstox Access Token found. Backtest will be skipped.")
# We don't exit(1) because we don't want to block the PR just because the token is expired
return

# 2. Setup GitHub
Expand Down
24 changes: 13 additions & 11 deletions scripts/trading_risk_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
# --- Risk Configuration ---
# Regex patterns for dangerous patterns
RISK_PATTERNS = {
"hardcoded_secrets": re.compile(r'(API_KEY|SECRET|PASSWORD|TOKEN|TOKEN_SECRET)\s*=\s*["'][a-zA-Z0-9_\-]{10,}["']', re.IGNORECASE),
"unlocalized_time": re.compile(r'datetime\.now\(\)(?!\.astimezone|.*tz=)', re.IGNORECASE),
"missing_stop_loss": re.compile(r'place_order\(.*(?!=stop_loss)', re.IGNORECASE),
"hardcoded_lots": re.compile(r'quantity\s*=\s*\d+', re.IGNORECASE),
"hardcoded_secrets": re.compile(r"(API_KEY|SECRET|PASSWORD|TOKEN|TOKEN_SECRET)\s*=\s*['\"][a-zA-Z0-9_\-]{10,}['\"]", re.IGNORECASE),
"unlocalized_time": re.compile(r"datetime\.now\(\)(?!\.astimezone|.*tz=)", re.IGNORECASE),
"missing_stop_loss": re.compile(r"place_order\((?!.*stop_loss)", re.IGNORECASE),
"hardcoded_lots": re.compile(r"quantity\s*=\s*\d+", re.IGNORECASE),
}

# Paths to skip
Expand Down Expand Up @@ -63,17 +63,19 @@ def main(diff_files):
all_violations.extend(violations)

if all_violations:
print("🚨 TRADING RISK VIOLATIONS DETECTED!")
print("-" * 50)
print("\n🚨 TRADING RISK VIOLATIONS DETECTED!")
print("=" * 60)
for v in all_violations:
print(f"[{v['type'].upper()}] {v['file']}:{v['line']}")
print(f" > {v['content']}")
print("-" * 50)
print(f"FAILED: {v['file']}:{v['line']}")
print(f"TYPE: {v['type'].upper()}")
print(f"CODE: {v['content']}")
print("-" * 60)

# In a real CI, we might exit 1 to block the PR
# sys.exit(1)
print(f"\n❌ Total violations: {len(all_violations)}")
sys.exit(1)
else:
print("✅ No trading risk violations detected.")
sys.exit(0)

if __name__ == "__main__":
parser = argparse.ArgumentParser()
Expand Down
2 changes: 1 addition & 1 deletion services/instrument_refresh_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ async def _save_nse_instrument_keys(
logger.error(f"❌ Failed to save NSE instrument data: {e}")
raise

async def _is_already_refreshed_today(self) -> bool:
async def is_already_refreshed_today(self) -> bool:
"""Check if instruments were already refreshed today."""
try:
mcx_file_path = self.data_dir / "mcx_instruments.json"
Expand Down
Loading
Loading