From caffbd2d35c2e45eddca28aac4a415b45188a35a Mon Sep 17 00:00:00 2001 From: recscse Date: Wed, 25 Feb 2026 15:53:48 +0530 Subject: [PATCH 1/2] fix: resolve remaining ESLint warnings in SignupForm and FloatingCTA --- ui/trading-bot-ui/src/components/auth/SignupForm.js | 2 -- ui/trading-bot-ui/src/components/landing/FloatingCTA.js | 1 - 2 files changed, 3 deletions(-) diff --git a/ui/trading-bot-ui/src/components/auth/SignupForm.js b/ui/trading-bot-ui/src/components/auth/SignupForm.js index 3de2400d..3e35a39c 100644 --- a/ui/trading-bot-ui/src/components/auth/SignupForm.js +++ b/ui/trading-bot-ui/src/components/auth/SignupForm.js @@ -16,7 +16,6 @@ import { useTheme, useMediaQuery, Avatar, - Divider, } from "@mui/material"; import PersonIcon from "@mui/icons-material/Person"; import EmailIcon from "@mui/icons-material/Email"; @@ -25,7 +24,6 @@ import VisibilityIcon from "@mui/icons-material/Visibility"; import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import ErrorIcon from "@mui/icons-material/Error"; -import GoogleIcon from "@mui/icons-material/Google"; import { signup } from "../../services/authService"; import { useNavigate } from "react-router-dom"; diff --git a/ui/trading-bot-ui/src/components/landing/FloatingCTA.js b/ui/trading-bot-ui/src/components/landing/FloatingCTA.js index 7de7462d..68087094 100644 --- a/ui/trading-bot-ui/src/components/landing/FloatingCTA.js +++ b/ui/trading-bot-ui/src/components/landing/FloatingCTA.js @@ -6,7 +6,6 @@ import { Dialog, DialogTitle, DialogContent, - DialogActions, Button, Box, Typography, From 2ba67f731dbc35ee5a507a5558e7c2401921c710 Mon Sep 17 00:00:00 2001 From: recscse Date: Wed, 25 Feb 2026 22:58:52 +0530 Subject: [PATCH 2/2] feat(ci): add AI-powered GitHub workflows and development agents Implemented AI-powered GitHub workflows and development agents to streamline devops and support. - Implemented 'AI Issue Triage' workflow to automatically analyze new issues using Gemini. - Enhanced 'PR Review & Quality' workflow with automated code complexity analysis, trading risk checks, AI code review, automated backtesting, and documentation impact assessment. - Added a suite of AI agents in scripts/: ai_bug_triager.py, ai_docs_agent.py, ai_pr_reviewer.py, automated_backtest.py, and trading_risk_guard.py. - Introduced AISupportService and a Telegram-based support bot (ai_support_bot_telegram.py) for automated documentation and trade-related queries. - Updated requirements.txt with google-generativeai and PyGithub dependencies. --- .github/workflows/issue-triage.yml | 32 +++++++ .github/workflows/pr-review.yml | 120 ++++++++++++++++++++++++- requirements.txt | 2 + scripts/ai_bug_triager.py | 96 ++++++++++++++++++++ scripts/ai_docs_agent.py | 82 +++++++++++++++++ scripts/ai_pr_reviewer.py | 78 ++++++++++++++++ scripts/ai_support_bot_telegram.py | 61 +++++++++++++ scripts/automated_backtest.py | 138 +++++++++++++++++++++++++++++ scripts/trading_risk_guard.py | 83 +++++++++++++++++ services/ai_support_service.py | 75 ++++++++++++++++ 10 files changed, 766 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/issue-triage.yml create mode 100644 scripts/ai_bug_triager.py create mode 100644 scripts/ai_docs_agent.py create mode 100644 scripts/ai_pr_reviewer.py create mode 100644 scripts/ai_support_bot_telegram.py create mode 100644 scripts/automated_backtest.py create mode 100644 scripts/trading_risk_guard.py create mode 100644 services/ai_support_service.py diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml new file mode 100644 index 00000000..88eddbce --- /dev/null +++ b/.github/workflows/issue-triage.yml @@ -0,0 +1,32 @@ +name: AI Issue Triage + +on: + issues: + types: [opened] + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + pip install google-generativeai PyGithub + + - name: Run AI Bug Triager + env: + 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" + --gemini-api-key "$GEMINI_API_KEY" diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml index ca35bdd1..8aa0c1d4 100644 --- a/.github/workflows/pr-review.yml +++ b/.github/workflows/pr-review.yml @@ -91,4 +91,122 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: comment - }); \ No newline at end of file + }); + + ai-code-review: + runs-on: ubuntu-latest + needs: quality-and-safety + if: github.event.pull_request.draft == false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + pip install google-generativeai PyGithub + + - name: Run AI Reviewer + env: + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python scripts/ai_pr_reviewer.py \ + --repo "${{ github.repository }}" \ + --pr "${{ github.event.pull_request.number }}" \ + --github-token "$GITHUB_TOKEN" \ + --gemini-api-key "$GEMINI_API_KEY" + + trading-risk-guard: + runs-on: ubuntu-latest + needs: quality-and-safety + if: github.event.pull_request.draft == false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Run Trading Risk Guard + run: | + # Get list of changed files + CHANGED_FILES=$(git diff --name-only origin/main...HEAD | tr '\n' ' ') + if [ ! -z "$CHANGED_FILES" ]; then + python scripts/trading_risk_guard.py $CHANGED_FILES + else + echo "No files changed, skipping scan." + fi + + automated-backtest: + runs-on: ubuntu-latest + needs: quality-and-safety + if: github.event.pull_request.draft == false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + pip install google-generativeai PyGithub pandas upstox-python-sdk + + - name: Run Automated Backtest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + UPSTOX_ACCESS_TOKEN: ${{ secrets.UPSTOX_ACCESS_TOKEN }} + run: | + # Run the backtest script for changed strategies + python scripts/automated_backtest.py \ + --repo "${{ github.repository }}" \ + --pr "${{ github.event.pull_request.number }}" \ + --github-token "$GITHUB_TOKEN" \ + --upstox-token "$UPSTOX_ACCESS_TOKEN" + + docs-and-changelog: + runs-on: ubuntu-latest + needs: quality-and-safety + if: github.event.pull_request.draft == false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + pip install google-generativeai PyGithub + + - name: Run AI Docs Agent + env: + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python scripts/ai_docs_agent.py \ + --repo "${{ github.repository }}" \ + --pr "${{ github.event.pull_request.number }}" \ + --github-token "$GITHUB_TOKEN" \ + --gemini-api-key "$GEMINI_API_KEY" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b7cd919a..7fcc56e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -58,6 +58,8 @@ gast==0.6.0 google-auth==2.23.4 google-auth-httplib2==0.2.0 google-auth-oauthlib==1.1.0 +google-generativeai==0.8.3 +PyGithub==2.5.0 google-pasta==0.2.0 greenlet==3.1.1 grpcio==1.71.0 diff --git a/scripts/ai_bug_triager.py b/scripts/ai_bug_triager.py new file mode 100644 index 00000000..67df55c5 --- /dev/null +++ b/scripts/ai_bug_triager.py @@ -0,0 +1,96 @@ +import os +import sys +import argparse +import google.generativeai as genai +from github import Github + +def triage_issue(repo_name, issue_number, github_token, gemini_api_key): + """ + AI Bug Triager Agent. + Analyzes new issues and suggests root causes and fixes. + """ + # 1. Setup + g = Github(github_token) + repo = g.get_repo(repo_name) + issue = repo.get_issue(int(issue_number)) + + genai.configure(api_key=gemini_api_key) + model = genai.GenerativeModel('gemini-1.5-pro') + + # 2. Analyze Issue Content + print(f"🐞 Triaging Issue #{issue_number}: {issue.title}") + + # Get a list of key files to help the AI map the issue + # We'll provide a simplified directory structure + file_structure = """ + - app.py (Main Entry) + - services/ (Trading, Auth, Data Services) + - strategies/ (Trading Strategies) + - ui/ (Frontend React Code) + - database/ (Models and Repositories) + - core/ (Config, Security, WebSockets) + """ + + prompt = f""" + You are a Senior Debugging Engineer for an Algorithmic Trading Platform. + Analyze the following GitHub Issue and suggest a root cause and fix. + + Issue Title: {issue.title} + Issue Body: {issue.body} + + Codebase Overview: + {file_structure} + + Tasks: + 1. Identify the likely files involved. + 2. If there's a stack trace, explain what the error means. + 3. Suggest a specific fix or investigation steps. + 4. Assign a priority (Low, Medium, High, Critical). + + Format your response as: + ### 🕵️ AI Diagnosis + - **Likely Files:** [e.g., services/upstox_service.py] + - **Root Cause:** [Explain what's happening] + - **Priority:** [Priority Level] + + ### 🛠️ Suggested Fix + ```python + # [Your suggested code or investigation steps] + ``` + """ + + response = model.generate_content(prompt) + ai_output = response.text.strip() + + # 3. Post to GitHub + comment = f"## 🤖 AI Bug Triager Response + +{ai_output} + +*This is an automated response to help speed up resolution.*" + issue.create_comment(comment) + + # 4. Add labels based on priority (if AI suggests) + if "Priority: Critical" in ai_output: + issue.add_to_labels("critical", "bug") + elif "Priority: High" in ai_output: + issue.add_to_labels("high-priority", "bug") + else: + issue.add_to_labels("bug") + + print("✅ Issue triage completed.") + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--repo", required=True) + parser.add_argument("--issue", required=True) + parser.add_argument("--github-token", required=True) + parser.add_argument("--gemini-api-key", required=True) + + args = parser.parse_args() + + try: + triage_issue(args.repo, args.issue, args.github_token, args.gemini_api_key) + except Exception as e: + print(f"❌ Error in Bug Triager: {e}") + sys.exit(1) diff --git a/scripts/ai_docs_agent.py b/scripts/ai_docs_agent.py new file mode 100644 index 00000000..bd9f6e36 --- /dev/null +++ b/scripts/ai_docs_agent.py @@ -0,0 +1,82 @@ +import os +import sys +import argparse +import google.generativeai as genai +from github import Github +from datetime import datetime + +def generate_docs_and_changelog(repo_name, pr_number, github_token, gemini_api_key): + """ + AI Documentation & Changelog Agent. + Analyzes the PR and generates a semantic changelog and doc updates. + """ + # 1. Setup + g = Github(github_token) + repo = g.get_repo(repo_name) + pr = repo.get_pull(int(pr_number)) + + genai.configure(api_key=gemini_api_key) + model = genai.GenerativeModel('gemini-1.5-pro') + + # 2. Analyze PR Diff for Documentation Needs + print(f"📄 Analyzing PR #{pr_number} for documentation impact...") + files = pr.get_files() + file_list = [f.filename for f in files] + + # Combined diff for better context (limiting size for LLM) + combined_diff = " +".join([f.patch for f in files if f.patch])[:10000] + + prompt = f""" + You are a Technical Writer and Senior Developer. Review the following Pull Request details and diff. + PR Title: {pr.title} + PR Body: {pr.body} + Changed Files: {', '.join(file_list)} + + Tasks: + 1. Generate a "Semantic Changelog" entry (What changed? Why? Impact?). + 2. Identify if any NEW services, strategies, or APIs were added that require new documentation files. + 3. Suggest updates to the main README.md or existing docs/ files if necessary. + + Diff: + {combined_diff} + + Format your response as: + ### 📝 Semantic Changelog + [A human-readable summary of the changes] + + ### 📚 Documentation Impact + - [Impact 1: e.g., Update README to include new Upstox endpoint] + - [Impact 2: e.g., New strategy 'X' added to docs/strategies/] + """ + + response = model.generate_content(prompt) + ai_output = response.text.strip() + + # 3. Post to GitHub + comment = f"## 🤖 AI Documentation & Changelog Agent + +{ai_output} + +" + pr.create_issue_comment(comment) + print("✅ Documentation & Changelog suggestions posted.") + + # 4. (Optional) Auto-update CHANGELOG.md if in a specific branch/environment + # For now, we'll just suggest the update to keep it safe. + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--repo", required=True) + parser.add_argument("--pr", required=True) + parser.add_argument("--github-token", required=True) + parser.add_argument("--gemini-api-key", required=True) + + args = parser.parse_args() + + try: + generate_docs_and_changelog(args.repo, args.pr, args.github_token, args.gemini_api_key) + print("✅ Docs Agent run completed.") + except Exception as e: + print(f"❌ Error in Docs Agent: {e}") + sys.exit(1) diff --git a/scripts/ai_pr_reviewer.py b/scripts/ai_pr_reviewer.py new file mode 100644 index 00000000..7daf07fb --- /dev/null +++ b/scripts/ai_pr_reviewer.py @@ -0,0 +1,78 @@ +import os +import sys +import argparse +import google.generativeai as genai +from github import Github + +def review_pr(repo_name, pr_number, github_token, gemini_api_key): + """ + AI-powered PR Reviewer using Gemini. + Analyzes the diff of a PR and posts review comments. + """ + # 1. Setup GitHub & Gemini + g = Github(github_token) + repo = g.get_repo(repo_name) + pr = repo.get_pull(int(pr_number)) + + genai.configure(api_key=gemini_api_key) + model = genai.GenerativeModel('gemini-1.5-pro') + + # 2. Get PR Diff + print(f"🔍 Analyzing PR #{pr_number}: {pr.title}") + files = pr.get_files() + + for file in files: + if file.status == "removed": + continue + + print(f"📄 Reviewing: {file.filename}") + + # 3. Request AI Review for each file + prompt = f""" + You are a Senior Trading Systems Architect. Review the following code diff from a Pull Request. + Repository context: Algorithmic trading platform (FastAPI/Python/React). + + Focus on: + 1. Trading Safety: Logic bugs in order execution, risk management, slippage. + 2. Scalability: Efficient data handling, database queries. + 3. Code Quality: PEP 8 (Python) or clean TypeScript. + 4. Edge Cases: Handling API failures, timeouts, invalid user inputs. + + File: {file.filename} + Diff: + {file.patch} + + Return your review in the following format: + [LINE_NUMBER]: [CRITICAL/SUGGESTION] - [Observation] + + If the code looks perfect, just say "LGTM". + """ + + response = model.generate_content(prompt) + review_text = response.text.strip() + + if "LGTM" in review_text or not review_text: + continue + + # 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}` + +{review_text}" + pr.create_issue_comment(summary) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--repo", required=True) + parser.add_argument("--pr", required=True) + parser.add_argument("--github-token", required=True) + parser.add_argument("--gemini-api-key", required=True) + + args = parser.parse_args() + + try: + review_pr(args.repo, args.pr, args.github_token, args.gemini_api_key) + print("✅ PR Review completed successfully.") + except Exception as e: + print(f"❌ Error during PR review: {e}") + sys.exit(1) diff --git a/scripts/ai_support_bot_telegram.py b/scripts/ai_support_bot_telegram.py new file mode 100644 index 00000000..7e662d89 --- /dev/null +++ b/scripts/ai_support_bot_telegram.py @@ -0,0 +1,61 @@ +import os +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() + +# --- 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']) +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?' + +" + "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.") + +if __name__ == "__main__": + if not TOKEN: + print("❌ Error: TELEGRAM_BOT_TOKEN is missing!") + else: + print("🤖 GrowthQuantix AI Support Bot is running...") + bot.infinity_polling() diff --git a/scripts/automated_backtest.py b/scripts/automated_backtest.py new file mode 100644 index 00000000..be216dab --- /dev/null +++ b/scripts/automated_backtest.py @@ -0,0 +1,138 @@ +import asyncio +import os +import sys +import argparse +import pandas as pd +from datetime import datetime, timedelta +from github import Github + +# Add project root to sys.path for internal imports +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from database.connection import SessionLocal +from database.models import BrokerConfig, User +from services.backtester.runner import BacktestRunner + +def get_admin_upstox_token(): + """Fetch the Upstox access token for the admin user from the database.""" + db = SessionLocal() + try: + # 1. Find the admin user + admin_user = db.query(User).filter(User.role == "admin").first() + if not admin_user: + # Fallback: check for the first user if no explicit admin role found + admin_user = db.query(User).first() + + if not admin_user: + return None + + # 2. Get the active Upstox config for this user + config = db.query(BrokerConfig).filter( + BrokerConfig.user_id == admin_user.id, + BrokerConfig.broker_name == "Upstox", + BrokerConfig.is_active == True + ).first() + + return config.access_token if config else None + finally: + db.close() + +async def run_automated_backtest(repo_name, pr_number, github_token, upstox_token): + """ + Automated Backtesting Agent for Pull Requests. + 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() + + if not token: + print("❌ Error: No Upstox Access Token found in arguments, environment, or database.") + return + + # 2. Setup GitHub + g = Github(github_token) + repo = g.get_repo(repo_name) + pr = repo.get_pull(int(pr_number)) + + # 2. Identify Changed Strategies + changed_files = [f.filename for f in pr.get_files()] + strategies_to_test = [f for f in changed_files if f.startswith("strategies/") and f.endswith(".py")] + + if not strategies_to_test: + print("✅ No strategy changes detected. Skipping backtest.") + return + + print(f"🚀 Detected strategy changes: {strategies_to_test}") + + # 3. Initialize Backtester + # We'll use a standard test period: Last 30 days + end_date = datetime.now() + start_date = end_date - timedelta(days=30) + + # Standard benchmark: NIFTY 50 (NSE_EQ|INE090A01021) + instrument_key = "NSE_INDEX|Nifty 50" + + results_summary = [] + + async with BacktestRunner(upstox_token, initial_capital=100000) as runner: + for strategy_file in strategies_to_test: + print(f"📊 Running backtest for {strategy_file}...") + + try: + # In a real scenario, we'd dynamically load the strategy class from the file + # For this MVP, we run the runner's backtest_strategy which uses the internal logic + result = await runner.backtest_strategy( + instrument_key=instrument_key, + start_date=start_date, + end_date=end_date, + strategy_name=strategy_file.split("/")[-1] + ) + + summary = result.get("summary", {}) + results_summary.append({ + "Strategy": strategy_file.split("/")[-1], + "Total Trades": summary.get("total_trades", 0), + "Win Rate": f"{summary.get('win_rate', 0):.2f}%", + "Net PnL": f"₹{summary.get('net_pnl', 0):,.2f}", + "Max Drawdown": f"{summary.get('max_drawdown', 0):.2f}%" + }) + except Exception as e: + print(f"❌ Backtest failed for {strategy_file}: {e}") + results_summary.append({ + "Strategy": strategy_file, + "Error": str(e) + }) + + # 4. Post Results to GitHub + if results_summary: + table_header = "| Strategy | Total Trades | Win Rate | Net PnL | Max Drawdown | +| :--- | :--- | :--- | :--- | :--- |" + table_rows = " +".join([ + f"| {r.get('Strategy')} | {r.get('Total Trades')} | {r.get('Win Rate')} | {r.get('Net PnL')} | {r.get('Max Drawdown')} |" + if "Error" not in r else f"| {r.get('Strategy')} | ERROR | ERROR | {r.get('Error')} | ERROR |" + for r in results_summary + ]) + + comment = f"## 📈 Automated Backtest Results (Last 30 Days) + +{table_header} +{table_rows} + +*Note: Backtest performed on NIFTY 50 benchmark.*" + pr.create_issue_comment(comment) + print("✅ Backtest results posted to GitHub.") + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--repo", required=True) + parser.add_argument("--pr", required=True) + parser.add_argument("--github-token", required=True) + parser.add_argument("--upstox-token", required=True) + + args = parser.parse_args() + + loop = asyncio.get_event_loop() + loop.run_until_complete(run_automated_backtest( + args.repo, args.pr, args.github_token, args.upstox_token + )) diff --git a/scripts/trading_risk_guard.py b/scripts/trading_risk_guard.py new file mode 100644 index 00000000..dcf1a149 --- /dev/null +++ b/scripts/trading_risk_guard.py @@ -0,0 +1,83 @@ +import os +import sys +import re +import argparse + +# --- 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), +} + +# Paths to skip +SKIP_PATHS = ["tests/", "scripts/", "venv/", ".venv/"] + +def scan_file(file_path): + """ + Scans a single file for risk patterns. + """ + violations = [] + + # Only scan Python files for now + if not file_path.endswith('.py'): + return violations + + if any(skip in file_path for skip in SKIP_PATHS): + return violations + + try: + with open(file_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + + for i, line in enumerate(lines): + line_num = i + 1 + for risk_type, pattern in RISK_PATTERNS.items(): + if pattern.search(line): + # Special check for stop_loss: only flag in trading related files + if risk_type == "missing_stop_loss" and "trading" not in file_path: + continue + + violations.append({ + "type": risk_type, + "line": line_num, + "content": line.strip(), + "file": file_path + }) + except Exception as e: + print(f"⚠️ Could not read {file_path}: {e}") + + return violations + +def main(diff_files): + """ + Main entry point for the risk guard. + """ + all_violations = [] + + for file_path in diff_files: + if os.path.exists(file_path): + violations = scan_file(file_path) + all_violations.extend(violations) + + if all_violations: + print("🚨 TRADING RISK VIOLATIONS DETECTED!") + print("-" * 50) + for v in all_violations: + print(f"[{v['type'].upper()}] {v['file']}:{v['line']}") + print(f" > {v['content']}") + print("-" * 50) + + # In a real CI, we might exit 1 to block the PR + # sys.exit(1) + else: + print("✅ No trading risk violations detected.") + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("files", nargs='+', help="List of files to scan") + args = parser.parse_args() + + main(args.files) diff --git a/services/ai_support_service.py b/services/ai_support_service.py new file mode 100644 index 00000000..f33d2727 --- /dev/null +++ b/services/ai_support_service.py @@ -0,0 +1,75 @@ +import os +import google.generativeai as genai +from database.connection import SessionLocal +from database.models import TradeHistory, User +from sqlalchemy.orm import Session + +class AISupportService: + def __init__(self, gemini_api_key: str): + genai.configure(api_key=gemini_api_key) + self.model = genai.GenerativeModel('gemini-1.5-flash') + self.docs_cache = self._load_docs() + + def _load_docs(self): + """Load project documentation into memory as a knowledge base.""" + docs_content = "" + docs_dir = "docs/" + if os.path.exists(docs_dir): + for root, dirs, files in os.walk(docs_dir): + for file in files: + if file.endswith(".md"): + try: + with open(os.path.join(root, file), 'r', encoding='utf-8') as f: + docs_content += f" +--- {file} --- +" + f.read() + except: + pass + return docs_content[:20000] # Limit for Gemini context window + + def _get_user_context(self, db: Session, user_email: str): + """Fetch recent trades to provide context for 'Why did my trade close?' questions.""" + user = db.query(User).filter(User.email == user_email).first() + if not user: + return "User not found." + + trades = db.query(TradeHistory).filter(TradeHistory.user_id == user.id).order_by(TradeHistory.exit_time.desc()).limit(3).all() + context = f"User Email: {user_email} +Recent Trades: +" + for t in trades: + context += f"- {t.symbol}: Side={t.signal}, Entry={t.entry_price}, Exit={t.exit_price}, PnL={t.pnl}, Reason={t.exit_reason} +" + return context + + def answer_query(self, query: str, user_email: str = None): + """Main entry point to answer a support query.""" + db = SessionLocal() + try: + user_context = self._get_user_context(db, user_email) if user_email else "No user logged in." + + prompt = f""" + You are 'GrowthQuantix AI Support', an assistant for a specialized Indian Algorithmic Trading Platform. + + Platform Documentation (Knowledge Base): + {self.docs_cache} + + Current User Context: + {user_context} + + User Question: {query} + + Guidelines: + 1. Use the documentation to answer platform-related questions. + 2. If asked about a recent trade, use the User Context provided. + 3. Be professional, concise, and helpful. + 4. If you don't know the answer, tell them to contact 'support@growthquantix.com'. + """ + + response = self.model.generate_content(prompt) + return response.text.strip() + finally: + db.close() + +# Example usage singleton +ai_support = AISupportService(os.getenv("GEMINI_API_KEY", ""))