├── .env.example ├── .gitignore ├── README.md ├── data └── optimization-results.json ├── docs ├── PinescriptProject1Plan.md ├── TaskList1.md ├── creating-templates.md └── template-system-guide.md ├── enhanced ├── simple_strategy_enhanced_1.pine └── simple_strategy_enhanced_2.pine ├── examples ├── BATS_CONL, 1D.csv ├── Example_PineScriptStrat1_1hr.txt ├── Example_PineScriptStrat2.txt ├── Modified_GoldScalping_Strategy.txt ├── Modified_GoldScalping_Strategy_With_Volume.txt ├── backtest-results.json ├── backtest_improved_results.txt ├── backtest_results.txt ├── basic_validation.ts ├── bollinger_strategy.pine └── simple_strategy.pine ├── jest.config.js ├── memory-bank ├── activeContext.md ├── implementationPlan.md ├── progress.md ├── systemPatterns.md └── uiDevelopmentPlan.md ├── package-lock.json ├── package.json ├── scripts ├── docs │ ├── ProjectPhase2TaskList.md │ ├── ProjectPlanPhase2.md │ ├── README-UI.md │ └── ui-connect-guide.md ├── network │ ├── allow-node-firewall.bat │ └── check-connectivity.bat └── server │ ├── run-electron-app.bat │ ├── run-express-server.bat │ ├── run-ui-admin.bat │ ├── run-ui-clean.bat │ ├── serve-test-page.bat │ └── start-ui-clean.ps1 ├── src ├── cli │ ├── commands │ │ ├── llm.ts │ │ ├── templates.ts │ │ └── test-anthropic.ts │ └── index.ts ├── config │ ├── configTool.ts │ ├── protocolConfig.js │ ├── protocolConfig.ts │ └── userConfig.ts ├── db │ ├── supabaseClient.ts │ └── vector │ │ ├── dbSetup.sql │ │ └── vectorStore.ts ├── fixers │ └── errorFixer.ts ├── index.ts ├── patched-protocol.d.ts ├── patched-protocol.js ├── prompts │ ├── templateManager.ts │ ├── templateRepository.ts │ ├── templateStructure.ts │ ├── templateVectorStore.ts │ └── templates │ │ ├── backtestAnalysis.ts │ │ ├── optimizationTemplate.ts │ │ ├── strategyAnalysis.ts │ │ └── strategyEnhancement.ts ├── services │ ├── anthropicProvider.ts │ ├── llmService.ts │ └── openaiProvider.ts ├── templates │ ├── indicators │ │ ├── bollingerBands.ts │ │ └── macd.ts │ ├── simplified-gold-strategy.js │ ├── strategies │ │ ├── movingAverageCross.ts │ │ └── rsiStrategy.ts │ ├── templateManager.ts │ └── testScript.ts ├── tests │ ├── apiKeyTest.js │ ├── fixApiKey.js │ ├── integration.test.ts │ ├── run-tests.js │ ├── template-system.test.ts │ ├── test-anthropic-backtest.ts │ ├── test-template-manager.ts │ ├── test-template-system.ts │ └── vector-store.test.ts ├── utils │ ├── errorDetector.ts │ ├── formatter.ts │ ├── versionDetector.ts │ └── versionManager.ts └── validators │ └── syntaxValidator.ts ├── tests ├── config │ ├── configTool.test.ts │ └── userConfig.test.ts ├── fixers │ └── errorFixer.test.ts ├── integration │ ├── realWorldTests.test.ts │ ├── realWorldTests.ts │ └── test-report.json ├── manual │ ├── test-basic.js │ ├── test-complex-validation.js │ ├── test-fix-errors.js │ ├── test-single-error.js │ ├── test-strategy-update.js │ ├── test-strategy.js │ └── test-validation.js ├── mcp │ ├── errorFixer.tool.test.ts │ ├── formatter.tool.test.ts │ ├── server.test.ts │ └── versionTool.test.ts ├── templates │ └── templateManager.test.ts ├── utils │ ├── formatter.test.ts │ ├── versionDetector.test.ts │ └── versionManager.test.ts └── validators │ ├── syntaxValidator.test.ts │ └── v6Validator.test.ts ├── tsconfig.json └── ui ├── .env.example ├── .gitignore ├── .next └── cache │ ├── .rscinfo │ └── webpack │ ├── client-development │ ├── 0.pack.gz │ ├── 1.pack.gz │ ├── 2.pack.gz │ ├── index.pack.gz │ └── index.pack.gz.old │ └── server-development │ ├── 0.pack.gz │ ├── 1.pack.gz │ ├── index.pack.gz │ └── index.pack.gz.old ├── README.md ├── components ├── Layout.jsx └── backtesting │ ├── BacktestResultsVisualization.jsx │ └── index.js ├── connection-test.html ├── electron ├── electron-data │ └── strategies.json ├── index.html ├── main.js ├── preload.js └── renderer.js ├── functional-tests.md ├── hosts-fix.ps1 ├── manual-test-steps.md ├── next-env.d.ts ├── next.config.js ├── next.config.mjs ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── analyze.jsx ├── backtest-results.jsx ├── index.js └── strategies.jsx ├── playwright-report └── index.html ├── playwright.config.ts ├── server-test.ps1 ├── server-troubleshoot.ps1 ├── simple-check.ps1 ├── simple-express-server.js ├── simple-http-server.js ├── simple-server.ps1 ├── simple-test-server.js ├── src ├── app │ ├── analyze │ │ └── page.tsx │ ├── api │ │ └── uploads │ │ │ └── log │ │ │ └── route.ts │ ├── layout.tsx │ ├── page.tsx │ ├── strategies │ │ └── page.tsx │ ├── templates │ │ └── page.tsx │ └── test │ │ └── page.tsx ├── lib │ └── supabaseClient.ts └── styles │ └── globals.css ├── start-server-fixed.ps1 ├── start-server.ps1 ├── start-ui-server.ps1 ├── start-ui.bat ├── styles └── globals.css ├── tailwind.config.js ├── test-page.html ├── test-python-server.py ├── test-results └── .last-run.json ├── test-server.js ├── testing-plan.md ├── tests ├── ai_testing │ ├── README.md │ ├── __init__.py │ ├── assistant_test_agent.py │ ├── pytest_integration.py │ ├── requirements.txt │ ├── run_tests.py │ ├── sample_test_data.py │ └── test_examples.py ├── basic.spec.ts └── data │ ├── history.csv │ └── trades.csv ├── tsconfig.json └── ui-diagnostics.ps1 /.env.example: -------------------------------------------------------------------------------- 1 | # NeonDB PostgreSQL Vector Database 2 | NEON_DB_URL=postgresql://username:password@your-neon-db-host.cloud/dbname?sslmode=require 3 | NEON_API_KEY=your_neon_api_key 4 | 5 | # OpenAI API 6 | OPENAI_API_KEY=your_openai_key_here 7 | 8 | # Anthropic API 9 | ANTHROPIC_API_KEY=your_anthropic_key_here 10 | 11 | # Supabase Configuration 12 | SUPABASE_URL=https://your-supabase-project.supabase.co 13 | SUPABASE_API_KEY=your_supabase_api_key 14 | SUPABASE_DB_URL=postgresql://postgres:password@localhost:5432/postgres 15 | 16 | # Web Search Configuration 17 | WEBSEARCH_API_PROVIDER=google 18 | GOOGLE_SEARCH_API_KEY=your_google_search_api_key 19 | GOOGLE_SEARCH_ENGINE_ID=your_search_engine_id 20 | 21 | # GitHub Integration 22 | GITHUB_ACCESS_TOKEN=your_github_personal_access_token 23 | GITHUB_USERNAME=your_github_username 24 | 25 | # File System MCP Configuration 26 | FS_ROOT_PATH=/path/to/root/directory 27 | FS_ALLOWED_PATHS=/path/to/allowed/directory1,/path/to/allowed/directory2 28 | 29 | # Vector Database (for semantic search) 30 | PINECONE_API_KEY=your_pinecone_api_key 31 | PINECONE_ENVIRONMENT=your_pinecone_region 32 | 33 | # Server settings 34 | HOST=127.0.0.1 35 | PORT=5001 36 | 37 | # Logging settings 38 | LOG_LEVEL=INFO -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | jspm_packages/ 4 | 5 | # Compiled output 6 | dist/ 7 | build/ 8 | out/ 9 | *.tsbuildinfo 10 | 11 | # Environment variables and secrets 12 | .env 13 | .env.* 14 | !.env.example 15 | 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | lerna-debug.log* 23 | 24 | # Cache and history files 25 | .npm 26 | .eslintcache 27 | .node_repl_history 28 | .pinescript_history/ 29 | 30 | # IDE and editor folders 31 | .idea/ 32 | .vscode/ 33 | *.swp 34 | *.swo 35 | .DS_Store 36 | .prettierrc 37 | 38 | # Coverage directories 39 | coverage/ 40 | .nyc_output/ 41 | 42 | # Testing 43 | .jest/ 44 | 45 | # Local development 46 | .local/ 47 | 48 | # Generated strategy files 49 | enhanced/ 50 | 51 | # NodeJS 52 | node_modules/ 53 | coverage/ 54 | dist/ 55 | *.log 56 | logs/ 57 | npm-debug.log* 58 | yarn-debug.log* 59 | yarn-error.log* 60 | .env 61 | .env.local 62 | .env.development.local 63 | .env.test.local 64 | .env.production.local 65 | 66 | # Server files 67 | *.bat 68 | !run-ui-clean.bat 69 | !run-ui-admin.bat 70 | !serve-test-page.bat 71 | !check-connectivity.bat 72 | !allow-node-firewall.bat 73 | !run-express-server.bat 74 | !run-electron-app.bat 75 | 76 | # Dependencies 77 | package-lock.json 78 | 79 | # Testing 80 | .nyc_output/ 81 | 82 | # TypeScript 83 | *.tsbuildinfo 84 | 85 | # VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | 92 | # History 93 | .history/ 94 | .pinescript_history/ 95 | 96 | # Misc 97 | .DS_Store 98 | Thumbs.db 99 | .idea/ 100 | *.swp 101 | *.swo 102 | 103 | # Next.js 104 | .next/ 105 | /out/ 106 | 107 | # Vercel 108 | .vercel -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PineScript MCP Project 2 | 3 | A comprehensive tool for creation, optimization, and management of PineScript trading strategies. 4 | 5 | ## Project Structure 6 | 7 | ``` 8 | ├── src/ # Core application code 9 | ├── ui/ # Next.js web interface 10 | ├── dist/ # Compiled JavaScript 11 | ├── docs/ # Documentation 12 | ├── scripts/ # Utility scripts 13 | │ ├── server/ # Server management scripts 14 | │ ├── network/ # Network diagnostics scripts 15 | │ └── docs/ # Documentation 16 | ├── data/ # Data files 17 | ├── tests/ # Application tests 18 | ├── examples/ # Example scripts and strategies 19 | └── memory-bank/ # Project context and information 20 | ``` 21 | 22 | ## Quick Start 23 | 24 | 1. Install dependencies: 25 | ``` 26 | npm install 27 | ``` 28 | 29 | 2. Start the UI (choose one method): 30 | ``` 31 | # Standard Next.js development server 32 | npm run ui 33 | 34 | # Clean start with port cleanup 35 | npm run ui:clean 36 | 37 | # Alternative Express server (if Next.js has issues) 38 | npm run ui:express 39 | 40 | # Desktop app (no web server required) 41 | npm run ui:electron 42 | ``` 43 | 44 | 3. Access the web interface at: 45 | ``` 46 | http://localhost:3001 47 | ``` 48 | (Not required for the desktop app option) 49 | 50 | ## UI Options 51 | 52 | The project includes multiple ways to access the UI: 53 | 54 | ### 1. Next.js Development Server 55 | ```bash 56 | # Standard Next.js dev server 57 | npm run ui 58 | 59 | # Clean start (kills any existing processes using the ports) 60 | npm run ui:clean 61 | 62 | # Start with administrator privileges (for stubborn processes) 63 | npm run ui:admin 64 | ``` 65 | 66 | ### 2. Alternative Server Options 67 | ```bash 68 | # Simple Express server (more stable alternative) 69 | npm run ui:express 70 | 71 | # Basic HTML test page server 72 | npm run ui:test 73 | ``` 74 | 75 | ### 3. Desktop Application 76 | For environments where web servers have connectivity issues: 77 | ```bash 78 | # Electron desktop application (no web server required) 79 | npm run ui:electron 80 | ``` 81 | 82 | ### Troubleshooting UI Server 83 | 84 | If you experience connection issues with the UI server: 85 | 86 | 1. Check your firewall settings 87 | 2. Run the firewall exception script: `npm run firewall:allow` 88 | 3. See the `scripts/docs/ui-connect-guide.md` for detailed connection troubleshooting 89 | 4. Try the desktop app option: `npm run ui:electron` 90 | 91 | ## Utility Scripts 92 | 93 | The repository includes several utility scripts to help with development and troubleshooting: 94 | 95 | ### Essential Server Scripts (in scripts/server/) 96 | - `run-ui-clean.bat` - Main script to start the UI server (cleans up existing processes) 97 | - `run-ui-admin.bat` - Starts the UI server with administrator privileges 98 | - `serve-test-page.bat` - Serves a simple HTML test page on port 8000 to test connectivity 99 | - `run-express-server.bat` - Starts an Express server as an alternative to Next.js 100 | - `run-electron-app.bat` - Starts the Electron desktop application (no web server required) 101 | 102 | ### Network Troubleshooting (in scripts/network/) 103 | - `check-connectivity.bat` - Comprehensive network connectivity diagnostics 104 | - `check-windows-firewall.bat` - Check firewall status and rules 105 | - `allow-node-firewall.bat` - Add Node.js to Windows Firewall exceptions 106 | - `test-network-connection.bat` - Test specific network connections 107 | - `test-port-3000.bat` - Test if port 3000 is available 108 | - `kill-port-3000.bat` - Kill any process using port 3000 109 | 110 | ### Documentation (in scripts/docs/) 111 | - `ui-connect-guide.md` - Comprehensive guide to UI connectivity troubleshooting 112 | - `README-UI.md` - UI-specific documentation 113 | 114 | ## Development 115 | 116 | ### Prerequisites 117 | 118 | - Node.js (v18 or higher) 119 | - npm (v8 or higher) 120 | 121 | ### Building the Project 122 | 123 | ```bash 124 | npm run build 125 | ``` 126 | 127 | ### Running Tests 128 | 129 | ```bash 130 | npm test 131 | ``` 132 | 133 | ## Features 134 | 135 | - Strategy creation and editing 136 | - Backtesting capabilities 137 | - Performance analysis 138 | - Strategy optimization 139 | - TradingView integration 140 | 141 | ## Documentation 142 | 143 | See the `docs/` directory for detailed documentation on: 144 | - API Reference 145 | - User Guide 146 | - Development Guide 147 | 148 | ## License 149 | 150 | This project is proprietary and confidential. 151 | 152 | Copyright © 2025. All rights reserved. -------------------------------------------------------------------------------- /data/optimization-results.json: -------------------------------------------------------------------------------- 1 | { 2 | "parameterSuggestions": [ 3 | { 4 | "name": "lookbackPeriod", 5 | "currentValue": 14, 6 | "suggestedValue": 20, 7 | "rationale": "Increasing the lookback period for indicators such as RSI or moving averages can provide a smoother and more stable signal, reducing the likelihood of reacting to short-term price noise.", 8 | "expectedImpact": "Expected to reduce the number of trades but improve the quality of signals, potentially increasing the strategy's win rate." 9 | }, 10 | { 11 | "name": "RSI_upper", 12 | "currentValue": 70, 13 | "suggestedValue": 75, 14 | "rationale": "Adjusting the RSI upper threshold to a higher value may allow the asset more room before being considered overbought, possibly avoiding premature exits in strong uptrends.", 15 | "expectedImpact": "Less frequent but more targeted exits in overbought conditions might slightly increase average trade duration and profitability." 16 | }, 17 | { 18 | "name": "RSI_lower", 19 | "currentValue": 30, 20 | "suggestedValue": 25, 21 | "rationale": "Lowering the RSI threshold for oversold conditions to 25 can help ensure that the market is truly oversold before entering a position, potentially increasing the likelihood of a strong rebound.", 22 | "expectedImpact": "This change is expected to reduce false entry signals in fluctuating or weakly trending markets, potentially improving overall trade profitability." 23 | } 24 | ], 25 | "optimizationApproach": { 26 | "methodology": "Backtesting with parameter sensitivity analysis, followed by walk-forward testing to confirm robustness.", 27 | "metrics": [ 28 | "Net Profit", 29 | "Sharpe Ratio", 30 | "Drawdown" 31 | ], 32 | "timeframes": [ 33 | "Current timeframe", 34 | "Higher timeframe for trend confirmation" 35 | ] 36 | }, 37 | "marketConditions": { 38 | "bestPerforming": [ 39 | "Markets with strong trends", 40 | "Moderate volatility" 41 | ], 42 | "worstPerforming": [ 43 | "Highly volatile, choppy markets", 44 | "Low volatility, sideways markets" 45 | ], 46 | "recommendations": [ 47 | "Implement a volatility filter to assess market conditions before trading, possibly using ATR or standard deviation.", 48 | "Consider using a trend confirmation from a higher timeframe to strengthen entry signals." 49 | ] 50 | }, 51 | "implementationPriority": [ 52 | { 53 | "change": "Adjust RSI thresholds", 54 | "priority": "High", 55 | "complexity": "Low", 56 | "impact": "Medium" 57 | }, 58 | { 59 | "change": "Increase lookback period", 60 | "priority": "Medium", 61 | "complexity": "Low", 62 | "impact": "Medium" 63 | }, 64 | { 65 | "change": "Implement trend and volatility filters", 66 | "priority": "Medium", 67 | "complexity": "Medium", 68 | "impact": "High" 69 | } 70 | ] 71 | } -------------------------------------------------------------------------------- /docs/TaskList1.md: -------------------------------------------------------------------------------- 1 | # TradingView PineScript MCP Server - Sequential Task List 2 | 3 | ## Phase 1: Core Framework Setup (Week 1) 4 | 1. ~~Initialize TypeScript project with necessary dependencies~~ 5 | 2. ~~Create basic MCP server structure following fastmcp patterns~~ 6 | 3. ~~Implement server entry point (index.ts)~~ 7 | 4. ~~Set up project directory structure for validators, fixers, templates~~ 8 | 5. ~~Develop basic validation architecture~~ 9 | 6. ~~Create simple template system foundation~~ 10 | 7. ~~Write initial documentation~~ 11 | 8. ~~Set up testing framework~~ 12 | 13 | ## Phase 2: Validation & Error Handling (Week 2) 14 | 9. ~~Implement PineScript v5 syntax validation~~ 15 | 10. ~~Extend validation for PineScript v6 support~~ 16 | 11. ~~Create version detection utility~~ 17 | 12. ~~Build error pattern detection system~~ 18 | 13. ~~Develop common syntax error fixes~~ 19 | 14. ~~Implement error message analyzer~~ 20 | 15. ~~Create error correction suggestion system~~ 21 | 16. ~~Test validation system with sample scripts~~ 22 | 23 | ## Phase 3: Templates & Integration (Week 3) 24 | 17. ~~Build template management system~~ 25 | 18. ~~Create library of basic strategy templates~~ 26 | 19. ~~Implement indicator templates~~ 27 | 20. ~~Develop version management system~~ 28 | 21. ~~Configure Cursor MCP integration~~ 29 | 22. ~~Add user configuration options~~ 30 | 23. ~~Implement script formatting utility~~ 31 | 24. ~~Create version history tracking~~ 32 | 33 | ## Phase 4: Testing & Refinement (Week 4) 34 | 25. ~~Develop comprehensive test suite~~ 35 | 26. ~~Test with real-world PineScript examples~~ 36 | 27. Optimize performance 37 | 28. Fix identified issues 38 | 29. ~~Complete user documentation~~ 39 | 30. Create installation guide 40 | 31. Develop usage examples 41 | 32. Package for distribution 42 | 43 | ## Final Steps 44 | 33. Review project against requirements 45 | 34. Final testing in Cursor environment 46 | 35. Release initial version 47 | 36. Document future enhancement possibilities -------------------------------------------------------------------------------- /enhanced/simple_strategy_enhanced_1.pine: -------------------------------------------------------------------------------- 1 | // Enhanced strategy v1 2 | Based on this analysis of a PineScript strategy: 3 | [{"version":"Enhanced version 1","code":"// Enhance 4 | // with improved entries -------------------------------------------------------------------------------- /enhanced/simple_strategy_enhanced_2.pine: -------------------------------------------------------------------------------- 1 | // Enhanced strategy v2 2 | Based on this analysis of a PineScript strategy: 3 | [{"version":"Enhanced version 1","code":"// Enhance 4 | // with better risk management -------------------------------------------------------------------------------- /examples/Example_PineScriptStrat1_1hr.txt: -------------------------------------------------------------------------------- 1 | //@version=6 2 | strategy("Gold Scalping BOS & CHoCH", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=1) 3 | 4 | // Support & Resistance Levels 5 | recentLow = ta.lowest(low, 10) 6 | recentHigh = ta.highest(high, 10) 7 | 8 | // Break of Structure (BOS) with Confirmation 9 | var float lastSwingHigh = na 10 | var float lastSwingLow = na 11 | bosBullish = false 12 | bosBearish = false 13 | 14 | lastSwingHigh := ta.highest(high[1], 5) 15 | bosBullish := high > lastSwingHigh 16 | 17 | lastSwingLow := ta.lowest(low[1], 5) 18 | bosBearish := low < lastSwingLow 19 | 20 | // Change of Character (CHoCH) with Immediate Reaction 21 | chochBullish = bosBearish and ta.crossover(close, lastSwingLow) 22 | chochBearish = bosBullish and ta.crossunder(close, lastSwingHigh) 23 | 24 | // Buy Entry Conditions (Refined for Early Entries) 25 | buyCondition = bosBullish and chochBullish 26 | 27 | // Sell Entry Conditions (Refined for Early Entries) 28 | sellCondition = bosBearish and chochBearish 29 | 30 | // Stop Loss & Take Profit (Dynamic and Adaptive) 31 | longSL = recentLow 32 | shortSL = recentHigh 33 | longTP = close + ((close - longSL) * 2) 34 | shortTP = close - ((shortSL - close) * 2) 35 | 36 | // Ensure SL and TP are valid before executing trades 37 | validLongTrade = buyCondition and (longSL < close) 38 | validShortTrade = sellCondition and (shortSL > close) 39 | 40 | // Execute Trades 41 | if validLongTrade 42 | strategy.entry("Long", strategy.long) 43 | strategy.exit("Exit Long", from_entry="Long", stop=longSL, limit=longTP) 44 | 45 | if validShortTrade 46 | strategy.entry("Short", strategy.short) 47 | strategy.exit("Exit Short", from_entry="Short", stop=shortSL, limit=shortTP) 48 | 49 | // Plot Buy/Sell Signals 50 | plotshape(series=buyCondition, location=location.belowbar, color=color.green, style=shape.labelup, title="BUY") 51 | plotshape(series=sellCondition, location=location.abovebar, color=color.red, style=shape.labeldown, title="SELL") 52 | 53 | // Debugging: Show swing high/low levels 54 | plot(lastSwingHigh, title="Last Swing High", color=color.blue, linewidth=2, style=plot.style_linebr) 55 | plot(lastSwingLow, title="Last Swing Low", color=color.orange, linewidth=2, style=plot.style_linebr) -------------------------------------------------------------------------------- /examples/Example_PineScriptStrat2.txt: -------------------------------------------------------------------------------- 1 | //@version=6 2 | strategy("Gold Scalping BOS & CHoCH", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=1) 3 | 4 | // Support & Resistance Levels 5 | recentLow = ta.lowest(low, 10) 6 | recentHigh = ta.highest(high, 10) 7 | 8 | // Break of Structure (BOS) with Confirmation 9 | var float lastSwingHigh = na 10 | var float lastSwingLow = na 11 | bosBullish = false 12 | bosBearish = false 13 | 14 | lastSwingHigh := ta.highest(high[1], 5) 15 | bosBullish := high > lastSwingHigh 16 | 17 | lastSwingLow := ta.lowest(low[1], 5) 18 | bosBearish := low < lastSwingLow 19 | 20 | // Change of Character (CHoCH) with Immediate Reaction 21 | chochBullish = bosBearish and ta.crossover(close, lastSwingLow) 22 | chochBearish = bosBullish and ta.crossunder(close, lastSwingHigh) 23 | 24 | // Buy Entry Conditions (Refined for Early Entries) 25 | buyCondition = bosBullish and chochBullish 26 | 27 | // Sell Entry Conditions (Refined for Early Entries) 28 | sellCondition = bosBearish and chochBearish 29 | 30 | // Stop Loss & Take Profit (Dynamic and Adaptive) 31 | longSL = recentLow 32 | shortSL = recentHigh 33 | longTP = close + ((close - longSL) * 2) 34 | shortTP = close - ((shortSL - close) * 2) 35 | 36 | // Ensure SL and TP are valid before executing trades 37 | validLongTrade = buyCondition and (longSL < close) 38 | validShortTrade = sellCondition and (shortSL > close) 39 | 40 | // Execute Trades 41 | if validLongTrade 42 | strategy.entry("Long", strategy.long) 43 | strategy.exit("Exit Long", from_entry="Long", stop=longSL, limit=longTP) 44 | 45 | if validShortTrade 46 | strategy.entry("Short", strategy.short) 47 | strategy.exit("Exit Short", from_entry="Short", stop=shortSL, limit=shortTP) 48 | 49 | // Plot Buy/Sell Signals 50 | plotshape(series=buyCondition, location=location.belowbar, color=color.green, style=shape.labelup, title="BUY") 51 | plotshape(series=sellCondition, location=location.abovebar, color=color.red, style=shape.labeldown, title="SELL") 52 | 53 | // Debugging: Show swing high/low levels 54 | plot(lastSwingHigh, title="Last Swing High", color=color.blue, linewidth=2, style=plot.style_linebr) 55 | plot(lastSwingLow, title="Last Swing Low", color=color.orange, linewidth=2, style=plot.style_linebr) 56 | -------------------------------------------------------------------------------- /examples/Modified_GoldScalping_Strategy_With_Volume.txt: -------------------------------------------------------------------------------- 1 | //@version=6 2 | strategy("Gold Scalping BOS & CHoCH with MA & Volume Filter", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=1) 3 | 4 | // Support & Resistance Levels 5 | recentLow = ta.lowest(low, 10) 6 | recentHigh = ta.highest(high, 10) 7 | 8 | // Moving Average Trend Filter 9 | fastMA = ta.ema(close, 20) 10 | slowMA = ta.ema(close, 50) 11 | uptrend = fastMA > slowMA 12 | downtrend = fastMA < slowMA 13 | 14 | // Volume Filter 15 | volumeMA = ta.sma(volume, 20) 16 | highVolume = volume > volumeMA * 1.2 // Volume at least 20% above the average 17 | 18 | // Break of Structure (BOS) with Confirmation 19 | var float lastSwingHigh = na 20 | var float lastSwingLow = na 21 | bosBullish = false 22 | bosBearish = false 23 | 24 | lastSwingHigh := ta.highest(high[1], 5) 25 | bosBullish := high > lastSwingHigh 26 | 27 | lastSwingLow := ta.lowest(low[1], 5) 28 | bosBearish := low < lastSwingLow 29 | 30 | // Change of Character (CHoCH) with Immediate Reaction 31 | chochBullish = bosBearish and ta.crossover(close, lastSwingLow) 32 | chochBearish = bosBullish and ta.crossunder(close, lastSwingHigh) 33 | 34 | // Buy Entry Conditions (with Volume Filter) 35 | buyCondition = bosBullish and chochBullish and uptrend and highVolume 36 | 37 | // Sell Entry Conditions (with Volume Filter) 38 | sellCondition = bosBearish and chochBearish and downtrend and highVolume 39 | 40 | // Stop Loss & Take Profit (Dynamic and Adaptive) 41 | longSL = recentLow 42 | shortSL = recentHigh 43 | longTP = close + ((close - longSL) * 2) 44 | shortTP = close - ((shortSL - close) * 2) 45 | 46 | // Ensure SL and TP are valid before executing trades 47 | validLongTrade = buyCondition and (longSL < close) 48 | validShortTrade = sellCondition and (shortSL > close) 49 | 50 | // Execute Trades 51 | if validLongTrade 52 | strategy.entry("Long", strategy.long) 53 | strategy.exit("Exit Long", from_entry="Long", stop=longSL, limit=longTP) 54 | 55 | if validShortTrade 56 | strategy.entry("Short", strategy.short) 57 | strategy.exit("Exit Short", from_entry="Short", stop=shortSL, limit=shortTP) 58 | 59 | // Plot Buy/Sell Signals 60 | plotshape(series=buyCondition, location=location.belowbar, color=color.green, style=shape.labelup, title="BUY") 61 | plotshape(series=sellCondition, location=location.abovebar, color=color.red, style=shape.labeldown, title="SELL") 62 | 63 | // Debugging: Show swing high/low levels 64 | plot(lastSwingHigh, title="Last Swing High", color=color.blue, linewidth=2, style=plot.style_linebr) 65 | plot(lastSwingLow, title="Last Swing Low", color=color.orange, linewidth=2, style=plot.style_linebr) 66 | 67 | // Plot Moving Averages 68 | plot(fastMA, title="Fast EMA", color=color.green, linewidth=1) 69 | plot(slowMA, title="Slow EMA", color=color.red, linewidth=1) 70 | 71 | // Plot Volume 72 | hline(0, "Zero Line", color=color.gray, linestyle=hline.style_dotted) 73 | plot(ta.change(volume) / volumeMA * 100, title="Volume Change %", color=color.purple, style=plot.style_columns, histbase=0) -------------------------------------------------------------------------------- /examples/backtest-results.json: -------------------------------------------------------------------------------- 1 | { 2 | "summary": { 3 | "netProfit": 2563.45, 4 | "profitFactor": 1.83, 5 | "winRate": 61.5, 6 | "maxDrawdown": 18.2, 7 | "recoveryFactor": 2.1, 8 | "sharpeRatio": 0.92, 9 | "totalTrades": 78, 10 | "winningTrades": 48, 11 | "losingTrades": 30 12 | }, 13 | "trades": [ 14 | { 15 | "entryTime": "2023-01-15T09:30:00Z", 16 | "exitTime": "2023-01-18T14:45:00Z", 17 | "entryPrice": 432.10, 18 | "exitPrice": 448.75, 19 | "profit": 16.65, 20 | "profitPercent": 3.85, 21 | "direction": "long" 22 | }, 23 | { 24 | "entryTime": "2023-01-25T12:15:00Z", 25 | "exitTime": "2023-01-27T15:30:00Z", 26 | "entryPrice": 456.25, 27 | "exitPrice": 442.80, 28 | "profit": -13.45, 29 | "profitPercent": -2.95, 30 | "direction": "long" 31 | }, 32 | { 33 | "entryTime": "2023-02-03T10:45:00Z", 34 | "exitTime": "2023-02-10T11:00:00Z", 35 | "entryPrice": 438.90, 36 | "exitPrice": 462.35, 37 | "profit": 23.45, 38 | "profitPercent": 5.34, 39 | "direction": "long" 40 | } 41 | ], 42 | "monthlyPerformance": [ 43 | { 44 | "month": "2023-01", 45 | "profit": 743.20, 46 | "trades": 18, 47 | "winRate": 64.2 48 | }, 49 | { 50 | "month": "2023-02", 51 | "profit": 925.35, 52 | "trades": 21, 53 | "winRate": 68.1 54 | }, 55 | { 56 | "month": "2023-03", 57 | "profit": 894.90, 58 | "trades": 19, 59 | "winRate": 58.3 60 | }, 61 | { 62 | "month": "2023-04", 63 | "profit": -425.60, 64 | "trades": 20, 65 | "winRate": 42.7 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /examples/backtest_improved_results.txt: -------------------------------------------------------------------------------- 1 | Backtest Results for Improved Gold Scalping BOS & CHoCH Strategy 2 | Timeframe: 1 Day 3 | Symbol: BATS_CONL 4 | Period: Jan 01, 2022 - Jun 30, 2023 5 | Initial Capital: $10,000 6 | 7 | PERFORMANCE SUMMARY: 8 | Net Profit: $3,876.24 (38.76%) 9 | Profit Factor: 2.27 10 | Win Rate: 67.8% 11 | Max Drawdown: -7.43% 12 | Sharpe Ratio: 1.48 13 | Sortino Ratio: 2.13 14 | Recovery Factor: 5.21 15 | Average Trade: $78.32 16 | Average Win: $152.56 17 | Average Loss: -$75.43 18 | Longest Winning Streak: 8 trades 19 | Longest Losing Streak: 3 trades 20 | 21 | TRADE STATISTICS: 22 | Total Trades: 59 23 | Winning Trades: 40 (67.8%) 24 | Losing Trades: 19 (32.2%) 25 | Average Hold Time: 4.7 days 26 | Max Contracts/Shares: 215 27 | 28 | PERFORMANCE COMPARISON WITH ORIGINAL STRATEGY: 29 | Metric | Original Strategy | Improved Strategy | Difference 30 | ---------------------|------------------|-------------------|------------ 31 | Net Profit | $2,346.82 | $3,876.24 | +65.2% 32 | Profit Factor | 1.85 | 2.27 | +22.7% 33 | Win Rate | 54.3% | 67.8% | +13.5% 34 | Max Drawdown | -12.8% | -7.43% | -41.9% 35 | Sharpe Ratio | 0.96 | 1.48 | +54.2% 36 | Average Trade | $45.13 | $78.32 | +73.5% 37 | 38 | IMPROVEMENT BREAKDOWN: 39 | 1. Volume Filter: Eliminated 17 losing trades with poor volume patterns 40 | 2. RSI Filter: Improved entry timing, increasing win rate by 8.7% 41 | 3. Dynamic ATR-based Stop Loss: Reduced average loss by 31.4% 42 | 4. Enhanced Take Profit Multiplier: Increased average win by 28.2% 43 | 5. Additional Confirmation Signals: Reduced false BOS/CHoCH patterns by 43.6% 44 | 45 | MONTHLY PERFORMANCE: 46 | Jan 2022: +4.2% 47 | Feb 2022: +2.1% 48 | Mar 2022: +5.8% 49 | Apr 2022: -2.3% 50 | May 2022: +7.9% 51 | Jun 2022: +1.8% 52 | Jul 2022: +3.4% 53 | Aug 2022: +5.2% 54 | Sep 2022: -3.1% 55 | Oct 2022: +6.3% 56 | Nov 2022: +3.5% 57 | Dec 2022: +0.9% 58 | Jan 2023: +4.8% 59 | Feb 2023: +2.7% 60 | Mar 2023: -1.4% 61 | Apr 2023: +5.2% 62 | May 2023: +3.8% 63 | Jun 2023: -1.6% 64 | 65 | KEY TRADES: 66 | - Largest Win: $486.25 (March 10-15, 2023) - Strong BOS/CHoCH pattern with high volume confirmation 67 | - Largest Loss: $187.60 (September 18-21, 2022) - False breakout stopped out by ATR-based stop loss 68 | - Best Sequence: May 9-27, 2022 (5 consecutive winning trades totaling $763.42) 69 | 70 | OPTIMIZATION INSIGHTS: 71 | - Optimal RSI Settings: Oversold = 30, Overbought = 70 72 | - Best ATR Multiplier: 1.5x 73 | - Most Effective Swing Lookback Period: 5 bars 74 | - Ideal Volume Filter Threshold: 1.5x average volume 75 | 76 | CONCLUSION: 77 | The improved Gold Scalping BOS & CHoCH strategy significantly outperforms the original version. 78 | Key improvements include more precise entry/exit points through volume and RSI filters, better 79 | risk management with ATR-based stop losses, and optimized take profit levels. The strategy 80 | excels in volatile market conditions while maintaining protection against excessive drawdowns. 81 | 82 | RECOMMENDED SETTINGS: 83 | - Swing Lookback: 5 84 | - Volume Filter: ON (1.5x threshold) 85 | - RSI Filter: ON (30/70 levels) 86 | - ATR-based Stop Loss: ON (1.5x multiplier) 87 | - Take Profit Multiplier: 2.0x -------------------------------------------------------------------------------- /examples/backtest_results.txt: -------------------------------------------------------------------------------- 1 | Backtest Results for Bollinger Bands Strategy 2 | Timeframe: 1 Day 3 | Period: Jan 01, 2022 - Dec 31, 2023 4 | Initial Capital: $10,000 5 | 6 | PERFORMANCE SUMMARY: 7 | Net Profit: $1,234.56 (12.35%) 8 | Profit Factor: 1.45 9 | Win Rate: 52% 10 | Max Drawdown: -8.72% 11 | Sharpe Ratio: 0.91 12 | Sortino Ratio: 1.18 13 | Recovery Factor: 1.42 14 | Average Trade: $18.43 15 | Average Win: $56.78 16 | Average Loss: -$23.45 17 | Longest Winning Streak: 5 trades 18 | Longest Losing Streak: 4 trades 19 | 20 | TRADE STATISTICS: 21 | Total Trades: 67 22 | Winning Trades: 35 (52.24%) 23 | Losing Trades: 32 (47.76%) 24 | Average Hold Time: 5.3 days 25 | Max Contracts/Shares: 245 26 | 27 | MONTHLY PERFORMANCE: 28 | Jan 2022: +2.1% 29 | Feb 2022: -1.7% 30 | Mar 2022: +3.2% 31 | Apr 2022: +0.8% 32 | May 2022: -2.3% 33 | Jun 2022: -1.9% 34 | Jul 2022: +4.1% 35 | Aug 2022: +1.5% 36 | Sep 2022: -3.2% 37 | Oct 2022: +2.8% 38 | Nov 2022: +1.1% 39 | Dec 2022: -0.7% 40 | Jan 2023: +3.4% 41 | Feb 2023: +1.2% 42 | Mar 2023: -1.8% 43 | Apr 2023: +2.6% 44 | May 2023: +0.5% 45 | Jun 2023: -1.1% 46 | Jul 2023: +2.9% 47 | Aug 2023: -0.9% 48 | Sep 2023: +1.7% 49 | Oct 2023: -2.5% 50 | Nov 2023: +3.6% 51 | Dec 2023: +0.4% 52 | 53 | ADDITIONAL METRICS: 54 | Calmar Ratio: 1.42 55 | Ulcer Index: 2.67 56 | Time in Market: 63% 57 | Maximum Consecutive Wins: 5 58 | Maximum Consecutive Losses: 4 -------------------------------------------------------------------------------- /examples/basic_validation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic PineScript Validation Example 3 | * 4 | * This example demonstrates how to use the PineScript MCP server to validate code. 5 | */ 6 | 7 | // Sample PineScript code for a simple strategy 8 | const pineScriptCode = ` 9 | //@version=5 10 | strategy("My Strategy", overlay=true) 11 | 12 | // Input parameters 13 | fastLength = input(12, "Fast Length") 14 | slowLength = input(26, "Slow Length") 15 | 16 | // Calculate indicators 17 | fastMA = ta.sma(close, fastLength) 18 | slowMA = ta.sma(close, slowLength) 19 | 20 | // Define trading conditions 21 | longCondition = ta.crossover(fastMA, slowMA) 22 | shortCondition = ta.crossunder(fastMA, slowMA) 23 | 24 | // Execute strategy 25 | if (longCondition) 26 | strategy.entry("Long", strategy.long) 27 | 28 | if (shortCondition) 29 | strategy.entry("Short", strategy.short) 30 | 31 | // Plot indicators 32 | plot(fastMA, "Fast MA", color.blue) 33 | plot(slowMA, "Slow MA", color.red) 34 | `; 35 | 36 | // Sample code with an error 37 | const pineScriptWithError = ` 38 | //@version=5 39 | strategy("My Strategy", overlay=true) 40 | 41 | // Input parameters 42 | fastLength = input(12, "Fast Length") 43 | slowLength = input(26, "Slow Length") 44 | 45 | // Calculate indicators 46 | fastMA = ta.sma(close, fastLength) 47 | slowMA = ta.sma(close, slowLength) 48 | 49 | // Define trading conditions 50 | longCondition = ta.crossover(fastMA, slowMA 51 | shortCondition = ta.crossunder(fastMA, slowMA) 52 | 53 | // Execute strategy 54 | if (longCondition) 55 | strategy.entry("Long", strategy.long) 56 | 57 | if (shortCondition) 58 | strategy.entry("Short", strategy.short) 59 | 60 | // Plot indicators 61 | plot(fastMA, "Fast MA", color.blue) 62 | plot(slowMA, "Slow MA", color.red) 63 | `; 64 | 65 | // Example of how to use the validation 66 | console.log("Validating correct PineScript code..."); 67 | // In a real implementation, you would call the MCP server here 68 | console.log("Sample code would be validated by MCP server"); 69 | 70 | console.log("\nValidating PineScript code with syntax error..."); 71 | // In a real implementation, you would call the MCP server here 72 | console.log("Sample code with error would be validated by MCP server"); 73 | 74 | // Example of using the template functionality 75 | console.log("\nGetting a strategy template..."); 76 | // In a real implementation, you would call the MCP server here 77 | console.log("MCP server would return a strategy template"); -------------------------------------------------------------------------------- /examples/bollinger_strategy.pine: -------------------------------------------------------------------------------- 1 | //@version=5 2 | strategy("Bollinger Bands Strategy", overlay=true) 3 | 4 | // Input parameters 5 | length = input.int(20, "BB Length", minval=1) 6 | mult = input.float(2.0, "BB StdDev", minval=0.1, step=0.1) 7 | src = input(close, "Source") 8 | smaLen = input.int(50, "SMA Filter Length", minval=1) 9 | 10 | // Calculate Bollinger Bands 11 | [middle, upper, lower] = ta.bb(src, length, mult) 12 | smaFilter = ta.sma(src, smaLen) 13 | 14 | // Trading conditions 15 | longCondition = ta.crossover(src, lower) and close > smaFilter 16 | shortCondition = ta.crossunder(src, upper) and close < smaFilter 17 | 18 | // Position sizing 19 | riskPct = input.float(1.0, "Risk %", minval=0.1, step=0.1) / 100 20 | atrLength = input.int(14, "ATR Length", minval=1) 21 | atrPeriod = ta.atr(atrLength) 22 | posSize = math.floor(strategy.equity * riskPct / atrPeriod) 23 | 24 | // Strategy execution 25 | if (longCondition) 26 | strategy.entry("BBLong", strategy.long, qty=posSize) 27 | 28 | if (shortCondition) 29 | strategy.entry("BBShort", strategy.short, qty=posSize) 30 | 31 | // Plot indicators 32 | plot(middle, "Middle Band", color=color.yellow) 33 | plot(upper, "Upper Band", color=color.red) 34 | plot(lower, "Lower Band", color=color.green) 35 | plot(smaFilter, "SMA Filter", color=color.blue) -------------------------------------------------------------------------------- /examples/simple_strategy.pine: -------------------------------------------------------------------------------- 1 | //@version=5 2 | strategy("Simple Moving Average Crossover", overlay=true) 3 | 4 | // Input parameters 5 | fastLength = input(9, "Fast MA Length") 6 | slowLength = input(21, "Slow MA Length") 7 | takeProfitPct = input.float(2.0, "Take Profit %", minval=0.1, step=0.1) / 100 8 | stopLossPct = input.float(1.0, "Stop Loss %", minval=0.1, step=0.1) / 100 9 | 10 | // Calculate moving averages 11 | fastMA = ta.sma(close, fastLength) 12 | slowMA = ta.sma(close, slowLength) 13 | 14 | // Generate signals 15 | longCondition = ta.crossover(fastMA, slowMA) 16 | shortCondition = ta.crossunder(fastMA, slowMA) 17 | 18 | // Plot indicators 19 | plot(fastMA, "Fast MA", color=color.blue) 20 | plot(slowMA, "Slow MA", color=color.red) 21 | 22 | // Execute strategy 23 | if (longCondition) 24 | strategy.entry("Long", strategy.long) 25 | 26 | if (shortCondition) 27 | strategy.close("Long") 28 | 29 | // Set take profit and stop loss 30 | strategy.exit("Take Profit / Stop Loss", "Long", 31 | profit=strategy.position_avg_price * takeProfitPct, 32 | loss=strategy.position_avg_price * stopLossPct) 33 | 34 | // Plot buy/sell signals 35 | plotshape(longCondition, "Buy Signal", shape.triangleup, location.belowbar, color.green, size=size.small) 36 | plotshape(shortCondition, "Sell Signal", shape.triangledown, location.abovebar, color.red, size=size.small) -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | export default { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | extensionsToTreatAsEsm: ['.ts'], 6 | transform: { 7 | '^.+\\.tsx?$': [ 8 | 'ts-jest', 9 | { 10 | useESM: true, 11 | }, 12 | ], 13 | }, 14 | moduleNameMapper: { 15 | '^(\\.{1,2}/.*)\\.js$': '$1', 16 | }, 17 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pinescript-mcp", 3 | "version": "0.2.0", 4 | "description": "PineScript Model Context Protocol", 5 | "main": "dist/index.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "build": "tsc", 11 | "test": "jest --coverage", 12 | "dev": "nodemon --watch \"src/**\" --ext \"ts,json\" --exec \"tsc && node dist/index.js\"", 13 | "start": "node dist/index.js", 14 | "ui": "cd ui && npm run dev", 15 | "ui:clean": "start scripts\\server\\run-ui-clean.bat", 16 | "ui:admin": "start scripts\\server\\run-ui-admin.bat", 17 | "ui:test": "start scripts\\server\\serve-test-page.bat", 18 | "ui:express": "start scripts\\server\\run-express-server.bat", 19 | "ui:electron": "start scripts\\server\\run-electron-app.bat", 20 | "clean": "taskkill /F /IM node.exe", 21 | "check:network": "start scripts\\network\\check-connectivity.bat", 22 | "firewall:allow": "start scripts\\network\\allow-node-firewall.bat", 23 | "check:firewall": "start scripts\\network\\check-windows-firewall.bat" 24 | }, 25 | "keywords": [ 26 | "pinescript", 27 | "tradingview", 28 | "mcp", 29 | "model", 30 | "context", 31 | "protocol" 32 | ], 33 | "author": "", 34 | "license": "Proprietary", 35 | "devDependencies": { 36 | "@types/jest": "^29.5.12", 37 | "@types/node": "^20.11.24", 38 | "jest": "^29.7.0", 39 | "ts-jest": "^29.1.2", 40 | "typescript": "^5.3.3" 41 | }, 42 | "dependencies": { 43 | "chalk": "^4.1.2", 44 | "dotenv": "^16.4.5", 45 | "node-fetch": "^2.7.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scripts/docs/README-UI.md: -------------------------------------------------------------------------------- 1 | # PineScript MCP - UI Server Guide 2 | 3 | ## Starting the UI 4 | 5 | To start the UI server, run: 6 | 7 | ``` 8 | start-ui-direct.bat 9 | ``` 10 | 11 | This will: 12 | 1. Check if port 3000 is already in use and free it if needed 13 | 2. Start the Next.js development server 14 | 3. Open the UI at http://127.0.0.1:3000 15 | 16 | ## Features 17 | 18 | The UI includes the following key pages: 19 | - Home page - Overview of the system 20 | - Strategies - View and manage trading strategies 21 | - Templates - Browse and use prompt templates 22 | - Analyze - Run analysis on your trading strategies 23 | 24 | ## Troubleshooting 25 | 26 | ### Common Issues 27 | 28 | 1. **Port 3000 Already in Use** 29 | - The start script will attempt to free port 3000 automatically 30 | - If that fails, run `taskkill /F /PID ` where `` is the ID found by running `netstat -ano | findstr :3000` 31 | 32 | 2. **Node.js Not Found** 33 | - Make sure Node.js is installed (download from https://nodejs.org/) 34 | - Add Node.js to your PATH 35 | 36 | 3. **Dependencies Not Installed** 37 | - Run `cd ui && npm install` to install all required dependencies 38 | 39 | 4. **Cannot Access the UI** 40 | - Try accessing the UI at http://127.0.0.1:3000 instead of localhost:3000 41 | - Check your firewall settings 42 | 43 | ## Accessing the UI 44 | 45 | The UI is only accessible from your local machine (127.0.0.1). This is for security reasons and simplifies the setup process. No external connections are allowed. 46 | 47 | ## Stopping the Server 48 | 49 | To stop the UI server, press Ctrl+C in the terminal window where the server is running. -------------------------------------------------------------------------------- /scripts/network/allow-node-firewall.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Adding Node.js Firewall Exception 3 | echo =============================== 4 | echo. 5 | echo This script will add a firewall exception for Node.js. 6 | echo Administrator privileges are required. 7 | echo. 8 | echo Press any key to continue or close this window to cancel. 9 | pause > nul 10 | 11 | :: Create a temporary VBS script to request admin privileges 12 | echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\nodejsfw.vbs" 13 | echo UAC.ShellExecute "cmd.exe", "/c netsh advfirewall firewall add rule name=""Node.js Server"" dir=in action=allow program=""%ProgramFiles%\nodejs\node.exe"" enable=yes & echo. & echo Firewall rule added. & pause", "", "runas", 1 >> "%temp%\nodejsfw.vbs" 14 | 15 | :: Execute the VBS script 16 | start "" "%temp%\nodejsfw.vbs" 17 | 18 | :: Delete the temporary VBS script 19 | timeout /t 1 > nul 20 | del "%temp%\nodejsfw.vbs" 21 | 22 | echo. 23 | echo If you approved the UAC prompt, the firewall rule should be added. 24 | echo You may need to restart any Node.js processes for changes to take effect. 25 | echo. 26 | pause -------------------------------------------------------------------------------- /scripts/network/check-connectivity.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo NETWORK CONNECTIVITY DIAGNOSTIC TOOL 3 | echo ================================== 4 | echo. 5 | 6 | echo This tool will check if your system can: 7 | echo 1. Resolve DNS 8 | echo 2. Connect to localhost 9 | echo 3. Connect to the internet 10 | echo 4. Check available ports 11 | echo. 12 | 13 | echo ----- SYSTEM INFORMATION ----- 14 | echo. 15 | echo Computer name: %COMPUTERNAME% 16 | echo Windows version: 17 | ver 18 | echo. 19 | 20 | echo ----- CHECKING IP CONFIGURATION ----- 21 | echo. 22 | ipconfig /all | findstr "IPv4 DNS" 23 | echo. 24 | 25 | echo ----- CHECKING LOCALHOST CONNECTIVITY ----- 26 | echo. 27 | ping 127.0.0.1 -n 2 28 | echo. 29 | 30 | echo ----- CHECKING INTERNET CONNECTIVITY ----- 31 | echo. 32 | ping 8.8.8.8 -n 2 33 | echo. 34 | ping www.google.com -n 2 35 | echo. 36 | 37 | echo ----- CHECKING FOR PROCESSES ON WEB PORTS ----- 38 | echo. 39 | echo Checking ports 3000, 3001, 8000, 8080... 40 | echo. 41 | netstat -ano | findstr /C:":3000 " /C:":3001 " /C:":8000 " /C:":8080 " 42 | echo. 43 | 44 | echo ----- CHECKING FIREWALL STATUS ----- 45 | echo. 46 | netsh advfirewall show allprofiles | findstr "State" 47 | echo. 48 | 49 | echo ----- DIAGNOSTIC COMPLETE ----- 50 | echo. 51 | echo If you see "Reply from 127.0.0.1", local connectivity is working. 52 | echo If you see "Reply from 8.8.8.8", internet connectivity is working. 53 | echo. 54 | echo If port checks show entries, those ports are in use. 55 | echo. 56 | echo Windows Firewall status affects your ability to connect to servers. 57 | echo. 58 | 59 | pause -------------------------------------------------------------------------------- /scripts/server/run-electron-app.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo PINESCRIPT MCP - ELECTRON APP 3 | echo ============================ 4 | echo. 5 | echo This batch file will start the Electron desktop app, 6 | echo which runs without requiring a web server. 7 | echo. 8 | 9 | cd ui 10 | 11 | echo Installing Electron globally if needed... 12 | call npm install -g electron 13 | 14 | echo Installing project dependencies... 15 | call npm install 16 | 17 | echo Starting Electron app... 18 | echo. 19 | echo If this is your first time, it may take a moment to start. 20 | echo. 21 | echo Press Ctrl+C to stop the app when done. 22 | echo. 23 | 24 | npx electron . 25 | 26 | cd .. 27 | pause -------------------------------------------------------------------------------- /scripts/server/run-express-server.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo SIMPLE EXPRESS SERVER 3 | echo ====================== 4 | echo. 5 | echo This will start a simple Express server that can serve: 6 | echo - The test HTML page 7 | echo - Static Next.js build files (if built) 8 | echo. 9 | 10 | REM Kill any existing processes on port 3001 11 | FOR /F "tokens=5" %%P IN ('netstat -ano ^| findstr :3001 ^| findstr LISTENING') DO ( 12 | echo Killing existing process on port 3001 (PID: %%P) 13 | taskkill /F /PID %%P >nul 2>&1 14 | ) 15 | 16 | cd ui 17 | echo Installing Express if needed... 18 | call npm install express --no-save --silent 19 | 20 | echo Starting Express server on port 3001... 21 | echo. 22 | echo Access the server at: http://localhost:3001/ 23 | echo. 24 | echo Press Ctrl+C to stop the server when done 25 | echo. 26 | 27 | node simple-express-server.js 28 | 29 | cd .. 30 | pause -------------------------------------------------------------------------------- /scripts/server/run-ui-admin.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo NEXT.JS SERVER LAUNCHER (ADMIN MODE) 3 | echo ================================== 4 | echo. 5 | echo This script will start the PowerShell cleanup script 6 | echo with administrator privileges for better process killing. 7 | echo. 8 | echo Press any key to continue, or close this window to cancel. 9 | pause > nul 10 | 11 | :: Create a temporary VBS script to elevate privileges 12 | echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\elevate.vbs" 13 | echo UAC.ShellExecute "powershell.exe", "-ExecutionPolicy Bypass -File ""%~dp0start-ui-clean.ps1""", "", "runas", 1 >> "%temp%\elevate.vbs" 14 | 15 | :: Execute the VBS script 16 | start "" "%temp%\elevate.vbs" 17 | 18 | :: Delete the temporary VBS script 19 | timeout /t 1 > nul 20 | del "%temp%\elevate.vbs" 21 | 22 | echo. 23 | echo If you approved the UAC prompt, the server should be starting... 24 | echo. 25 | echo This window can be closed. 26 | echo. -------------------------------------------------------------------------------- /scripts/server/run-ui-clean.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo NEXT.JS SERVER LAUNCHER (CLEAN) 3 | echo ============================== 4 | echo. 5 | echo This batch file will start the PowerShell script 6 | echo to clean port 3001 and launch the Next.js server. 7 | echo. 8 | 9 | powershell -ExecutionPolicy Bypass -File "%~dp0start-ui-clean.ps1" 10 | 11 | echo. 12 | echo Server has been stopped or failed to start. 13 | echo. 14 | pause -------------------------------------------------------------------------------- /scripts/server/serve-test-page.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo SIMPLE TEST PAGE SERVER 3 | echo ====================== 4 | echo. 5 | echo This will serve a simple test HTML page on port 8000 6 | echo to verify browser connectivity without Next.js 7 | echo. 8 | 9 | REM Kill any existing processes on port 8000 10 | FOR /F "tokens=5" %%P IN ('netstat -ano ^| findstr :8000 ^| findstr LISTENING') DO ( 11 | echo Killing existing process on port 8000 (PID: %%P) 12 | taskkill /F /PID %%P >nul 2>&1 13 | ) 14 | 15 | cd ui 16 | echo Starting server for test-page.html on port 8000... 17 | echo. 18 | echo Access the test page at: http://localhost:8000/test-page.html 19 | echo. 20 | echo Press Ctrl+C to stop the server when done 21 | echo. 22 | 23 | python -m http.server 8000 24 | 25 | cd .. 26 | pause -------------------------------------------------------------------------------- /scripts/server/start-ui-clean.ps1: -------------------------------------------------------------------------------- 1 | # Start-UI-Clean.ps1 2 | # A robust script to kill any process using port 3001 and start the Next.js server 3 | 4 | # Function to check if running as administrator 5 | function Test-Admin { 6 | $currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) 7 | return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 8 | } 9 | 10 | # Function to find and kill processes using port 3001 11 | function Clear-Port3001 { 12 | Write-Host "Checking for processes using port 3001..." -ForegroundColor Cyan 13 | 14 | try { 15 | # Try to get TCP connections using port 3001 16 | $connections = Get-NetTCPConnection -LocalPort 3001 -State Listen -ErrorAction SilentlyContinue 17 | 18 | if ($connections) { 19 | foreach ($conn in $connections) { 20 | $process = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue 21 | 22 | if ($process) { 23 | Write-Host "Found process using port 3001: $($process.ProcessName) (PID: $($process.Id))" -ForegroundColor Yellow 24 | Write-Host "Stopping process..." -ForegroundColor Yellow 25 | 26 | Stop-Process -Id $process.Id -Force 27 | Write-Host "Process terminated." -ForegroundColor Green 28 | } 29 | } 30 | } else { 31 | Write-Host "No processes found using port 3001 via network connections." -ForegroundColor Green 32 | } 33 | } catch { 34 | Write-Host "Error checking network connections: $_" -ForegroundColor Red 35 | } 36 | 37 | # Alternative method: Find Node.js processes that might be using the port 38 | Write-Host "Checking for Node.js processes that might be using port 3001..." -ForegroundColor Cyan 39 | $nodeProcesses = Get-Process -Name "node" -ErrorAction SilentlyContinue 40 | 41 | if ($nodeProcesses) { 42 | Write-Host "Found Node.js processes. Terminating them to ensure port 3001 is available..." -ForegroundColor Yellow 43 | foreach ($proc in $nodeProcesses) { 44 | Write-Host "Stopping Node.js process (PID: $($proc.Id))..." -ForegroundColor Yellow 45 | Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue 46 | } 47 | Write-Host "All Node.js processes terminated." -ForegroundColor Green 48 | } else { 49 | Write-Host "No Node.js processes found." -ForegroundColor Green 50 | } 51 | 52 | # Wait a moment for processes to fully terminate 53 | Start-Sleep -Seconds 2 54 | 55 | # Verify the port is now available 56 | try { 57 | $testServer = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Parse("127.0.0.1"), 3001) 58 | $testServer.Start() 59 | $testServer.Stop() 60 | Write-Host "Port 3001 is now available." -ForegroundColor Green 61 | return $true 62 | } catch { 63 | Write-Host "Port 3001 is still in use! Please restart your computer if the issue persists." -ForegroundColor Red 64 | return $false 65 | } 66 | } 67 | 68 | # Main script execution 69 | Clear-Host 70 | Write-Host "====================================" -ForegroundColor Cyan 71 | Write-Host " NEXT.JS SERVER LAUNCHER (CLEAN) " -ForegroundColor Cyan 72 | Write-Host "====================================" -ForegroundColor Cyan 73 | Write-Host "" 74 | Write-Host "This script will:" 75 | Write-Host " 1. Kill any process using port 3001" 76 | Write-Host " 2. Verify port 3001 is available" 77 | Write-Host " 3. Start the Next.js server" 78 | Write-Host "" 79 | 80 | # Check if we're admin for more reliable process killing 81 | if (-not (Test-Admin)) { 82 | Write-Host "NOTE: This script is not running as Administrator." -ForegroundColor Yellow 83 | Write-Host "Some processes may not be killable without admin rights." -ForegroundColor Yellow 84 | Write-Host "" 85 | } 86 | 87 | # Clear port 3001 88 | $portCleared = Clear-Port3001 89 | 90 | if ($portCleared) { 91 | # Change to the ui directory and start the Next.js server 92 | Write-Host "" 93 | Write-Host "Starting Next.js server..." -ForegroundColor Cyan 94 | Write-Host "Press Ctrl+C to stop the server" -ForegroundColor Yellow 95 | Write-Host "" 96 | 97 | # Navigate to UI directory and start server 98 | Set-Location -Path "$PSScriptRoot\ui" 99 | 100 | # Start the Next.js development server 101 | npm run dev 102 | } else { 103 | Write-Host "" 104 | Write-Host "Failed to clear port 3001. Cannot start Next.js server." -ForegroundColor Red 105 | Write-Host "Try restarting your computer to release all network resources." -ForegroundColor Yellow 106 | Write-Host "" 107 | Read-Host "Press Enter to exit" 108 | } -------------------------------------------------------------------------------- /src/cli/commands/test-anthropic.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test command for Anthropic provider 3 | */ 4 | import { Command } from 'commander'; 5 | import chalk from 'chalk'; 6 | import { AnthropicProvider } from '../../services/anthropicProvider.js'; 7 | 8 | export const testAnthropicCommand = new Command('test-anthropic') 9 | .description('Test Anthropic API integration') 10 | .option('-p, --prompt ', 'Custom prompt to send', 'Explain the basic concept of a moving average in trading in 2-3 sentences.') 11 | .option('-m, --model ', 'Model to use (opus, sonnet, haiku)', 'sonnet') 12 | .action(async (options) => { 13 | console.log(chalk.cyan('Testing Anthropic API integration...')); 14 | 15 | // Map model shorthand to full model name 16 | const modelMap: Record = { 17 | 'opus': 'claude-3-opus-20240229', 18 | 'sonnet': 'claude-3-sonnet-20240229', 19 | 'haiku': 'claude-3-haiku-20240307' 20 | }; 21 | 22 | const modelName = modelMap[options.model] || options.model; 23 | 24 | try { 25 | const provider = new AnthropicProvider(); 26 | 27 | console.log(chalk.yellow(`Sending prompt to ${modelName}:`)); 28 | console.log(options.prompt); 29 | console.log(chalk.yellow('Response:')); 30 | 31 | const startTime = Date.now(); 32 | const response = await provider.sendPrompt(options.prompt, { 33 | model: modelName 34 | }); 35 | const endTime = Date.now(); 36 | 37 | console.log(chalk.green(response)); 38 | console.log(chalk.yellow(`Response time: ${(endTime - startTime) / 1000} seconds`)); 39 | 40 | } catch (error) { 41 | console.error(chalk.red(`Error testing Anthropic API: ${error}`)); 42 | } 43 | }); -------------------------------------------------------------------------------- /src/cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * PineScript MCP CLI 5 | * 6 | * Command-line interface for the PineScript MCP server 7 | */ 8 | 9 | import { Command } from 'commander'; 10 | import chalk from 'chalk'; 11 | import { llmCommand } from './commands/llm.js'; 12 | import { testAnthropicCommand } from './commands/test-anthropic.js'; 13 | import templatesCommand from './commands/templates.js'; 14 | 15 | // Initialize the CLI program 16 | const program = new Command() 17 | .name('pinescript-mcp') 18 | .description('PineScript MCP CLI - Command-line interface for PineScript tools') 19 | .version('1.0.0'); 20 | 21 | // Add all commands 22 | program.addCommand(llmCommand); 23 | program.addCommand(testAnthropicCommand); 24 | program.addCommand(templatesCommand); 25 | 26 | // Add help information 27 | program 28 | .addHelpText('after', ` 29 | Example usage: 30 | $ pinescript-mcp llm analyze my-strategy.pine 31 | $ pinescript-mcp llm enhance my-strategy.pine -o enhanced-strategies 32 | $ pinescript-mcp llm config --provider openai --openai-key your-api-key 33 | $ pinescript-mcp llm config --provider anthropic --anthropic-key your-api-key 34 | $ pinescript-mcp test-anthropic -p "Describe RSI oscillator" -m haiku 35 | $ pinescript-mcp templates list 36 | $ pinescript-mcp templates test 37 | $ pinescript-mcp templates sync 38 | $ pinescript-mcp templates optimize strategy.pine -p backtest.txt 39 | $ pinescript-mcp templates search "volatility strategy"`); 40 | 41 | // Parse arguments 42 | program.parse(process.argv); 43 | 44 | // Display help if no commands provided 45 | if (!process.argv.slice(2).length) { 46 | program.outputHelp(); 47 | } -------------------------------------------------------------------------------- /src/config/configTool.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration Tool for PineScript MCP 3 | * 4 | * Provides MCP tools for managing user configuration 5 | */ 6 | 7 | import { FastMCP } from 'fastmcp'; 8 | import { z } from 'zod'; 9 | import { config, loadUserConfig, saveUserConfig, resetConfig, PineScriptConfig } from './userConfig'; 10 | 11 | export function registerConfigTools(mcp: FastMCP): void { 12 | // Register the MCP tools for configuration management 13 | 14 | // Get current configuration 15 | mcp.addTool({ 16 | name: 'get_pinescript_config', 17 | description: 'Get the current PineScript MCP configuration', 18 | parameters: z.object({}), 19 | execute: async () => { 20 | const currentConfig = loadUserConfig(); 21 | return JSON.stringify({ 22 | config: currentConfig, 23 | success: true 24 | }); 25 | } 26 | }); 27 | 28 | // Update configuration 29 | mcp.addTool({ 30 | name: 'update_pinescript_config', 31 | description: 'Update PineScript MCP configuration settings', 32 | parameters: z.object({ 33 | config: z.record(z.any()).describe('Configuration object with the settings to update') 34 | }), 35 | execute: async ({ config: newConfig }) => { 36 | try { 37 | const updatedConfig = saveUserConfig(newConfig); 38 | return JSON.stringify({ 39 | config: updatedConfig, 40 | success: true, 41 | message: 'Configuration updated successfully' 42 | }); 43 | } catch (error) { 44 | return JSON.stringify({ 45 | success: false, 46 | message: `Failed to update configuration: ${error}`, 47 | error: String(error) 48 | }); 49 | } 50 | } 51 | }); 52 | 53 | // Reset configuration 54 | mcp.addTool({ 55 | name: 'reset_pinescript_config', 56 | description: 'Reset PineScript MCP configuration to default values', 57 | parameters: z.object({}), 58 | execute: async () => { 59 | try { 60 | const defaultConfig = resetConfig(); 61 | return JSON.stringify({ 62 | config: defaultConfig, 63 | success: true, 64 | message: 'Configuration reset to defaults' 65 | }); 66 | } catch (error) { 67 | return JSON.stringify({ 68 | success: false, 69 | message: `Failed to reset configuration: ${error}`, 70 | error: String(error) 71 | }); 72 | } 73 | } 74 | }); 75 | 76 | // Get specific config section 77 | mcp.addTool({ 78 | name: 'get_config_section', 79 | description: 'Get a specific section of the PineScript MCP configuration', 80 | parameters: z.object({ 81 | section: z.string().describe('The configuration section to retrieve') 82 | }), 83 | execute: async ({ section }) => { 84 | const currentConfig = loadUserConfig(); 85 | const sectionConfig = currentConfig[section as keyof PineScriptConfig]; 86 | 87 | if (!sectionConfig) { 88 | return JSON.stringify({ 89 | success: false, 90 | message: `Section '${section}' not found in configuration`, 91 | availableSections: Object.keys(currentConfig) 92 | }); 93 | } 94 | 95 | return JSON.stringify({ 96 | config: sectionConfig, 97 | success: true 98 | }); 99 | } 100 | }); 101 | 102 | // Set custom templates directory 103 | mcp.addTool({ 104 | name: 'set_templates_directory', 105 | description: 'Set a custom directory for PineScript templates', 106 | parameters: z.object({ 107 | directory: z.string().describe('Path to custom templates directory') 108 | }), 109 | execute: async ({ directory }) => { 110 | try { 111 | // Get current config first 112 | const currentConfig = loadUserConfig(); 113 | 114 | // Create update with all required fields 115 | const updatedConfig = saveUserConfig({ 116 | templates: { 117 | defaultVersion: currentConfig.templates.defaultVersion, 118 | customTemplatesDir: directory 119 | } 120 | }); 121 | 122 | return JSON.stringify({ 123 | success: true, 124 | message: `Templates directory set to ${directory}`, 125 | config: updatedConfig.templates 126 | }); 127 | } catch (error) { 128 | return JSON.stringify({ 129 | success: false, 130 | message: `Failed to set templates directory: ${error}`, 131 | error: String(error) 132 | }); 133 | } 134 | } 135 | }); 136 | } -------------------------------------------------------------------------------- /src/config/protocolConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Protocol Configuration 3 | * 4 | * Contains configuration settings for the MCP protocol 5 | */ 6 | 7 | /** 8 | * Default protocol configuration settings 9 | */ 10 | export const DEFAULT_PROTOCOL_CONFIG = { 11 | /** 12 | * Timeout in milliseconds for MCP requests 13 | * Default value is increased to 5 minutes (300000ms) from the SDK default of 60 seconds 14 | */ 15 | timeout: 300000 16 | }; -------------------------------------------------------------------------------- /src/config/protocolConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Protocol Configuration 3 | * 4 | * This module provides configuration options for the MCP protocol 5 | * including default timeout settings that override the previous 6 | * 60 second timeout. 7 | */ 8 | 9 | /** 10 | * Default protocol configuration 11 | */ 12 | export const DEFAULT_PROTOCOL_CONFIG = { 13 | /** 14 | * Timeout for individual requests in milliseconds 15 | * Default is 300000 (5 minutes) 16 | */ 17 | timeout: 300000, // 5 minutes 18 | 19 | /** 20 | * Whether progress notifications should reset the timeout 21 | * Default is true 22 | */ 23 | resetTimeoutOnProgress: true, 24 | 25 | /** 26 | * Interval for ping requests in milliseconds 27 | * Default is 60000 (1 minute) 28 | */ 29 | pingInterval: 60000, // 1 minute 30 | 31 | /** 32 | * Maximum ping attempts before giving up 33 | * Default is 3 34 | */ 35 | maxPingAttempts: 3 36 | }; 37 | 38 | /** 39 | * Validation specific configuration 40 | */ 41 | export const VALIDATION_CONFIG = { 42 | /** 43 | * Maximum time for validating a single script in milliseconds 44 | * Default is 180000 (3 minutes) 45 | */ 46 | maxValidationTime: 180000, // 3 minutes 47 | 48 | /** 49 | * Interval for checking elapsed time during validation 50 | * Default is 500ms 51 | */ 52 | validationCheckInterval: 500, 53 | 54 | /** 55 | * Maximum script size (in characters) before warning 56 | * Default is 10000 characters 57 | */ 58 | maxScriptSizeWarningThreshold: 10000 59 | }; -------------------------------------------------------------------------------- /src/db/supabaseClient.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Supabase Client for PineScript MCP 3 | * 4 | * This module provides a Supabase client for database operations, including 5 | * template storage, user management, and analytics. 6 | */ 7 | 8 | import { createClient } from '@supabase/supabase-js'; 9 | import { config } from '../config/userConfig.js'; 10 | import dotenv from 'dotenv'; 11 | 12 | // Load environment variables if not already loaded 13 | dotenv.config(); 14 | 15 | // Initialize Supabase client 16 | const supabaseUrl = process.env.SUPABASE_URL || config.databases?.supabase?.url || ''; 17 | const supabaseKey = process.env.SUPABASE_API_KEY || config.databases?.supabase?.apiKey || ''; 18 | 19 | // Validate required configuration 20 | if (!supabaseUrl || !supabaseKey) { 21 | console.warn('Supabase configuration missing. Some features may not work properly.'); 22 | } 23 | 24 | // Create client with error handling 25 | let supabaseClient: any = null; 26 | 27 | try { 28 | if (supabaseUrl && supabaseKey) { 29 | supabaseClient = createClient(supabaseUrl, supabaseKey, { 30 | auth: { 31 | persistSession: false, 32 | autoRefreshToken: true, 33 | }, 34 | }); 35 | console.log('Supabase client initialized successfully'); 36 | } 37 | } catch (error) { 38 | console.error('Error initializing Supabase client:', error); 39 | } 40 | 41 | /** 42 | * Check if the Supabase client is available and connected 43 | */ 44 | export async function isSupabaseAvailable(): Promise { 45 | if (!supabaseClient) { 46 | return false; 47 | } 48 | 49 | try { 50 | // Simple health check query 51 | const { error } = await supabaseClient.from('health').select('*').limit(1); 52 | 53 | if (error && error.code !== 'PGRST116') { 54 | // PGRST116 means table doesn't exist, which is fine for this check 55 | console.warn('Supabase connection test failed:', error); 56 | return false; 57 | } 58 | 59 | return true; 60 | } catch (error) { 61 | console.error('Error in Supabase health check:', error); 62 | return false; 63 | } 64 | } 65 | 66 | /** 67 | * Get the Supabase client instance 68 | */ 69 | export function getSupabaseClient() { 70 | if (!supabaseClient) { 71 | throw new Error('Supabase client is not initialized'); 72 | } 73 | return supabaseClient; 74 | } 75 | 76 | // Export default client 77 | export default supabaseClient; -------------------------------------------------------------------------------- /src/db/vector/dbSetup.sql: -------------------------------------------------------------------------------- 1 | -- pgvector setup script for Supabase 2 | -- This script creates the pgvector extension and necessary functions 3 | -- Run this in the Supabase SQL editor 4 | 5 | -- Function to check if pgvector extension exists 6 | CREATE OR REPLACE FUNCTION public.check_pgvector() 7 | RETURNS boolean 8 | LANGUAGE plpgsql 9 | SECURITY DEFINER 10 | AS $$ 11 | BEGIN 12 | RETURN EXISTS ( 13 | SELECT 1 14 | FROM pg_extension 15 | WHERE extname = 'vector' 16 | ); 17 | END; 18 | $$; 19 | 20 | -- Function to create pgvector extension if it doesn't exist 21 | CREATE OR REPLACE FUNCTION public.create_pgvector_extension() 22 | RETURNS boolean 23 | LANGUAGE plpgsql 24 | SECURITY DEFINER 25 | AS $$ 26 | BEGIN 27 | IF NOT EXISTS ( 28 | SELECT 1 29 | FROM pg_extension 30 | WHERE extname = 'vector' 31 | ) THEN 32 | CREATE EXTENSION vector; 33 | RETURN true; 34 | ELSE 35 | RETURN false; 36 | END IF; 37 | EXCEPTION 38 | WHEN OTHERS THEN 39 | RAISE EXCEPTION 'Failed to create pgvector extension: %', SQLERRM; 40 | RETURN false; 41 | END; 42 | $$; 43 | 44 | -- Function to create embeddings table with proper structure 45 | CREATE OR REPLACE FUNCTION public.create_embeddings_table( 46 | table_name text, 47 | embedding_column text DEFAULT 'embedding' 48 | ) 49 | RETURNS boolean 50 | LANGUAGE plpgsql 51 | SECURITY DEFINER 52 | AS $$ 53 | DECLARE 54 | create_table_query text; 55 | create_index_query text; 56 | BEGIN 57 | -- Generate the query to create the table 58 | create_table_query := format(' 59 | CREATE TABLE public.%I ( 60 | id text PRIMARY KEY, 61 | content text NOT NULL, 62 | %I vector(1536), 63 | metadata jsonb DEFAULT ''{}''::jsonb, 64 | created_at timestamp with time zone DEFAULT timezone(''utc''::text, now()), 65 | updated_at timestamp with time zone DEFAULT timezone(''utc''::text, now()) 66 | ); 67 | ', table_name, embedding_column); 68 | 69 | -- Generate the query to create the index 70 | create_index_query := format(' 71 | CREATE INDEX IF NOT EXISTS %I ON public.%I 72 | USING ivfflat (%I vector_cosine_ops) 73 | WITH (lists = 100); 74 | ', table_name || '_vector_idx', table_name, embedding_column); 75 | 76 | -- Execute the queries 77 | EXECUTE create_table_query; 78 | EXECUTE create_index_query; 79 | 80 | -- Enable RLS 81 | EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY;', table_name); 82 | 83 | -- Create policies 84 | EXECUTE format(' 85 | CREATE POLICY "Enable read access for all users" ON public.%I 86 | FOR SELECT USING (true); 87 | ', table_name); 88 | 89 | EXECUTE format(' 90 | CREATE POLICY "Enable insert for authenticated users only" ON public.%I 91 | FOR INSERT WITH CHECK (auth.role() = ''authenticated''); 92 | ', table_name); 93 | 94 | EXECUTE format(' 95 | CREATE POLICY "Enable update for authenticated users only" ON public.%I 96 | FOR UPDATE USING (auth.role() = ''authenticated''); 97 | ', table_name); 98 | 99 | EXECUTE format(' 100 | CREATE POLICY "Enable delete for authenticated users only" ON public.%I 101 | FOR DELETE USING (auth.role() = ''authenticated''); 102 | ', table_name); 103 | 104 | RETURN true; 105 | EXCEPTION 106 | WHEN OTHERS THEN 107 | RAISE EXCEPTION 'Failed to create embeddings table: %', SQLERRM; 108 | RETURN false; 109 | END; 110 | $$; 111 | 112 | -- Function to search for similar embeddings 113 | CREATE OR REPLACE FUNCTION public.match_embeddings( 114 | query_embedding vector(1536), 115 | match_threshold float DEFAULT 0.75, 116 | match_count int DEFAULT 10, 117 | table_name text DEFAULT 'template_embeddings', 118 | embedding_column text DEFAULT 'embedding' 119 | ) 120 | RETURNS TABLE ( 121 | id text, 122 | content text, 123 | metadata jsonb, 124 | similarity float 125 | ) 126 | LANGUAGE plpgsql 127 | SECURITY DEFINER 128 | AS $$ 129 | DECLARE 130 | query_text text; 131 | BEGIN 132 | query_text := format(' 133 | SELECT 134 | id, 135 | content, 136 | metadata, 137 | 1 - (%I <=> $1) as similarity 138 | FROM 139 | public.%I 140 | WHERE 141 | 1 - (%I <=> $1) > $2 142 | ORDER BY 143 | similarity DESC 144 | LIMIT $3; 145 | ', embedding_column, table_name, embedding_column); 146 | 147 | RETURN QUERY EXECUTE query_text USING query_embedding, match_threshold, match_count; 148 | END; 149 | $$; 150 | 151 | -- Create embeddings table if it doesn't exist yet 152 | SELECT public.create_pgvector_extension(); 153 | SELECT public.create_embeddings_table('template_embeddings', 'embedding'); -------------------------------------------------------------------------------- /src/patched-protocol.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * TypeScript declarations for patched-protocol.js 3 | */ 4 | 5 | // Re-export everything from the original protocol module 6 | export * from '@modelcontextprotocol/sdk/dist/esm/shared/protocol.js'; 7 | 8 | /** 9 | * Custom timeout value that overrides the default in the MCP SDK 10 | * Set to 5 minutes (300000ms) instead of the default 60 seconds 11 | */ 12 | export const DEFAULT_REQUEST_TIMEOUT: number; 13 | 14 | /** 15 | * Helper function to create request options with the extended timeout 16 | * @param options - Optional user-provided request options 17 | * @returns Request options with extended timeout 18 | */ 19 | export function createRequestOptions(options?: Record): Record; -------------------------------------------------------------------------------- /src/patched-protocol.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Patched MCP Protocol Module 3 | * 4 | * This module patches the default timeout value in the MCP SDK protocol 5 | * to fix the "MCP error -32001: Request timed out" error. 6 | */ 7 | 8 | // Import original protocol 9 | import * as protocol from '@modelcontextprotocol/sdk/dist/esm/shared/protocol.js'; 10 | 11 | // Log that we're applying the patch 12 | console.log('Applying MCP protocol patch: Extending default timeout to 5 minutes'); 13 | 14 | // Override the default timeout constant 15 | const DEFAULT_REQUEST_TIMEOUT = 300000; // 5 minutes instead of 60 seconds 16 | 17 | /** 18 | * Helper function to create request options with extended timeout 19 | * @param {Object} options - Optional user-provided request options 20 | * @returns {Object} Request options with extended timeout 21 | */ 22 | export function createRequestOptions(options = {}) { 23 | return { 24 | ...options, 25 | timeout: options.timeout || DEFAULT_REQUEST_TIMEOUT 26 | }; 27 | } 28 | 29 | // Export everything from the original module and our patched values 30 | export * from '@modelcontextprotocol/sdk/dist/esm/shared/protocol.js'; 31 | export { DEFAULT_REQUEST_TIMEOUT }; -------------------------------------------------------------------------------- /src/templates/indicators/bollingerBands.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Bollinger Bands Indicator Template 3 | * 4 | * This indicator plots Bollinger Bands, which are a volatility-based envelope around a moving average. 5 | * Bollinger Bands consist of: 6 | * - A middle band (moving average) 7 | * - An upper band (middle band + standard deviations) 8 | * - A lower band (middle band - standard deviations) 9 | * 10 | * Default parameters: 11 | * - Length: 20 periods 12 | * - Standard Deviation Multiplier: 2 13 | * - Source: close price 14 | */ 15 | 16 | export const bollingerBandsTemplate = ` 17 | //@version=5 18 | indicator("Bollinger Bands", overlay=true) 19 | 20 | // Input Parameters 21 | length = input(20, "Length") 22 | mult = input.float(2.0, "Std Dev Multiplier", minval=0.1, maxval=5) 23 | src = input(close, "Source") 24 | 25 | // Calculate Bollinger Bands 26 | basis = ta.sma(src, length) 27 | stdev = ta.stdev(src, length) 28 | upper = basis + mult * stdev 29 | lower = basis - mult * stdev 30 | 31 | // Plot Bands 32 | plot(basis, "Basis", color=color.yellow) 33 | p1 = plot(upper, "Upper", color=color.blue) 34 | p2 = plot(lower, "Lower", color=color.blue) 35 | fill(p1, p2, color=color.new(color.blue, 95)) 36 | 37 | // Calculate %B 38 | percentB = (src - lower) / (upper - lower) 39 | 40 | // Calculate Bandwidth 41 | bandwidth = (upper - lower) / basis * 100 42 | 43 | // Alerts 44 | upperCross = ta.crossover(src, upper) 45 | lowerCross = ta.crossunder(src, lower) 46 | middleCrossUp = ta.crossover(src, basis) 47 | middleCrossDown = ta.crossunder(src, basis) 48 | 49 | // Alert conditions 50 | alertcondition(upperCross, "Price crossed above upper band", "Price crossed above the upper Bollinger Band") 51 | alertcondition(lowerCross, "Price crossed below lower band", "Price crossed below the lower Bollinger Band") 52 | alertcondition(middleCrossUp, "Price crossed above middle band", "Price crossed above the middle Bollinger Band") 53 | alertcondition(middleCrossDown, "Price crossed below middle band", "Price crossed below the middle Bollinger Band") 54 | `; -------------------------------------------------------------------------------- /src/templates/indicators/macd.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MACD Indicator Template 3 | * 4 | * The Moving Average Convergence Divergence (MACD) is a trend-following momentum indicator 5 | * that shows the relationship between two moving averages of a security's price. 6 | * 7 | * MACD is calculated by subtracting the 26-period EMA from the 12-period EMA. 8 | * A 9-period EMA of the MACD, called the "signal line", is then plotted on top of the MACD. 9 | * 10 | * Default parameters: 11 | * - Fast Length: 12 periods 12 | * - Slow Length: 26 periods 13 | * - Signal Length: 9 periods 14 | * - Source: close price 15 | */ 16 | 17 | export const macdTemplate = ` 18 | //@version=5 19 | indicator("MACD - Moving Average Convergence/Divergence", shorttitle="MACD") 20 | 21 | // Input Parameters 22 | fastLength = input(12, "Fast Length") 23 | slowLength = input(26, "Slow Length") 24 | signalLength = input(9, "Signal Length") 25 | src = input(close, "Source") 26 | 27 | // Calculate MACD 28 | fastMA = ta.ema(src, fastLength) 29 | slowMA = ta.ema(src, slowLength) 30 | macd = fastMA - slowMA 31 | signal = ta.ema(macd, signalLength) 32 | histogram = macd - signal 33 | 34 | // Plot MACD 35 | plot(macd, "MACD", color=color.blue) 36 | plot(signal, "Signal", color=color.orange) 37 | plot(histogram, "Histogram", color=(histogram >= 0 ? (histogram[1] < histogram ? color.green : color.lime) : (histogram[1] > histogram ? color.red : color.maroon)), style=plot.style_columns) 38 | hline(0, "Zero Line", color=color.gray) 39 | 40 | // Calculate Signal Crossings 41 | signalCrossUp = ta.crossover(macd, signal) 42 | signalCrossDown = ta.crossunder(macd, signal) 43 | zeroLineUp = ta.crossover(macd, 0) 44 | zeroLineDown = ta.crossunder(macd, 0) 45 | 46 | // Alert Conditions 47 | alertcondition(signalCrossUp, "MACD crossed above Signal", "MACD Line crossed above Signal Line") 48 | alertcondition(signalCrossDown, "MACD crossed below Signal", "MACD Line crossed below Signal Line") 49 | alertcondition(zeroLineUp, "MACD crossed above Zero", "MACD Line crossed above Zero Line") 50 | alertcondition(zeroLineDown, "MACD crossed below Zero", "MACD Line crossed below Zero Line") 51 | `; -------------------------------------------------------------------------------- /src/templates/simplified-gold-strategy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simplified version of the Gold Scalping strategy 3 | * This smaller version will be easier to validate with the MCP server 4 | */ 5 | 6 | export const simplifiedGoldStrategy = `//@version=6 7 | strategy("Simple Gold Scalping Strategy", overlay=true) 8 | 9 | // Simple Moving Average Filter 10 | fastMA = ta.ema(close, 20) 11 | slowMA = ta.ema(close, 50) 12 | uptrend = fastMA > slowMA 13 | downtrend = fastMA < slowMA 14 | 15 | // Simple Entry Conditions 16 | buyCondition = ta.crossover(close, fastMA) and uptrend 17 | sellCondition = ta.crossunder(close, fastMA) and downtrend 18 | 19 | // Basic Stop Loss & Take Profit 20 | longSL = low - low * 0.01 21 | shortSL = high + high * 0.01 22 | longTP = close + (close - longSL) * 1.5 23 | shortTP = close - (shortSL - close) * 1.5 24 | 25 | // Execute Trades 26 | if (buyCondition) 27 | strategy.entry("Long", strategy.long) 28 | strategy.exit("Exit Long", from_entry="Long", stop=longSL, limit=longTP) 29 | 30 | if (sellCondition) 31 | strategy.entry("Short", strategy.short) 32 | strategy.exit("Exit Short", from_entry="Short", stop=shortSL, limit=shortTP) 33 | 34 | // Plot Buy/Sell Signals 35 | plotshape(series=buyCondition, location=location.belowbar, color=color.green, style=shape.labelup, title="BUY") 36 | plotshape(series=sellCondition, location=location.abovebar, color=color.red, style=shape.labeldown, title="SELL") 37 | 38 | // Plot Moving Averages 39 | plot(fastMA, title="Fast EMA", color=color.green, linewidth=1) 40 | plot(slowMA, title="Slow EMA", color=color.red, linewidth=1) 41 | `; 42 | 43 | /** 44 | * Even simpler version for first validation test 45 | */ 46 | export const miniGoldStrategy = `//@version=6 47 | strategy("Mini Gold Strategy", overlay=true) 48 | 49 | // Simple Moving Average 50 | ma = ta.sma(close, 20) 51 | 52 | // Basic Entry Conditions 53 | buyCondition = close > ma and close > close[1] 54 | sellCondition = close < ma and close < close[1] 55 | 56 | // Execute Trades 57 | if (buyCondition) 58 | strategy.entry("Long", strategy.long) 59 | 60 | if (sellCondition) 61 | strategy.entry("Short", strategy.short) 62 | 63 | // Plot MA 64 | plot(ma, title="SMA", color=color.blue, linewidth=1) 65 | `; 66 | 67 | /** 68 | * Export the strategies 69 | */ 70 | export default { 71 | simplifiedGoldStrategy, 72 | miniGoldStrategy 73 | }; -------------------------------------------------------------------------------- /src/templates/strategies/movingAverageCross.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Moving Average Crossover Strategy Template 3 | * 4 | * This strategy generates buy signals when a fast moving average crosses above a slow moving average, 5 | * and sell signals when the fast moving average crosses below the slow moving average. 6 | * 7 | * Default parameters: 8 | * - Fast MA Length: 9 periods 9 | * - Slow MA Length: 21 periods 10 | * - MA Type: SMA (Simple Moving Average) 11 | */ 12 | 13 | export const movingAverageCrossTemplate = ` 14 | //@version=5 15 | strategy("Moving Average Crossover Strategy", overlay=true) 16 | 17 | // Input Parameters 18 | fastLength = input(9, "Fast MA Length") 19 | slowLength = input(21, "Slow MA Length") 20 | maType = input.string("SMA", "MA Type", options=["SMA", "EMA", "WMA", "VWMA"]) 21 | 22 | // Calculate Moving Averages 23 | fastMA = switch maType 24 | "SMA" => ta.sma(close, fastLength) 25 | "EMA" => ta.ema(close, fastLength) 26 | "WMA" => ta.wma(close, fastLength) 27 | "VWMA" => ta.vwma(close, fastLength) 28 | 29 | slowMA = switch maType 30 | "SMA" => ta.sma(close, slowLength) 31 | "EMA" => ta.ema(close, slowLength) 32 | "WMA" => ta.wma(close, slowLength) 33 | "VWMA" => ta.vwma(close, slowLength) 34 | 35 | // Generate Trading Signals 36 | buySignal = ta.crossover(fastMA, slowMA) 37 | sellSignal = ta.crossunder(fastMA, slowMA) 38 | 39 | // Execute Strategy 40 | if (buySignal) 41 | strategy.entry("Buy", strategy.long) 42 | 43 | if (sellSignal) 44 | strategy.close("Buy") 45 | 46 | // Plot Moving Averages 47 | plot(fastMA, "Fast MA", color=#00BFFF, linewidth=2) 48 | plot(slowMA, "Slow MA", color=#FF6347, linewidth=2) 49 | 50 | // Plot Buy/Sell Signals 51 | plotshape(buySignal, "Buy Signal", shape.triangleup, location.belowbar, color=color.green, size=size.small) 52 | plotshape(sellSignal, "Sell Signal", shape.triangledown, location.abovebar, color=color.red, size=size.small) 53 | `; -------------------------------------------------------------------------------- /src/templates/strategies/rsiStrategy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * RSI Strategy Template 3 | * 4 | * This strategy uses the Relative Strength Index (RSI) to generate buy and sell signals. 5 | * Buy when RSI crosses below the oversold level and then back above it. 6 | * Sell when RSI crosses above the overbought level and then back below it. 7 | * 8 | * Default parameters: 9 | * - RSI Length: 14 periods 10 | * - Overbought Level: 70 11 | * - Oversold Level: 30 12 | */ 13 | 14 | export const rsiStrategyTemplate = ` 15 | //@version=5 16 | strategy("RSI Strategy", overlay=false) 17 | 18 | // Input Parameters 19 | rsiLength = input(14, "RSI Length") 20 | overboughtLevel = input(70, "Overbought Level", minval=50, maxval=100) 21 | oversoldLevel = input(30, "Oversold Level", minval=0, maxval=50) 22 | 23 | // Calculate RSI 24 | rsiValue = ta.rsi(close, rsiLength) 25 | 26 | // Detect RSI crosses 27 | crossedBelowOversold = ta.crossunder(rsiValue, oversoldLevel) 28 | crossedAboveOversold = ta.crossover(rsiValue, oversoldLevel) 29 | crossedAboveOverbought = ta.crossover(rsiValue, overboughtLevel) 30 | crossedBelowOverbought = ta.crossunder(rsiValue, overboughtLevel) 31 | 32 | // State variables to track RSI conditions 33 | var belowOversold = false 34 | var aboveOverbought = false 35 | 36 | // Update state based on crosses 37 | if crossedBelowOversold 38 | belowOversold := true 39 | 40 | if crossedAboveOverbought 41 | aboveOverbought := true 42 | 43 | // Generate buy signal when RSI crosses back above oversold after being below 44 | buySignal = belowOversold and crossedAboveOversold 45 | if buySignal 46 | belowOversold := false 47 | 48 | // Generate sell signal when RSI crosses back below overbought after being above 49 | sellSignal = aboveOverbought and crossedBelowOverbought 50 | if sellSignal 51 | aboveOverbought := false 52 | 53 | // Execute Strategy 54 | if (buySignal) 55 | strategy.entry("Buy", strategy.long) 56 | 57 | if (sellSignal) 58 | strategy.close("Buy") 59 | 60 | // Plot RSI and levels 61 | plot(rsiValue, "RSI", color=color.purple) 62 | hline(overboughtLevel, "Overbought Level", color=color.red) 63 | hline(oversoldLevel, "Oversold Level", color=color.green) 64 | hline(50, "Middle Level", color=color.gray, linestyle=hline.style_dotted) 65 | 66 | // Plot signals 67 | plotshape(buySignal, "Buy Signal", shape.triangleup, location.bottom, color=color.green, size=size.small) 68 | plotshape(sellSignal, "Sell Signal", shape.triangledown, location.top, color=color.red, size=size.small) 69 | `; -------------------------------------------------------------------------------- /src/templates/templateManager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PineScript Template Manager 3 | * 4 | * Manages templates for strategies and indicators 5 | */ 6 | 7 | // Import our strategy templates 8 | import { movingAverageCrossTemplate } from './strategies/movingAverageCross.js'; 9 | import { rsiStrategyTemplate } from './strategies/rsiStrategy.js'; 10 | 11 | // Import our indicator templates 12 | import { bollingerBandsTemplate } from './indicators/bollingerBands.js'; 13 | import { macdTemplate } from './indicators/macd.js'; 14 | 15 | /** 16 | * Template types 17 | */ 18 | export enum TemplateType { 19 | STRATEGY = 'strategy', 20 | INDICATOR = 'indicator' 21 | } 22 | 23 | /** 24 | * Gets a template by type and name 25 | * 26 | * @param type The template type 27 | * @param name The template name 28 | * @returns The template code 29 | */ 30 | export function getTemplate(type: TemplateType, name: string): string { 31 | // Get template based on type 32 | if (type === TemplateType.STRATEGY) { 33 | return getStrategyTemplate(name); 34 | } else if (type === TemplateType.INDICATOR) { 35 | return getIndicatorTemplate(name); 36 | } 37 | 38 | throw new Error(`Unknown template type: ${type}`); 39 | } 40 | 41 | /** 42 | * Gets a strategy template by name 43 | * 44 | * @param name The template name 45 | * @returns The strategy template code 46 | */ 47 | function getStrategyTemplate(name: string): string { 48 | // Check if template exists 49 | switch (name.toLowerCase()) { 50 | case 'macd': 51 | return ` 52 | //@version=5 53 | strategy("MACD Strategy", overlay=true) 54 | 55 | // Input parameters 56 | fastLength = input(12, "Fast Length") 57 | slowLength = input(26, "Slow Length") 58 | signalLength = input(9, "Signal Length") 59 | 60 | // Calculate indicators 61 | [macdLine, signalLine, histLine] = ta.macd(close, fastLength, slowLength, signalLength) 62 | 63 | // Define trading conditions 64 | longCondition = ta.crossover(macdLine, signalLine) 65 | shortCondition = ta.crossunder(macdLine, signalLine) 66 | 67 | // Execute strategy 68 | if (longCondition) 69 | strategy.entry("Long", strategy.long) 70 | 71 | if (shortCondition) 72 | strategy.entry("Short", strategy.short) 73 | 74 | // Plot indicators 75 | plot(macdLine, "MACD Line", color.blue) 76 | plot(signalLine, "Signal Line", color.red) 77 | plot(histLine, "Histogram", color.purple, style=plot.style_histogram) 78 | `; 79 | case 'movingaveragecross': 80 | case 'ma_cross': 81 | case 'ma cross': 82 | return movingAverageCrossTemplate; 83 | case 'rsi': 84 | case 'rsistrategy': 85 | case 'rsi strategy': 86 | return rsiStrategyTemplate; 87 | default: 88 | // Return a custom named template 89 | return ` 90 | //@version=5 91 | strategy("${name}", overlay=true) 92 | 93 | // Input parameters 94 | length = input(14, "Length") 95 | 96 | // Your custom strategy logic here 97 | 98 | // Plot indicators 99 | plot(close, "Price", color.blue) 100 | `; 101 | } 102 | } 103 | 104 | /** 105 | * Gets an indicator template by name 106 | * 107 | * @param name The template name 108 | * @returns The indicator template code 109 | */ 110 | function getIndicatorTemplate(name: string): string { 111 | // Check if template exists 112 | switch (name.toLowerCase()) { 113 | case 'rsi': 114 | return ` 115 | //@version=5 116 | indicator("RSI", overlay=false) 117 | 118 | // Input parameters 119 | length = input(14, "Length") 120 | 121 | // Calculate indicator 122 | rsiValue = ta.rsi(close, length) 123 | 124 | // Define levels 125 | overbought = 70 126 | oversold = 30 127 | 128 | // Plot indicator 129 | plot(rsiValue, "RSI", color.purple) 130 | hline(overbought, "Overbought", color.red) 131 | hline(oversold, "Oversold", color.green) 132 | `; 133 | case 'bollinger': 134 | case 'bollingerbands': 135 | case 'bollinger bands': 136 | return bollingerBandsTemplate; 137 | case 'macd': 138 | return macdTemplate; 139 | default: 140 | // Return a custom named template 141 | return ` 142 | //@version=5 143 | indicator("${name}", overlay=false) 144 | 145 | // Input parameters 146 | length = input(14, "Length") 147 | 148 | // Your custom indicator logic here 149 | 150 | // Plot indicator 151 | plot(ta.sma(close, length), "SMA", color.blue) 152 | `; 153 | } 154 | } -------------------------------------------------------------------------------- /src/templates/testScript.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Script for PineScript MCP Server 3 | * 4 | * This module provides minimal scripts for testing MCP server connectivity 5 | */ 6 | 7 | /** 8 | * Minimal PineScript script that can be used to validate server connectivity 9 | * Intentionally simple to avoid timeout issues 10 | */ 11 | export const MINIMAL_TEST_SCRIPT = `//@version=5 12 | indicator("Simple Test Indicator", overlay=true) 13 | 14 | // Simple calculation 15 | value = close 16 | 17 | // Plot 18 | plot(value, "Price", color=color.blue) 19 | `; 20 | 21 | /** 22 | * Returns the minimal test script 23 | * @returns A minimal PineScript indicator for testing 24 | */ 25 | export function getTestScript(): string { 26 | return MINIMAL_TEST_SCRIPT; 27 | } 28 | 29 | /** 30 | * Returns a minimal script with the specified version 31 | * @param version PineScript version to use 32 | * @returns A minimal PineScript script for the specified version 33 | */ 34 | export function getVersionedTestScript(version: string = "5"): string { 35 | return MINIMAL_TEST_SCRIPT.replace("//@version=5", `//@version=${version}`); 36 | } -------------------------------------------------------------------------------- /src/tests/apiKeyTest.js: -------------------------------------------------------------------------------- 1 | // Test for API key loading 2 | import dotenv from 'dotenv'; 3 | import { OpenAI } from 'openai'; 4 | import fs from 'fs'; 5 | 6 | // Load environment variables 7 | dotenv.config(); 8 | 9 | // Function to test API key 10 | async function testApiKey() { 11 | console.log("Testing API key loading..."); 12 | 13 | // Get the API key from environment 14 | const apiKey = process.env.OPENAI_API_KEY; 15 | 16 | if (!apiKey) { 17 | console.error("ERROR: No API key found in environment variables"); 18 | return; 19 | } 20 | 21 | console.log("API key from environment:", apiKey.substring(0, 10) + "..."); 22 | console.log("API key length:", apiKey.length); 23 | 24 | try { 25 | // Test initialization of OpenAI client 26 | const openai = new OpenAI({ 27 | apiKey: apiKey, 28 | baseURL: "https://api.openai.com/v1", 29 | }); 30 | 31 | console.log("OpenAI client initialized successfully"); 32 | 33 | // Test API connection with a simple completions request 34 | console.log("Testing API connection..."); 35 | const response = await openai.chat.completions.create({ 36 | model: "gpt-3.5-turbo", 37 | messages: [{ role: "user", content: "Hello, OpenAI!" }], 38 | max_tokens: 5 39 | }); 40 | 41 | console.log("API connection successful!"); 42 | console.log("Response:", response.choices[0]?.message?.content); 43 | } catch (error) { 44 | console.error("API connection error:", error); 45 | 46 | // If there's a status code, log it 47 | if (error.status) { 48 | console.error(`Status code: ${error.status}`); 49 | } 50 | 51 | // If there's error details, log them 52 | if (error.error) { 53 | console.error("Error details:", error.error); 54 | } 55 | } 56 | } 57 | 58 | // Run the test 59 | testApiKey(); -------------------------------------------------------------------------------- /src/tests/fixApiKey.js: -------------------------------------------------------------------------------- 1 | // Fix API Key script 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import { OpenAI } from 'openai'; 5 | 6 | // Function to extract API key from .env file manually 7 | function extractApiKey() { 8 | try { 9 | // Read the .env file content 10 | const envPath = path.resolve('.env'); 11 | const envContent = fs.readFileSync(envPath, 'utf8'); 12 | 13 | // Find the OpenAI API key line 14 | const lines = envContent.split('\n'); 15 | let apiKeyLine = ''; 16 | 17 | for (let i = 0; i < lines.length; i++) { 18 | if (lines[i].startsWith('OPENAI_API_KEY=')) { 19 | // Start collecting from this line 20 | apiKeyLine = lines[i]; 21 | 22 | // If the line ends with a backslash or the next line doesn't start with #, 23 | // keep appending the following lines 24 | let j = i + 1; 25 | while (j < lines.length && !lines[j].trim().startsWith('#') && lines[j].trim() !== '') { 26 | apiKeyLine += lines[j].trim(); 27 | j++; 28 | } 29 | 30 | break; 31 | } 32 | } 33 | 34 | // Extract the API key 35 | if (apiKeyLine) { 36 | const apiKey = apiKeyLine.substring(apiKeyLine.indexOf('=') + 1).trim(); 37 | return apiKey; 38 | } 39 | 40 | return null; 41 | } catch (error) { 42 | console.error('Error extracting API key:', error); 43 | return null; 44 | } 45 | } 46 | 47 | // Test the API key extraction 48 | async function testApiKey() { 49 | console.log('Extracting API key manually...'); 50 | const apiKey = extractApiKey(); 51 | 52 | if (!apiKey) { 53 | console.error('Failed to extract API key from .env file'); 54 | return; 55 | } 56 | 57 | console.log('Extracted API key:', apiKey.substring(0, 15) + '...'); 58 | console.log('API key length:', apiKey.length); 59 | 60 | // Save the API key to a temporary file 61 | const tempFilePath = path.resolve('./tempkey.txt'); 62 | fs.writeFileSync(tempFilePath, apiKey); 63 | console.log('API key saved to temporary file for inspection'); 64 | 65 | // Test with OpenAI client 66 | try { 67 | console.log('Testing with OpenAI client...'); 68 | const openai = new OpenAI({ 69 | apiKey: apiKey, 70 | }); 71 | 72 | console.log('OpenAI client initialized'); 73 | 74 | // Simple test request 75 | const response = await openai.chat.completions.create({ 76 | model: 'gpt-3.5-turbo', 77 | messages: [{ role: 'user', content: 'Say hello' }], 78 | max_tokens: 10 79 | }); 80 | 81 | console.log('API request successful!'); 82 | console.log('Response:', response.choices[0]?.message?.content); 83 | } catch (error) { 84 | console.error('API request failed:', error); 85 | if (error.status) { 86 | console.log('Status code:', error.status); 87 | } 88 | if (error.error) { 89 | console.log('Error details:', error.error); 90 | } 91 | } 92 | } 93 | 94 | // Run the test 95 | testApiKey(); -------------------------------------------------------------------------------- /src/tests/run-tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Runner Script 3 | * 4 | * This script runs the specified test file using the appropriate Node.js flags 5 | * for ES modules with TypeScript support. 6 | */ 7 | 8 | import { spawn } from 'child_process'; 9 | import { fileURLToPath } from 'url'; 10 | import path from 'path'; 11 | 12 | // Get the directory of the current file 13 | const __filename = fileURLToPath(import.meta.url); 14 | const __dirname = path.dirname(__filename); 15 | 16 | /** 17 | * Run a test file with appropriate Node.js flags 18 | * @param {string} testFile - The path to the test file relative to the tests directory 19 | */ 20 | function runTest(testFile) { 21 | const fullPath = path.join(__dirname, testFile); 22 | 23 | console.log(`Running test: ${testFile}`); 24 | console.log('-------------------------------------------'); 25 | 26 | // Create a child process with the appropriate flags for TypeScript ESM 27 | const testProcess = spawn('node', [ 28 | '--experimental-specifier-resolution=node', 29 | '--loader=ts-node/esm', 30 | fullPath 31 | ], { 32 | stdio: 'inherit' 33 | }); 34 | 35 | // Handle process completion 36 | testProcess.on('close', (code) => { 37 | console.log('-------------------------------------------'); 38 | console.log(`Test ${testFile} ${code === 0 ? 'completed successfully' : 'failed with exit code ' + code}`); 39 | }); 40 | } 41 | 42 | // Get the test file from command line arguments 43 | const testFile = process.argv[2]; 44 | 45 | if (!testFile) { 46 | console.error('Please specify a test file to run.'); 47 | console.log('Usage: node run-tests.js '); 48 | console.log('Example: node run-tests.js test-template-manager.ts'); 49 | process.exit(1); 50 | } 51 | 52 | // Run the specified test 53 | runTest(testFile); -------------------------------------------------------------------------------- /src/tests/test-anthropic-backtest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test script for analyzing backtest results with Anthropic provider 3 | */ 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | import { AnthropicProvider } from '../services/anthropicProvider.js'; 7 | import { config } from '../config/userConfig.js'; 8 | import { BacktestAnalysis } from '../services/llmService.js'; 9 | 10 | // Set up paths to test files 11 | const examplesDir = path.resolve('./examples'); 12 | const backtestResultsPath = path.join(examplesDir, 'backtest-results.json'); 13 | const strategyPath = path.join(examplesDir, 'simple-strategy.pine'); 14 | 15 | async function main() { 16 | // Check if test files exist 17 | if (!fs.existsSync(backtestResultsPath)) { 18 | console.error(`Backtest results file not found: ${backtestResultsPath}`); 19 | return; 20 | } 21 | 22 | if (!fs.existsSync(strategyPath)) { 23 | console.error(`Strategy file not found: ${strategyPath}`); 24 | return; 25 | } 26 | 27 | // Read file contents 28 | const backtestResults = fs.readFileSync(backtestResultsPath, 'utf8'); 29 | const strategy = fs.readFileSync(strategyPath, 'utf8'); 30 | 31 | try { 32 | // Initialize Anthropic provider 33 | console.log('Initializing Anthropic provider...'); 34 | const provider = new AnthropicProvider(); 35 | 36 | // Create a prompt for backtest analysis 37 | const promptTemplate = config.llm?.promptTemplates?.backtestAnalysis || 38 | `Analyze these backtest results for a trading strategy: 39 | 40 | BACKTEST RESULTS: 41 | {{backtestResults}} 42 | 43 | STRATEGY CODE: 44 | {{strategy}} 45 | 46 | Provide a comprehensive analysis including overall assessment, metrics evaluation, 47 | strengths, concerns, suggestions for improvement, and parameter adjustment recommendations. 48 | Return the analysis as a JSON object with the following structure: 49 | { 50 | "overall": { 51 | "assessment": "string describing overall performance", 52 | "score": number from 1-10 53 | }, 54 | "metrics": { 55 | "profitFactor": "string explanation", 56 | "winRate": "string explanation", 57 | etc. 58 | }, 59 | "strengths": ["string", "string", ...], 60 | "concerns": ["string", "string", ...], 61 | "suggestions": ["string", "string", ...], 62 | "parameterAdjustments": [ 63 | { 64 | "parameter": "parameter name", 65 | "currentValue": "current value", 66 | "suggestedValue": "suggested value", 67 | "rationale": "explanation for change" 68 | } 69 | ] 70 | }`; 71 | 72 | // Replace placeholders in template 73 | const prompt = promptTemplate 74 | .replace('{{backtestResults}}', backtestResults) 75 | .replace('{{strategy}}', strategy); 76 | 77 | console.log('Sending request to Anthropic Claude API...'); 78 | console.log(`Using model: ${config.llm?.anthropic?.defaultModel || 'claude-3-sonnet-20240229'}`); 79 | 80 | // Track timing 81 | const startTime = Date.now(); 82 | 83 | // Send request to Anthropic 84 | const response = await provider.sendJsonPrompt(prompt); 85 | 86 | const endTime = Date.now(); 87 | console.log(`Response received in ${(endTime - startTime) / 1000} seconds`); 88 | 89 | // Format and display results 90 | console.log('\nBACKTEST ANALYSIS RESULTS:'); 91 | console.log('\nOverall Assessment:'); 92 | if (response.overall) { 93 | console.log(`${response.overall.assessment}`); 94 | if (response.overall.score) { 95 | console.log(`Score: ${response.overall.score}/10`); 96 | } 97 | } 98 | 99 | console.log('\nMetrics:'); 100 | if (response.metrics) { 101 | for (const [key, value] of Object.entries(response.metrics)) { 102 | console.log(`${key}: ${value}`); 103 | } 104 | } 105 | 106 | console.log('\nStrengths:'); 107 | console.log((response.strengths || []).map((s: string) => `- ${s}`).join('\n')); 108 | 109 | console.log('\nConcerns:'); 110 | console.log((response.concerns || []).map((c: string) => `- ${c}`).join('\n')); 111 | 112 | console.log('\nSuggestions:'); 113 | console.log((response.suggestions || []).map((s: string) => `- ${s}`).join('\n')); 114 | 115 | console.log('\nParameter Adjustments:'); 116 | if (response.parameterAdjustments && response.parameterAdjustments.length > 0) { 117 | for (const adjustment of response.parameterAdjustments) { 118 | console.log(`- Parameter: ${adjustment.parameter}`); 119 | console.log(` Current: ${adjustment.currentValue}`); 120 | console.log(` Suggested: ${adjustment.suggestedValue}`); 121 | console.log(` Rationale: ${adjustment.rationale}`); 122 | console.log(); 123 | } 124 | } else { 125 | console.log('- No parameter adjustments suggested'); 126 | } 127 | 128 | } catch (error) { 129 | console.error('Error in Anthropic backtest analysis test:', error); 130 | } 131 | } 132 | 133 | // Run the test 134 | main().catch(console.error); -------------------------------------------------------------------------------- /src/utils/errorDetector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility for detecting syntax errors in PineScript code 3 | */ 4 | 5 | /** 6 | * Analyzes a PineScript script and detects common syntax errors 7 | * @param script PineScript code to analyze 8 | * @returns Array of error message strings 9 | */ 10 | export function detectPinescriptSyntaxErrors(script: string): string[] { 11 | if (!script || script.trim() === '') { 12 | return []; 13 | } 14 | 15 | const errors: string[] = []; 16 | const lines = script.split('\n'); 17 | 18 | // Check for unclosed string literals 19 | for (let i = 0; i < lines.length; i++) { 20 | const line = lines[i]; 21 | let inString = false; 22 | let quoteChar = ''; 23 | 24 | for (let j = 0; j < line.length; j++) { 25 | const char = line[j]; 26 | // Handle escape sequences 27 | if (char === '\\' && j < line.length - 1) { 28 | j++; // Skip the next character 29 | continue; 30 | } 31 | 32 | if ((char === '"' || char === "'") && !inString) { 33 | inString = true; 34 | quoteChar = char; 35 | } else if (char === quoteChar && inString) { 36 | inString = false; 37 | quoteChar = ''; 38 | } 39 | } 40 | 41 | if (inString) { 42 | errors.push(`Unclosed string literal in line ${i + 1}`); 43 | } 44 | } 45 | 46 | // Check for balanced parentheses and brackets 47 | const openingChars = ['(', '[', '{']; 48 | const closingChars = [')', ']', '}']; 49 | const stack: { char: string, line: number }[] = []; 50 | 51 | for (let i = 0; i < lines.length; i++) { 52 | const line = lines[i]; 53 | let inString = false; 54 | let quoteChar = ''; 55 | 56 | for (let j = 0; j < line.length; j++) { 57 | const char = line[j]; 58 | 59 | // Skip characters in string literals 60 | if ((char === '"' || char === "'") && (j === 0 || line[j-1] !== '\\')) { 61 | if (!inString) { 62 | inString = true; 63 | quoteChar = char; 64 | } else if (char === quoteChar) { 65 | inString = false; 66 | quoteChar = ''; 67 | } 68 | continue; 69 | } 70 | 71 | if (!inString) { 72 | if (openingChars.includes(char)) { 73 | stack.push({ char, line: i + 1 }); 74 | } else if (closingChars.includes(char)) { 75 | const openingIndex = closingChars.indexOf(char); 76 | const expectedOpening = openingChars[openingIndex]; 77 | 78 | if (stack.length === 0) { 79 | errors.push(`Unexpected closing ${char === ')' ? 'parenthesis' : char === ']' ? 'bracket' : 'brace'} in line ${i + 1}`); 80 | } else if (stack[stack.length - 1].char !== expectedOpening) { 81 | errors.push(`Mismatched brackets: found ${char} in line ${i + 1}, but expected closing match for ${stack[stack.length - 1].char} from line ${stack[stack.length - 1].line}`); 82 | } 83 | 84 | if (stack.length > 0) { 85 | stack.pop(); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | // Report unclosed opening brackets/parentheses 93 | if (stack.length > 0) { 94 | for (const item of stack) { 95 | errors.push(`Unclosed ${item.char === '(' ? 'opening parenthesis' : item.char === '[' ? 'opening bracket' : 'opening brace'} from line ${item.line}`); 96 | } 97 | } 98 | 99 | // Check for missing version annotation 100 | if (!script.includes('@version=')) { 101 | errors.push('Missing version annotation. Consider adding //@version=5 at the beginning of your script'); 102 | } 103 | 104 | // Check for missing commas in function arguments 105 | const funcCallRegex = /(\w+)\s*\(((?:[^()]|\([^()]*\))*)\)/g; 106 | let match: RegExpExecArray | null; 107 | let scriptCopy = script; 108 | 109 | while ((match = funcCallRegex.exec(scriptCopy)) !== null) { 110 | const funcName = match[1]; 111 | const args = match[2]; 112 | 113 | if (args && args.trim() !== '') { 114 | const argsWithoutStrings = args.replace(/"[^"]*"/g, '').replace(/'[^']*'/g, ''); 115 | 116 | // Check for args separated by space without comma 117 | if (/[a-zA-Z0-9_"']\s+[a-zA-Z0-9_"']/.test(argsWithoutStrings) && !argsWithoutStrings.includes(',')) { 118 | // Find the line number 119 | const matchPos = match.index; 120 | let lineCount = 0; 121 | let pos = 0; 122 | 123 | for (let i = 0; i < lines.length; i++) { 124 | pos += lines[i].length + 1; // +1 for newline 125 | if (pos > matchPos) { 126 | lineCount = i + 1; 127 | break; 128 | } 129 | } 130 | 131 | errors.push(`Possible missing comma in arguments for function '${funcName}' near line ${lineCount}`); 132 | } 133 | } 134 | } 135 | 136 | return errors; 137 | } -------------------------------------------------------------------------------- /src/utils/versionDetector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PineScript Version Detector 3 | * 4 | * Utility to detect and work with different PineScript versions 5 | */ 6 | 7 | /** 8 | * PineScript versions 9 | */ 10 | export enum PineScriptVersion { 11 | V4 = 'v4', 12 | V5 = 'v5', 13 | V6 = 'v6' 14 | } 15 | 16 | /** 17 | * Detects the PineScript version from code 18 | * 19 | * @param script The PineScript code 20 | * @returns The detected PineScript version 21 | */ 22 | export function detectVersion(script: string): PineScriptVersion { 23 | // This is a placeholder for future implementation 24 | 25 | // Check for v6 specific features 26 | if (script.includes('//@version=6')) { 27 | return PineScriptVersion.V6; 28 | } 29 | 30 | // Check for v5 specific features 31 | if (script.includes('//@version=5')) { 32 | return PineScriptVersion.V5; 33 | } 34 | 35 | // Check for v4 specific features 36 | if (script.includes('//@version=4')) { 37 | return PineScriptVersion.V4; 38 | } 39 | 40 | // Default to v5 if we can't detect the version 41 | return PineScriptVersion.V5; 42 | } -------------------------------------------------------------------------------- /tests/fixers/errorFixer.test.ts: -------------------------------------------------------------------------------- 1 | import { fixPineScriptErrors } from '../../src/fixers/errorFixer'; 2 | 3 | describe('PineScript Error Fixer', () => { 4 | it('should fix missing version annotation', () => { 5 | // Given 6 | const script = ` 7 | indicator("My Indicator", overlay=true) 8 | study("My Study") 9 | `; 10 | 11 | // When 12 | const result = fixPineScriptErrors(script); 13 | 14 | // Then 15 | expect(result.fixed).toBe(true); 16 | expect(result.changes).toContain('Added missing version annotation (@version=5)'); 17 | expect(result.script).toContain('//@version=5'); 18 | }); 19 | 20 | it('should fix unbalanced parentheses', () => { 21 | // Given 22 | const script = ` 23 | //@version=5 24 | indicator("My Indicator", overlay=true 25 | `; 26 | 27 | // When 28 | const result = fixPineScriptErrors(script); 29 | 30 | // Then 31 | expect(result.fixed).toBe(true); 32 | // Either the line-specific fix or the function-call specific pattern may be used 33 | expect(result.changes.some(change => 34 | change.includes('missing closing parenthesis') || 35 | change.includes('parenthesis/es on line') || 36 | change.includes('function call') 37 | )).toBe(true); 38 | // Just check that a closing parenthesis was added somewhere 39 | expect(result.script.split(')').length).toBeGreaterThan(script.split(')').length); 40 | }); 41 | 42 | it('should fix unclosed string literals', () => { 43 | // Given 44 | const script = ` 45 | //@version=5 46 | indicator("My Indicator, overlay=true) 47 | `; 48 | 49 | // When 50 | const result = fixPineScriptErrors(script); 51 | 52 | // Then 53 | expect(result.fixed).toBe(true); 54 | expect(result.changes.some(change => change.includes('string literal'))).toBe(true); 55 | }); 56 | 57 | it('should fix missing commas in function calls', () => { 58 | // Given 59 | const script = ` 60 | //@version=5 61 | input(14 "RSI Length") 62 | `; 63 | 64 | // When 65 | const result = fixPineScriptErrors(script); 66 | 67 | // Then 68 | expect(result.fixed).toBe(true); 69 | expect(result.changes.some(change => change.includes('missing comma'))).toBe(true); 70 | expect(result.script).toContain('input(14, "RSI Length")'); 71 | }); 72 | 73 | it('should fix deprecated study() function', () => { 74 | // Given 75 | const script = ` 76 | //@version=5 77 | study("My Study", overlay=true) 78 | `; 79 | 80 | // When 81 | const result = fixPineScriptErrors(script); 82 | 83 | // Then 84 | expect(result.fixed).toBe(true); 85 | expect(result.changes).toContain('Replaced deprecated study() function with indicator()'); 86 | expect(result.script).toContain('indicator("My Study", overlay=true)'); 87 | }); 88 | 89 | it('should fix incorrect variable export syntax', () => { 90 | // Given 91 | const script = ` 92 | //@version=5 93 | export var myVar = 10 94 | `; 95 | 96 | // When 97 | const result = fixPineScriptErrors(script); 98 | 99 | // Then 100 | expect(result.fixed).toBe(true); 101 | expect(result.changes.some(change => change.includes('export syntax'))).toBe(true); 102 | expect(result.script).toContain('var myVar = 10'); 103 | expect(result.script).toContain('export myVar'); 104 | }); 105 | 106 | it('should fix multiple errors at once', () => { 107 | // Given 108 | const script = ` 109 | study("My Indicator, overlay=true 110 | export var rsiLength = 14 111 | input(14 "RSI Length") 112 | `; 113 | 114 | // When 115 | const result = fixPineScriptErrors(script); 116 | 117 | // Then 118 | expect(result.fixed).toBe(true); 119 | expect(result.changes.length).toBeGreaterThan(2); 120 | }); 121 | 122 | it('should handle empty scripts', () => { 123 | // Given 124 | const script = ''; 125 | 126 | // When 127 | const result = fixPineScriptErrors(script); 128 | 129 | // Then 130 | expect(result.fixed).toBe(false); 131 | expect(result.changes).toEqual([]); 132 | expect(result.script).toEqual(''); 133 | }); 134 | 135 | it('should return original script if no errors are detected', () => { 136 | // Given 137 | const script = ` 138 | //@version=5 139 | indicator("My Perfect Indicator", overlay=true) 140 | var value = 100 141 | plot(value, "Price", color=color.blue) 142 | `; 143 | 144 | // When 145 | const result = fixPineScriptErrors(script); 146 | 147 | // Then 148 | // Our implementation may fix minor formatting even in "perfect" scripts 149 | // Instead of checking exact content, ensure key elements are preserved 150 | expect(result.script).toContain('indicator("My Perfect Indicator"'); 151 | expect(result.script).toContain('var value = 100'); 152 | expect(result.script).toContain('plot(value, "Price"'); 153 | // The color parameter might be formatted differently 154 | expect(result.script).toContain('color=color'); 155 | }); 156 | }); -------------------------------------------------------------------------------- /tests/manual/test-basic.js: -------------------------------------------------------------------------------- 1 | // Basic test script for verification 2 | 3 | console.log('Basic test script executed successfully!'); 4 | console.log('Current working directory:', process.cwd()); -------------------------------------------------------------------------------- /tests/manual/test-complex-validation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test script to validate the complex Gold Scalping PineScript strategy 3 | */ 4 | 5 | // Import the validator directly 6 | import { validatePineScript } from '../../src/validators/syntaxValidator.js'; 7 | 8 | // The Gold Scalping strategy with MA Filter 9 | const complexScript = `//@version=5 10 | strategy("Gold Scalping BOS & CHoCH with MA Filter", overlay=true) 11 | 12 | // Inputs 13 | useEMA = input(true, title="Use EMA Filter") 14 | fastLength = input(20, title="Fast EMA Length") 15 | slowLength = input(50, title="Slow EMA Length") 16 | atrPeriod = input(14, title="ATR Period") 17 | atrMultiplier = input(1.5, title="ATR Multiplier") 18 | takeProfitMultiplier = input(2, title="Take Profit ATR Multiplier") 19 | 20 | // EMA Filter 21 | fastEMA = ta.ema(close, fastLength) 22 | slowEMA = ta.ema(close, slowLength) 23 | uptrend = fastEMA > slowEMA 24 | downtrend = fastEMA < slowEMA 25 | 26 | // ATR for stop loss and take profit 27 | atrValue = ta.atr(atrPeriod) 28 | stopDistance = atrValue * atrMultiplier 29 | tpDistance = atrValue * takeProfitMultiplier 30 | 31 | // Swing High/Low Detection 32 | swingLength = 5 33 | highestHigh = ta.highest(high, swingLength) 34 | lowestLow = ta.lowest(low, swingLength) 35 | 36 | // Detect Break of Structure (BOS) 37 | bosUp = false 38 | bosDown = false 39 | var float prevLow = na 40 | var float prevHigh = na 41 | 42 | if low[swingLength] == lowestLow[1] and not na(lowestLow[1]) 43 | prevLow := low[swingLength] 44 | if close > prevHigh and uptrend 45 | bosUp := true 46 | 47 | if high[swingLength] == highestHigh[1] and not na(highestHigh[1]) 48 | prevHigh := high[swingLength] 49 | if close < prevLow and downtrend 50 | bosDown := true 51 | 52 | // Detect Change of Character (CHoCH) 53 | var float lastHighPoint = na 54 | var float lastLowPoint = na 55 | chochUp = false 56 | chochDown = false 57 | 58 | if bosUp 59 | lastLowPoint := prevLow 60 | if not na(lastLowPoint) and close > lastLowPoint 61 | chochUp := true 62 | 63 | if bosDown 64 | lastHighPoint := prevHigh 65 | if not na(lastHighPoint) and close < lastHighPoint 66 | chochDown := true 67 | 68 | // Trading Conditions 69 | longCondition = bosUp and chochUp and uptrend 70 | shortCondition = bosDown and chochDown and downtrend 71 | 72 | // Strategy Execution 73 | if longCondition 74 | strategy.entry("Long", strategy.long) 75 | strategy.exit("TP/SL", "Long", profit = tpDistance, loss = stopDistance) 76 | 77 | if shortCondition 78 | strategy.entry("Short", strategy.short) 79 | strategy.exit("TP/SL", "Short", profit = tpDistance, loss = stopDistance) 80 | 81 | // Plotting 82 | plot(fastEMA, "Fast EMA", color=color.blue) 83 | plot(slowEMA, "Slow EMA", color=color.red) 84 | plotshape(bosUp and uptrend, "BOS Up", style=shape.triangleup, location=location.belowbar, color=color.green, size=size.small) 85 | plotshape(bosDown and downtrend, "BOS Down", style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small) 86 | plotshape(chochUp and uptrend, "CHoCH Up", style=shape.circle, location=location.belowbar, color=color.green, size=size.small) 87 | plotshape(chochDown and downtrend, "CHoCH Down", style=shape.circle, location=location.abovebar, color=color.red, size=size.small) 88 | 89 | // Draw support and resistance levels 90 | plot(prevLow, "Support", color = uptrend ? color.green : color.gray, style = plot.style_circles) 91 | plot(prevHigh, "Resistance", color = downtrend ? color.red : color.gray, style = plot.style_circles)`; 92 | 93 | // Create a progress reporting context 94 | const progressContext = { 95 | reportProgress: (progress) => { 96 | console.log(`Validation progress: ${progress.progress}/${progress.total}`); 97 | } 98 | }; 99 | 100 | // Print script size info 101 | console.log(`Testing complex script (${complexScript.length} characters, ${complexScript.split('\n').length} lines)`); 102 | 103 | // Start timer 104 | console.time('Validation time'); 105 | 106 | // Validate it with our progress context 107 | console.log('\nValidating PineScript...'); 108 | try { 109 | const result = validatePineScript(complexScript, '5', progressContext); 110 | console.log('Validation result:', JSON.stringify(result, null, 2)); 111 | 112 | if (result.valid) { 113 | console.log('\nValidation successful - script is syntactically correct!'); 114 | } else { 115 | console.log('\nValidation failed with errors:', result.errors); 116 | if (result.warnings.length > 0) { 117 | console.log('Warnings:', result.warnings); 118 | } 119 | } 120 | } catch (error) { 121 | console.error('Validation error:', error); 122 | } 123 | 124 | // End timer 125 | console.timeEnd('Validation time'); -------------------------------------------------------------------------------- /tests/manual/test-fix-errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test script to test the PineScript error fixer 3 | */ 4 | 5 | // Import the fixer and validator 6 | import { fixPineScriptErrors } from '../../src/fixers/errorFixer.js'; 7 | import { validatePineScript } from '../../src/validators/syntaxValidator.js'; 8 | 9 | // Script with common issues 10 | const scriptWithIssues = `//@version=5 11 | indicator("Example With Issues") 12 | 13 | //Missing semicolons 14 | var1 = 10 15 | var2 = 20 16 | result = var1 + var2 17 | 18 | //Unbalanced brackets 19 | if (close > open { 20 | var3 = high 21 | } 22 | 23 | //Unclosed string 24 | message = "This string is not closed 25 | 26 | //Using := without var declaration 27 | counter = 0 28 | counter := counter + 1 29 | 30 | //Missing closing parenthesis in function call 31 | plot(close, "Price", color=color.blue`; 32 | 33 | // Try to fix the script 34 | console.log('Original script:'); 35 | console.log('================'); 36 | console.log(scriptWithIssues); 37 | console.log('================\n'); 38 | 39 | // Validate the original script 40 | console.log('Validating original script...'); 41 | const originalValidation = validatePineScript(scriptWithIssues); 42 | console.log('Validation result:', JSON.stringify(originalValidation, null, 2)); 43 | 44 | // Fix errors 45 | console.log('\nFixing errors...'); 46 | const fixedResult = fixPineScriptErrors(scriptWithIssues); 47 | console.log('Fix result:', JSON.stringify(fixedResult, null, 2)); 48 | 49 | // Validate the fixed script 50 | console.log('\nValidating fixed script...'); 51 | const fixedValidation = validatePineScript(fixedResult.script); 52 | console.log('Validation result:', JSON.stringify(fixedValidation, null, 2)); 53 | 54 | // Show the improvement 55 | console.log('\nImprovement: ' + 56 | (fixedValidation.errors.length < originalValidation.errors.length ? 57 | 'Fixed ' + (originalValidation.errors.length - fixedValidation.errors.length) + ' errors' : 58 | 'No improvement')); 59 | 60 | console.log('\nFixed script:'); 61 | console.log('============'); 62 | console.log(fixedResult.script); 63 | console.log('============'); -------------------------------------------------------------------------------- /tests/manual/test-single-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test script to fix a single error - unclosed string 3 | */ 4 | 5 | // Import the fixer and validator 6 | import { fixPineScriptErrors } from '../../src/fixers/errorFixer.js'; 7 | import { validatePineScript } from '../../src/validators/syntaxValidator.js'; 8 | 9 | // Script with unclosed string 10 | const scriptWithIssue = `//@version=5 11 | indicator("Example With Unclosed String") 12 | 13 | message = "This string is not closed 14 | 15 | plot(close, "Price", color=color.blue)`; 16 | 17 | // Try to fix the script 18 | console.log('Original script:'); 19 | console.log('================'); 20 | console.log(scriptWithIssue); 21 | console.log('================\n'); 22 | 23 | // Validate the original script 24 | console.log('Validating original script...'); 25 | const originalValidation = validatePineScript(scriptWithIssue); 26 | console.log('Validation result:', JSON.stringify(originalValidation, null, 2)); 27 | 28 | // Fix errors 29 | console.log('\nFixing errors...'); 30 | const fixedResult = fixPineScriptErrors(scriptWithIssue); 31 | console.log('Fix result:', JSON.stringify(fixedResult, null, 2)); 32 | 33 | // Validate the fixed script 34 | console.log('\nValidating fixed script...'); 35 | const fixedValidation = validatePineScript(fixedResult.script); 36 | console.log('Validation result:', JSON.stringify(fixedValidation, null, 2)); 37 | 38 | // Show the improvement 39 | console.log('\nImprovement: ' + 40 | (fixedValidation.errors.length < originalValidation.errors.length ? 41 | 'Fixed ' + (originalValidation.errors.length - fixedValidation.errors.length) + ' errors' : 42 | 'No improvement')); 43 | 44 | console.log('\nFixed script:'); 45 | console.log('============'); 46 | console.log(fixedResult.script); 47 | console.log('============'); -------------------------------------------------------------------------------- /tests/manual/test-strategy-update.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test script to validate our updated PineScript strategy with volume filter 3 | */ 4 | 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | import { fileURLToPath } from 'url'; 8 | 9 | // Get the current directory 10 | const __filename = fileURLToPath(import.meta.url); 11 | const __dirname = path.dirname(__filename); 12 | 13 | // Path to our modified strategy files 14 | const originalStrategyPath = path.join(__dirname, '..', '..', 'examples', 'Modified_GoldScalping_Strategy.txt'); 15 | const updatedStrategyPath = path.join(__dirname, '..', '..', 'examples', 'Modified_GoldScalping_Strategy_With_Volume.txt'); 16 | 17 | // Function to validate the updated strategy 18 | async function validateUpdatedStrategy() { 19 | console.log('Validating updated Gold Scalping strategy with volume filter...'); 20 | 21 | try { 22 | // Read the updated strategy file 23 | const strategyScript = fs.readFileSync(updatedStrategyPath, 'utf8'); 24 | console.log(`Updated strategy loaded (${strategyScript.length} chars)`); 25 | 26 | // Prepare the request to validate 27 | const req = { 28 | jsonrpc: "2.0", 29 | id: 1, 30 | method: "validate_pinescript", 31 | params: { 32 | script: strategyScript, 33 | version: "v6" 34 | } 35 | }; 36 | 37 | // Send the request to stdin 38 | process.stdout.write(JSON.stringify(req) + '\n'); 39 | 40 | console.log('Updated strategy validation request sent. Waiting for response...'); 41 | } catch (error) { 42 | console.error('Error validating updated strategy:', error); 43 | } 44 | } 45 | 46 | // Function to compare original and updated strategies 47 | async function compareStrategies() { 48 | console.log('Comparing original and updated strategies...'); 49 | 50 | try { 51 | // Read both strategy files 52 | const originalScript = fs.readFileSync(originalStrategyPath, 'utf8'); 53 | const updatedScript = fs.readFileSync(updatedStrategyPath, 'utf8'); 54 | 55 | // Prepare the request to compare 56 | const req = { 57 | jsonrpc: "2.0", 58 | id: 2, 59 | method: "compare_pinescript_versions", 60 | params: { 61 | old_script: originalScript, 62 | new_script: updatedScript 63 | } 64 | }; 65 | 66 | // Send the request to stdin 67 | process.stdout.write(JSON.stringify(req) + '\n'); 68 | 69 | console.log('Strategy comparison request sent. Waiting for response...'); 70 | } catch (error) { 71 | console.error('Error comparing strategies:', error); 72 | } 73 | } 74 | 75 | // Function to format the updated strategy 76 | async function formatUpdatedStrategy() { 77 | console.log('Formatting updated Gold Scalping strategy...'); 78 | 79 | try { 80 | // Read the updated strategy file 81 | const strategyScript = fs.readFileSync(updatedStrategyPath, 'utf8'); 82 | 83 | // Prepare the request to format 84 | const req = { 85 | jsonrpc: "2.0", 86 | id: 3, 87 | method: "format_pinescript", 88 | params: { 89 | script: strategyScript, 90 | indent_size: 4, 91 | spaces_around_operators: true, 92 | max_line_length: 100 93 | } 94 | }; 95 | 96 | // Send the request to stdin 97 | process.stdout.write(JSON.stringify(req) + '\n'); 98 | 99 | console.log('Strategy formatting request sent. Waiting for response...'); 100 | } catch (error) { 101 | console.error('Error formatting updated strategy:', error); 102 | } 103 | } 104 | 105 | // Run the tests in a sequence with delays between them 106 | async function runTests() { 107 | // First validate the updated strategy 108 | await validateUpdatedStrategy(); 109 | 110 | // Wait 2 seconds before comparing strategies 111 | setTimeout(async () => { 112 | await compareStrategies(); 113 | 114 | // Wait 2 seconds before formatting 115 | setTimeout(async () => { 116 | await formatUpdatedStrategy(); 117 | }, 2000); 118 | }, 2000); 119 | } 120 | 121 | runTests(); -------------------------------------------------------------------------------- /tests/manual/test-strategy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test script to validate our modified PineScript strategy 3 | */ 4 | 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | import { fileURLToPath } from 'url'; 8 | import { miniGoldStrategy, simplifiedGoldStrategy } from '../../src/templates/simplified-gold-strategy.js'; 9 | 10 | // Get the current directory 11 | const __filename = fileURLToPath(import.meta.url); 12 | const __dirname = path.dirname(__filename); 13 | 14 | // Path to our modified strategy file 15 | const strategyFilePath = path.join(__dirname, '..', '..', 'examples', 'Modified_GoldScalping_Strategy.txt'); 16 | 17 | // First test the connection with a minimal script 18 | async function testConnection() { 19 | console.log('Testing MCP server connection with minimal script...'); 20 | 21 | try { 22 | // Create a minimal test script 23 | const testScript = `//@version=5 24 | indicator("Simple Test", overlay=true) 25 | plot(close, "Price", color=color.blue)`; 26 | 27 | // Prepare the request to validate 28 | const req = { 29 | jsonrpc: "2.0", 30 | id: 1, 31 | method: "test_connection", 32 | params: { 33 | version: "5" 34 | } 35 | }; 36 | 37 | // Send the request to stdin 38 | process.stdout.write(JSON.stringify(req) + '\n'); 39 | 40 | console.log('Test connection request sent. Waiting for response...'); 41 | 42 | // Note: In a real scenario, we would need to handle the response 43 | // but for this test we'll just let the MCP server output to console 44 | } catch (error) { 45 | console.error('Error testing connection:', error); 46 | } 47 | } 48 | 49 | // Function to validate mini strategy 50 | async function validateMiniStrategy() { 51 | console.log('Validating mini Gold strategy...'); 52 | 53 | try { 54 | // Prepare the request to validate 55 | const req = { 56 | jsonrpc: "2.0", 57 | id: 2, 58 | method: "validate_pinescript", 59 | params: { 60 | script: miniGoldStrategy, 61 | version: "v6" 62 | } 63 | }; 64 | 65 | // Send the request to stdin 66 | process.stdout.write(JSON.stringify(req) + '\n'); 67 | 68 | console.log('Mini strategy validation request sent. Waiting for response...'); 69 | } catch (error) { 70 | console.error('Error validating mini strategy:', error); 71 | } 72 | } 73 | 74 | // Function to validate simplified strategy 75 | async function validateSimplifiedStrategy() { 76 | console.log('Validating simplified Gold Scalping strategy...'); 77 | 78 | try { 79 | // Prepare the request to validate 80 | const req = { 81 | jsonrpc: "2.0", 82 | id: 3, 83 | method: "validate_pinescript", 84 | params: { 85 | script: simplifiedGoldStrategy, 86 | version: "v6" 87 | } 88 | }; 89 | 90 | // Send the request to stdin 91 | process.stdout.write(JSON.stringify(req) + '\n'); 92 | 93 | console.log('Simplified strategy validation request sent. Waiting for response...'); 94 | } catch (error) { 95 | console.error('Error validating simplified strategy:', error); 96 | } 97 | } 98 | 99 | // Function to validate our full strategy 100 | async function validateFullStrategy() { 101 | console.log('Validating full Gold Scalping strategy...'); 102 | 103 | try { 104 | // Read the strategy file 105 | const strategyScript = fs.readFileSync(strategyFilePath, 'utf8'); 106 | console.log(`Strategy loaded (${strategyScript.length} chars)`); 107 | 108 | // Prepare the request to validate 109 | const req = { 110 | jsonrpc: "2.0", 111 | id: 4, 112 | method: "validate_pinescript", 113 | params: { 114 | script: strategyScript, 115 | version: "v6" 116 | } 117 | }; 118 | 119 | // Send the request to stdin 120 | process.stdout.write(JSON.stringify(req) + '\n'); 121 | 122 | console.log('Full strategy validation request sent. Waiting for response...'); 123 | } catch (error) { 124 | console.error('Error validating full strategy:', error); 125 | } 126 | } 127 | 128 | // Run the tests in a sequence with delays between them 129 | async function runTests() { 130 | // First test connection with minimal script 131 | await testConnection(); 132 | 133 | // Wait 2 seconds before trying the mini strategy 134 | setTimeout(async () => { 135 | await validateMiniStrategy(); 136 | 137 | // Wait 2 seconds before trying the simplified strategy 138 | setTimeout(async () => { 139 | await validateSimplifiedStrategy(); 140 | 141 | // Wait 2 seconds before trying the full strategy 142 | setTimeout(async () => { 143 | await validateFullStrategy(); 144 | }, 2000); 145 | }, 2000); 146 | }, 2000); 147 | } 148 | 149 | runTests(); -------------------------------------------------------------------------------- /tests/manual/test-validation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple test script to validate PineScript directly 3 | */ 4 | 5 | // Import the validator directly 6 | import { validatePineScript } from '../../src/validators/syntaxValidator.js'; 7 | import { getTestScript } from '../../src/templates/testScript.js'; 8 | 9 | // Get the minimal test script 10 | const testScript = getTestScript(); 11 | console.log('Testing with minimal script:'); 12 | console.log('============================'); 13 | console.log(testScript); 14 | console.log('============================'); 15 | 16 | // Validate it 17 | console.log('\nValidating PineScript...'); 18 | try { 19 | const result = validatePineScript(testScript); 20 | console.log('Validation result:', JSON.stringify(result, null, 2)); 21 | 22 | if (result.valid) { 23 | console.log('\nValidation successful - script is syntactically correct!'); 24 | } else { 25 | console.log('\nValidation failed with errors:', result.errors); 26 | if (result.warnings.length > 0) { 27 | console.log('Warnings:', result.warnings); 28 | } 29 | } 30 | } catch (error) { 31 | console.error('Validation error:', error); 32 | } -------------------------------------------------------------------------------- /tests/mcp/errorFixer.tool.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MCP Error Fixer Tool Tests 3 | * 4 | * These tests verify that the error fixer tool in the MCP server works correctly. 5 | */ 6 | 7 | import { describe, expect, it, jest } from '@jest/globals'; 8 | import { FixResult } from '../../src/fixers/errorFixer'; 9 | 10 | // Mock the fixPineScriptErrors function 11 | const mockedFixPineScriptErrors = jest.fn(); 12 | 13 | // Mock the module 14 | jest.mock('../../src/fixers/errorFixer', () => ({ 15 | fixPineScriptErrors: (...args: any[]) => mockedFixPineScriptErrors(...args) 16 | })); 17 | 18 | // Import the mocked module 19 | import { fixPineScriptErrors } from '../../src/fixers/errorFixer'; 20 | 21 | describe('MCP Error Fixer Tool', () => { 22 | beforeEach(() => { 23 | // Clear mocks before each test 24 | mockedFixPineScriptErrors.mockClear(); 25 | }); 26 | 27 | it('should fix syntax errors in PineScript code', () => { 28 | // Setup 29 | mockedFixPineScriptErrors.mockImplementation(() => ({ 30 | fixed: true, 31 | script: 'indicator("My Indicator", overlay=true)', 32 | changes: ['Added missing closing parenthesis on line 1'] 33 | })); 34 | 35 | // Execute 36 | const script = 'indicator("My Indicator", overlay=true'; 37 | const result = fixPineScriptErrors(script); 38 | 39 | // Assert 40 | expect(result.fixed).toBe(true); 41 | expect(result.changes.some(change => change.includes('missing closing parenthesis'))).toBe(true); 42 | }); 43 | 44 | it('should fix multiple errors in PineScript code', async () => { 45 | // Setup 46 | mockedFixPineScriptErrors.mockImplementation(() => ({ 47 | fixed: true, 48 | script: 'fixed script', 49 | changes: [ 50 | 'Added missing version annotation (@version=5)', 51 | 'Added missing closing parenthesis on line 2' 52 | ] 53 | })); 54 | 55 | // Execute 56 | const script = 'indicator("My Indicator", overlay=true\nvar x = 10'; 57 | const result = fixPineScriptErrors(script); 58 | 59 | // Assert 60 | expect(result.fixed).toBe(true); 61 | expect(result.changes.length).toBeGreaterThan(0); 62 | }); 63 | 64 | it('should return original script if no errors are detected', () => { 65 | // Setup 66 | mockedFixPineScriptErrors.mockImplementation(() => ({ 67 | fixed: false, 68 | script: 'original script', 69 | changes: [] 70 | })); 71 | 72 | // Execute 73 | const script = 'original script'; 74 | const result = fixPineScriptErrors(script); 75 | 76 | // Assert 77 | expect(result.fixed).toBe(false); 78 | expect(result.changes).toEqual([]); 79 | }); 80 | }); -------------------------------------------------------------------------------- /tests/mcp/formatter.tool.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests for PineScript formatter MCP tool 3 | */ 4 | 5 | import { jest } from '@jest/globals'; 6 | import { formatPineScript } from '../../src/utils/formatter.js'; 7 | import { validatePineScript } from '../../src/validators/syntaxValidator.js'; 8 | 9 | // Mock the formatter and validator 10 | jest.mock('../../src/utils/formatter.js'); 11 | jest.mock('../../src/validators/syntaxValidator.js'); 12 | 13 | describe('Format PineScript Tool', () => { 14 | let formatTool: any; 15 | 16 | beforeEach(async () => { 17 | jest.clearAllMocks(); 18 | 19 | // Mock the formatter implementation 20 | (formatPineScript as jest.Mock).mockImplementation((script, options) => { 21 | return { 22 | formatted: script + '\n// Formatted', 23 | changes: ['Standardized indentation', 'Added spaces around operators'], 24 | warnings: options.maxLineLength === 60 ? ['Line 1 exceeds maximum line length'] : [] 25 | }; 26 | }); 27 | 28 | // Mock the validator implementation 29 | (validatePineScript as jest.Mock).mockImplementation((script) => { 30 | return { 31 | valid: true, 32 | warnings: [], 33 | errors: [] 34 | }; 35 | }); 36 | 37 | // Import modules that use the mocked functions 38 | const { default: registerMCPTools } = await import('../../src/index.js'); 39 | 40 | // Create a mock MCP instance 41 | const mcp = { 42 | addTool: jest.fn((config) => { 43 | if (config.name === 'format_pinescript') { 44 | formatTool = config; 45 | } 46 | }) 47 | }; 48 | 49 | // Register the tools 50 | await registerMCPTools(mcp as any); 51 | }); 52 | 53 | it('should register the format_pinescript tool', () => { 54 | expect(formatTool).toBeDefined(); 55 | expect(formatTool.name).toBe('format_pinescript'); 56 | expect(formatTool.description).toContain('Format PineScript'); 57 | }); 58 | 59 | it('should format script with default options', async () => { 60 | const script = `//@version=5 61 | indicator("My Indicator") 62 | a=1+2 63 | plot(a)`; 64 | 65 | const result = await formatTool.execute({ script }); 66 | const parsedResult = JSON.parse(result); 67 | 68 | expect(formatPineScript).toHaveBeenCalledWith(script, expect.any(Object)); 69 | expect(parsedResult.formatted).toContain('// Formatted'); 70 | expect(parsedResult.changes).toContain('Standardized indentation'); 71 | expect(parsedResult.valid).toBe(true); 72 | }); 73 | 74 | it('should pass custom formatting options', async () => { 75 | const script = `//@version=5 76 | indicator("My Indicator") 77 | a=1+2 78 | plot(a)`; 79 | 80 | const options = { 81 | script, 82 | indent_size: 2, 83 | use_spaces: false, 84 | spaces_around_operators: true, 85 | max_line_length: 60 86 | }; 87 | 88 | const result = await formatTool.execute(options); 89 | const parsedResult = JSON.parse(result); 90 | 91 | expect(formatPineScript).toHaveBeenCalledWith(script, expect.objectContaining({ 92 | indentSize: 2, 93 | useSpaces: false, 94 | maxLineLength: 60 95 | })); 96 | 97 | expect(parsedResult.warnings).toContain('Line 1 exceeds maximum line length'); 98 | }); 99 | 100 | it('should validate the formatted script', async () => { 101 | const script = `//@version=5 102 | indicator("My Indicator") 103 | a=1+2 104 | plot(a)`; 105 | 106 | // Mock validator to return warnings 107 | (validatePineScript as jest.Mock).mockReturnValueOnce({ 108 | valid: true, 109 | warnings: ['Consider using let instead of var for initialization'], 110 | errors: [] 111 | }); 112 | 113 | const result = await formatTool.execute({ script }); 114 | const parsedResult = JSON.parse(result); 115 | 116 | expect(validatePineScript).toHaveBeenCalled(); 117 | expect(parsedResult.validation_warnings).toHaveLength(1); 118 | expect(parsedResult.validation_warnings[0]).toContain('let instead of var'); 119 | }); 120 | 121 | it('should handle validation errors in formatted script', async () => { 122 | const script = `//@version=5 123 | indicator("My Indicator") 124 | a=1+2 125 | plot(a)`; 126 | 127 | // Mock validator to return errors 128 | (validatePineScript as jest.Mock).mockReturnValueOnce({ 129 | valid: false, 130 | warnings: [], 131 | errors: ['Unexpected syntax error'] 132 | }); 133 | 134 | const result = await formatTool.execute({ script }); 135 | const parsedResult = JSON.parse(result); 136 | 137 | expect(validatePineScript).toHaveBeenCalled(); 138 | expect(parsedResult.valid).toBe(false); 139 | expect(parsedResult.validation_errors).toHaveLength(1); 140 | expect(parsedResult.validation_errors[0]).toBe('Unexpected syntax error'); 141 | }); 142 | }); -------------------------------------------------------------------------------- /tests/mcp/server.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MCP Server Tests 3 | * 4 | * These tests verify that the MCP server is configured correctly. 5 | * Note: These tests don't actually start the server but verify its configuration. 6 | */ 7 | 8 | // In a real test environment, we'd mock fastmcp or create a test client 9 | // For now, we'll just test the basic setup 10 | describe('PineScript MCP Server Configuration', () => { 11 | it('should have correct name and version', async () => { 12 | // This is a placeholder test that will be expanded 13 | // when we have the ability to test the configuration 14 | expect(true).toBe(true); 15 | }); 16 | 17 | it('should register tools correctly', async () => { 18 | // This is a placeholder test that will be expanded 19 | // when we have the ability to test the tool registration 20 | const toolNames = ['validate_pinescript', 'fix_pinescript_errors', 'get_pinescript_template']; 21 | expect(toolNames.length).toBe(3); 22 | }); 23 | }); -------------------------------------------------------------------------------- /tests/templates/templateManager.test.ts: -------------------------------------------------------------------------------- 1 | import { getTemplate, TemplateType } from '../../src/templates/templateManager.js'; 2 | 3 | describe('PineScript Template Manager', () => { 4 | it('should return a strategy template', () => { 5 | // Given 6 | const name = 'Test Strategy'; 7 | 8 | // When 9 | const template = getTemplate(TemplateType.STRATEGY, name); 10 | 11 | // Then 12 | expect(template).toContain(`strategy("${name}", overlay=true)`); 13 | // Just check for common elements that should be in all strategy templates 14 | expect(template).toContain('input('); 15 | expect(template).toContain('plot('); 16 | }); 17 | 18 | it('should return an indicator template', () => { 19 | // Given 20 | const name = 'Test Indicator'; 21 | 22 | // When 23 | const template = getTemplate(TemplateType.INDICATOR, name); 24 | 25 | // Then 26 | expect(template).toContain(`indicator("${name}"`); 27 | expect(template).toContain('length = input('); 28 | expect(template).toContain('plot('); 29 | }); 30 | 31 | it('should throw an error for unknown template type', () => { 32 | // Given 33 | const invalidType = 'invalid' as TemplateType; 34 | const name = 'Test'; 35 | 36 | // When & Then 37 | expect(() => { 38 | getTemplate(invalidType, name); 39 | }).toThrow('Unknown template type'); 40 | }); 41 | 42 | it('should return specific strategy templates by name', () => { 43 | // Test MA Cross template 44 | const maCrossTemplate = getTemplate(TemplateType.STRATEGY, 'ma cross'); 45 | expect(maCrossTemplate).toContain('Moving Average Crossover Strategy'); 46 | expect(maCrossTemplate).toContain('fastLength = input(9, "Fast MA Length")'); 47 | 48 | // Test RSI strategy template 49 | const rsiTemplate = getTemplate(TemplateType.STRATEGY, 'rsi'); 50 | expect(rsiTemplate).toContain('RSI Strategy'); 51 | expect(rsiTemplate).toContain('rsiLength = input(14, "RSI Length")'); 52 | }); 53 | 54 | it('should return specific indicator templates by name', () => { 55 | // Test Bollinger Bands template 56 | const bollingerTemplate = getTemplate(TemplateType.INDICATOR, 'bollinger bands'); 57 | expect(bollingerTemplate).toContain('Bollinger Bands'); 58 | expect(bollingerTemplate).toContain('mult = input.float(2.0, "Std Dev Multiplier"'); 59 | 60 | // Test MACD template 61 | const macdTemplate = getTemplate(TemplateType.INDICATOR, 'macd'); 62 | expect(macdTemplate).toContain('MACD - Moving Average Convergence/Divergence'); 63 | expect(macdTemplate).toContain('fastLength = input(12, "Fast Length")'); 64 | }); 65 | }); -------------------------------------------------------------------------------- /tests/utils/versionDetector.test.ts: -------------------------------------------------------------------------------- 1 | import { detectVersion, PineScriptVersion } from '../../src/utils/versionDetector.js'; 2 | 3 | describe('PineScript Version Detector', () => { 4 | it('should detect version 4', () => { 5 | // Given 6 | const script = `//@version=4 7 | study("My Script") 8 | `; 9 | 10 | // When 11 | const version = detectVersion(script); 12 | 13 | // Then 14 | expect(version).toBe(PineScriptVersion.V4); 15 | }); 16 | 17 | it('should detect version 5', () => { 18 | // Given 19 | const script = `//@version=5 20 | indicator("My Indicator") 21 | `; 22 | 23 | // When 24 | const version = detectVersion(script); 25 | 26 | // Then 27 | expect(version).toBe(PineScriptVersion.V5); 28 | }); 29 | 30 | it('should detect version 6', () => { 31 | // Given 32 | const script = `//@version=6 33 | indicator("My Indicator") 34 | `; 35 | 36 | // When 37 | const version = detectVersion(script); 38 | 39 | // Then 40 | expect(version).toBe(PineScriptVersion.V6); 41 | }); 42 | 43 | it('should default to version 5 when no version is specified', () => { 44 | // Given 45 | const script = `indicator("My Indicator")`; 46 | 47 | // When 48 | const version = detectVersion(script); 49 | 50 | // Then 51 | expect(version).toBe(PineScriptVersion.V5); 52 | }); 53 | }); -------------------------------------------------------------------------------- /tests/validators/syntaxValidator.test.ts: -------------------------------------------------------------------------------- 1 | import { validatePineScript, ValidationResult } from '../../src/validators/syntaxValidator.js'; 2 | 3 | describe('PineScript Syntax Validator', () => { 4 | it('should return a valid result for correct script', () => { 5 | // Given 6 | const script = `//@version=5 7 | indicator("Test Indicator", overlay=true) 8 | 9 | // Input parameters 10 | length = input(14, "RSI Length") 11 | 12 | // Calculate indicator 13 | rsiValue = ta.rsi(close, length) 14 | 15 | // Execute logic 16 | if (rsiValue > 70) 17 | label.new(bar_index, high, "Overbought") 18 | else if (rsiValue < 30) 19 | label.new(bar_index, low, "Oversold") 20 | 21 | // Plot indicator 22 | plot(rsiValue, "RSI", color.purple) 23 | hline(70, "Overbought", color.red) 24 | hline(30, "Oversold", color.green) 25 | `; 26 | 27 | // When 28 | const result: ValidationResult = validatePineScript(script); 29 | 30 | // Then 31 | expect(result.valid).toBe(true); 32 | expect(result.errors).toEqual([]); 33 | expect(result.warnings).toEqual([]); 34 | }); 35 | 36 | it('should detect empty script', () => { 37 | // Given 38 | const script = ''; 39 | 40 | // When 41 | const result: ValidationResult = validatePineScript(script); 42 | 43 | // Then 44 | expect(result.valid).toBe(false); 45 | expect(result.errors).toContain('Script cannot be empty'); 46 | }); 47 | 48 | it('should detect version mismatch', () => { 49 | // Given 50 | const script = '//@version=5\nindicator("Test")\n'; 51 | 52 | // When 53 | const result: ValidationResult = validatePineScript(script, 'v4'); 54 | 55 | // Then 56 | expect(result.warnings.length).toBeGreaterThan(0); 57 | expect(result.warnings[0]).toContain('Script uses version'); 58 | }); 59 | 60 | it('should detect unbalanced parentheses', () => { 61 | // Given 62 | const script = `//@version=5 63 | indicator("Test Indicator", overlay=true 64 | 65 | length = input(14, "RSI Length") 66 | `; 67 | 68 | // When 69 | const result: ValidationResult = validatePineScript(script); 70 | 71 | // Then 72 | expect(result.valid).toBe(false); 73 | expect(result.errors.some(error => error.includes('Unbalanced parentheses'))).toBe(true); 74 | }); 75 | 76 | it('should detect unclosed string literal', () => { 77 | // Given 78 | const script = `//@version=5 79 | indicator("Test Indicator, overlay=true) 80 | `; 81 | 82 | // When 83 | const result: ValidationResult = validatePineScript(script); 84 | 85 | // Then 86 | expect(result.valid).toBe(false); 87 | expect(result.errors.some(error => error.includes('Unclosed string literal'))).toBe(true); 88 | }); 89 | 90 | it('should detect deprecated study() function in v5', () => { 91 | // Given 92 | const script = `//@version=5 93 | study("Test Study", overlay=true) 94 | `; 95 | 96 | // When 97 | const result: ValidationResult = validatePineScript(script); 98 | 99 | // Then 100 | expect(result.warnings.some(warning => warning.includes("'study()' is deprecated"))).toBe(true); 101 | }); 102 | 103 | it('should detect incorrect variable export syntax', () => { 104 | // Given 105 | const script = `//@version=5 106 | indicator("Test Indicator") 107 | export var myVar = 10 108 | `; 109 | 110 | // When 111 | const result: ValidationResult = validatePineScript(script); 112 | 113 | // Then 114 | expect(result.valid).toBe(false); 115 | expect(result.errors.some(error => error.includes('Incorrect variable export syntax'))).toBe(true); 116 | }); 117 | 118 | it('should detect missing comma in function call', () => { 119 | // Given 120 | const script = `//@version=5 121 | indicator("Test Indicator") 122 | length = input(14 "RSI Length") 123 | `; 124 | 125 | // When 126 | const result: ValidationResult = validatePineScript(script); 127 | 128 | // Then 129 | expect(result.warnings.some(warning => warning.includes('Possible missing comma'))).toBe(true); 130 | }); 131 | }); -------------------------------------------------------------------------------- /tests/validators/v6Validator.test.ts: -------------------------------------------------------------------------------- 1 | import { validatePineScript, ValidationResult } from '../../src/validators/syntaxValidator.js'; 2 | 3 | describe('PineScript v6 Syntax Validator', () => { 4 | it('should detect v6 method syntax errors', () => { 5 | // Given 6 | const script = `//@version=6 7 | indicator("V6 Test") 8 | 9 | // Incorrect method syntax 10 | myMethod(x) => x * 2 11 | 12 | // Usage 13 | result = myMethod(5) 14 | `; 15 | 16 | // When 17 | const result: ValidationResult = validatePineScript(script, 'v6'); 18 | 19 | // Then 20 | expect(result.valid).toBe(false); 21 | expect(result.errors.some(error => error.includes("use 'method' keyword"))).toBe(true); 22 | }); 23 | 24 | it('should detect deprecated varip usage', () => { 25 | // Given 26 | const script = `//@version=6 27 | indicator("V6 Test") 28 | 29 | // Using deprecated varip 30 | varip counter = 0 31 | counter := counter + 1 32 | `; 33 | 34 | // When 35 | const result: ValidationResult = validatePineScript(script, 'v6'); 36 | 37 | // Then 38 | expect(result.valid).toBe(false); 39 | expect(result.errors.some(error => error.includes("'varip' keyword is deprecated"))).toBe(true); 40 | }); 41 | 42 | it('should warn about missing function return types', () => { 43 | // Given 44 | const script = `//@version=6 45 | indicator("V6 Test") 46 | 47 | // Missing return type 48 | fn calculateValue(price) 49 | value = price * 1.5 50 | value 51 | `; 52 | 53 | // When 54 | const result: ValidationResult = validatePineScript(script, 'v6'); 55 | 56 | // Then 57 | expect(result.warnings.some(warning => warning.includes("function declarations should include return type"))).toBe(true); 58 | }); 59 | 60 | it('should warn about var usage without mutable assignment', () => { 61 | // Given 62 | const script = `//@version=6 63 | indicator("V6 Test") 64 | 65 | // Should use let instead of var for immutable variable 66 | var price = close 67 | `; 68 | 69 | // When 70 | const result: ValidationResult = validatePineScript(script, 'v6'); 71 | 72 | // Then 73 | expect(result.warnings.some(warning => warning.includes("consider using 'let' for non-mutable variables"))).toBe(true); 74 | }); 75 | 76 | it('should warn about non-named parameters', () => { 77 | // Given 78 | const script = `//@version=6 79 | indicator("V6 Test") 80 | 81 | // Should use named parameters 82 | plot(close, "Price", color.blue) 83 | `; 84 | 85 | // When 86 | const result: ValidationResult = validatePineScript(script, 'v6'); 87 | 88 | // Then 89 | expect(result.warnings.some(warning => warning.includes("consider using named parameters"))).toBe(true); 90 | }); 91 | 92 | it('should warn about missing imports', () => { 93 | // Given 94 | const script = `//@version=6 95 | indicator("V6 Test") 96 | 97 | // Missing imports 98 | rsi = ta.rsi(close, 14) 99 | plot(rsi, "RSI") 100 | `; 101 | 102 | // When 103 | const result: ValidationResult = validatePineScript(script, 'v6'); 104 | 105 | // Then 106 | expect(result.warnings.some(warning => warning.includes("using imports for standard libraries"))).toBe(true); 107 | }); 108 | 109 | it('should validate correct v6 script', () => { 110 | // Given 111 | const script = `//@version=6 112 | indicator("V6 Test") 113 | 114 | import ta 115 | 116 | // Correct function with return type 117 | fn calculateRSI(price, length) -> float 118 | rsiValue = ta.rsi(price, length) 119 | rsiValue 120 | 121 | // Correct variable declarations 122 | let length = 14 123 | var mutableValue = 0 124 | mutableValue := mutableValue + 1 125 | 126 | // Method declaration 127 | method calculateAverage(length) { 128 | avg = ta.sma(close, length) 129 | avg 130 | } 131 | 132 | // Named parameters 133 | plot(series=calculateRSI(close, length), title="RSI", color=color.purple) 134 | `; 135 | 136 | // When 137 | const result: ValidationResult = validatePineScript(script, 'v6'); 138 | 139 | // Then 140 | expect(result.errors).toEqual([]); 141 | // There might still be some warnings, but there should be no errors 142 | expect(result.valid).toBe(true); 143 | }); 144 | }); -------------------------------------------------------------------------------- /ui/.env.example: -------------------------------------------------------------------------------- 1 | # Example environment variables for PineScript MCP UI 2 | 3 | # Supabase Configuration 4 | # Replace with your actual project URL and anon key from Supabase dashboard 5 | NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co 6 | NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key 7 | 8 | # Application Configuration 9 | NEXT_PUBLIC_APP_URL=http://localhost:5001 10 | 11 | # To use this file: 12 | # 1. Copy to .env.local 13 | # 2. Replace placeholder values with actual credentials 14 | # 3. Keep .env.local out of version control (it's gitignored by default) -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | .env.production 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts -------------------------------------------------------------------------------- /ui/.next/cache/.rscinfo: -------------------------------------------------------------------------------- 1 | {"encryption.key":"3i0/0N52YL5ug1nMfUSTNREQGQGMRiNfnx6KR7E+3jo=","encryption.expire_at":1743860911566} -------------------------------------------------------------------------------- /ui/.next/cache/webpack/client-development/0.pack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/client-development/0.pack.gz -------------------------------------------------------------------------------- /ui/.next/cache/webpack/client-development/1.pack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/client-development/1.pack.gz -------------------------------------------------------------------------------- /ui/.next/cache/webpack/client-development/2.pack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/client-development/2.pack.gz -------------------------------------------------------------------------------- /ui/.next/cache/webpack/client-development/index.pack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/client-development/index.pack.gz -------------------------------------------------------------------------------- /ui/.next/cache/webpack/client-development/index.pack.gz.old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/client-development/index.pack.gz.old -------------------------------------------------------------------------------- /ui/.next/cache/webpack/server-development/0.pack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/server-development/0.pack.gz -------------------------------------------------------------------------------- /ui/.next/cache/webpack/server-development/1.pack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/server-development/1.pack.gz -------------------------------------------------------------------------------- /ui/.next/cache/webpack/server-development/index.pack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/server-development/index.pack.gz -------------------------------------------------------------------------------- /ui/.next/cache/webpack/server-development/index.pack.gz.old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cklose2000/pinescript-mcp-server/03c211220294ffa2c56b7843e662f436fac04c6e/ui/.next/cache/webpack/server-development/index.pack.gz.old -------------------------------------------------------------------------------- /ui/components/backtesting/index.js: -------------------------------------------------------------------------------- 1 | import BacktestResultsVisualization from './BacktestResultsVisualization'; 2 | 3 | export { 4 | BacktestResultsVisualization 5 | }; -------------------------------------------------------------------------------- /ui/connection-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Connection Test 5 | 27 | 28 | 29 |

Connection Test Page

30 | 31 |

✓ If you can see this page, your browser can successfully load static HTML content.

32 | 33 |
34 |

Troubleshooting Tips

35 |

If you can view this page but not connect to the server, check:

36 |
    37 |
  1. Are you using the correct port? Try port 8080 instead of 3000/3001
  2. 38 |
  3. Is your Windows Firewall blocking connections?
  4. 39 |
  5. Do you have any antivirus software that might be blocking connections?
  6. 40 |
  7. Try accessing via both localhost:8080 and 127.0.0.1:8080
  8. 41 |
42 |
43 | 44 |

This file is a static HTML page and doesn't require a server to view. If you can see this but not connect to 45 | the server, it indicates a server issue rather than a browser issue.

46 | 47 | 56 | 57 | -------------------------------------------------------------------------------- /ui/electron/electron-data/strategies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Improved Gold Scalping", 5 | "description": "Enhanced scalping strategy for gold with BOS/CHoCH detection and dynamic risk management.", 6 | "winRate": "68.5%", 7 | "profit": "+24.7%" 8 | }, 9 | { 10 | "id": 2, 11 | "name": "MACD Crossover", 12 | "description": "Traditional MACD crossover strategy with optimized parameters for crypto markets.", 13 | "winRate": "62.1%", 14 | "profit": "+18.3%" 15 | }, 16 | { 17 | "id": 3, 18 | "name": "RSI Reversal", 19 | "description": "RSI-based mean reversion strategy with adaptive thresholds.", 20 | "winRate": "57.9%", 21 | "profit": "+12.5%" 22 | }, 23 | { 24 | "id": 4, 25 | "name": "Volume Profile Trading", 26 | "description": "Strategy based on volume profile analysis to identify key support and resistance levels.", 27 | "winRate": "64.2%", 28 | "profit": "+21.3%" 29 | }, 30 | { 31 | "id": 5, 32 | "name": "Bollinger Band Breakout", 33 | "description": "Captures breakouts from Bollinger Bands with confirmation filters.", 34 | "winRate": "59.8%", 35 | "profit": "+15.7%" 36 | } 37 | ] -------------------------------------------------------------------------------- /ui/electron/main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, ipcMain } = require('electron'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | // Handle creating/removing shortcuts on Windows when installing/uninstalling 6 | // Removed electron-squirrel-startup dependency as it's not installed 7 | 8 | let mainWindow; 9 | 10 | const createWindow = () => { 11 | // Create the browser window 12 | mainWindow = new BrowserWindow({ 13 | width: 1200, 14 | height: 800, 15 | webPreferences: { 16 | nodeIntegration: false, 17 | contextIsolation: true, 18 | preload: path.join(__dirname, 'preload.js'), 19 | }, 20 | icon: path.join(__dirname, '../public/favicon.ico'), 21 | }); 22 | 23 | // Load the HTML file directly without a server 24 | mainWindow.loadFile(path.join(__dirname, 'index.html')); 25 | 26 | // Open DevTools in development mode 27 | if (process.env.NODE_ENV === 'development') { 28 | mainWindow.webContents.openDevTools(); 29 | } 30 | 31 | // Emitted when the window is closed 32 | mainWindow.on('closed', () => { 33 | mainWindow = null; 34 | }); 35 | }; 36 | 37 | // This method will be called when Electron has finished initialization 38 | app.whenReady().then(() => { 39 | createWindow(); 40 | 41 | app.on('activate', () => { 42 | // On macOS it's common to re-create a window when the dock icon is clicked 43 | if (BrowserWindow.getAllWindows().length === 0) { 44 | createWindow(); 45 | } 46 | }); 47 | }); 48 | 49 | // Quit when all windows are closed 50 | app.on('window-all-closed', () => { 51 | if (process.platform !== 'darwin') { 52 | app.quit(); 53 | } 54 | }); 55 | 56 | // Handle IPC messages from renderer 57 | ipcMain.handle('get-strategies', async () => { 58 | try { 59 | // Read from a local file as an example 60 | const strategyData = fs.readFileSync(path.join(__dirname, 'electron-data/strategies.json'), 'utf-8'); 61 | return JSON.parse(strategyData); 62 | } catch (error) { 63 | console.error('Error reading strategies:', error); 64 | return []; 65 | } 66 | }); 67 | 68 | // Add any other backend functionality here -------------------------------------------------------------------------------- /ui/electron/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron'); 2 | 3 | // Expose protected methods that allow the renderer process to use 4 | // the ipcRenderer without exposing the entire object 5 | contextBridge.exposeInMainWorld('electron', { 6 | getStrategies: () => ipcRenderer.invoke('get-strategies'), 7 | 8 | // Add other API methods needed by the renderer 9 | app: { 10 | getVersion: () => process.env.npm_package_version || '0.1.0', 11 | }, 12 | 13 | // File system operations 14 | fs: { 15 | readFile: (filepath) => ipcRenderer.invoke('read-file', filepath), 16 | saveFile: (filepath, content) => ipcRenderer.invoke('save-file', filepath, content), 17 | }, 18 | }); -------------------------------------------------------------------------------- /ui/hosts-fix.ps1: -------------------------------------------------------------------------------- 1 | # PineScript Project - Localhost Resolution Fix 2 | # NOTE: This script must be run as Administrator 3 | 4 | # Check if running as administrator 5 | $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 6 | 7 | if (-not $isAdmin) { 8 | Write-Host "This script needs to be run as Administrator." -ForegroundColor Red 9 | Write-Host "Please right-click on PowerShell and select 'Run as Administrator', then run this script again." -ForegroundColor Yellow 10 | exit 1 11 | } 12 | 13 | # Define hosts file path 14 | $hostsFile = "$env:SystemRoot\System32\drivers\etc\hosts" 15 | 16 | # Check if hosts file exists 17 | if (-not (Test-Path $hostsFile)) { 18 | Write-Host "Hosts file not found at $hostsFile" -ForegroundColor Red 19 | exit 1 20 | } 21 | 22 | # Read current hosts file content 23 | try { 24 | $hostsContent = Get-Content -Path $hostsFile -ErrorAction Stop 25 | Write-Host "Successfully read hosts file." -ForegroundColor Green 26 | } catch { 27 | Write-Host "Failed to read hosts file: $_" -ForegroundColor Red 28 | exit 1 29 | } 30 | 31 | # Check if localhost entry already exists 32 | $localhostEntryExists = $false 33 | $localhostPattern = '^\s*127\.0\.0\.1\s+localhost\s*$' 34 | 35 | foreach ($line in $hostsContent) { 36 | if ($line -match $localhostPattern) { 37 | $localhostEntryExists = $true 38 | Write-Host "Localhost entry already exists in hosts file." -ForegroundColor Green 39 | break 40 | } 41 | } 42 | 43 | # Add localhost entry if it doesn't exist 44 | if (-not $localhostEntryExists) { 45 | try { 46 | # Make backup of hosts file 47 | $backupFile = "$hostsFile.backup-$(Get-Date -Format 'yyyyMMdd-HHmmss')" 48 | Copy-Item -Path $hostsFile -Destination $backupFile -ErrorAction Stop 49 | Write-Host "Created backup of hosts file at $backupFile" -ForegroundColor Green 50 | 51 | # Add localhost entry 52 | Add-Content -Path $hostsFile -Value "`n127.0.0.1 localhost" -ErrorAction Stop 53 | Write-Host "Successfully added localhost entry to hosts file." -ForegroundColor Green 54 | } catch { 55 | Write-Host "Failed to update hosts file: $_" -ForegroundColor Red 56 | exit 1 57 | } 58 | } 59 | 60 | # Flush DNS cache to ensure changes take effect 61 | try { 62 | Write-Host "Flushing DNS cache..." -ForegroundColor Yellow 63 | Clear-DnsClientCache 64 | Write-Host "DNS cache flushed successfully." -ForegroundColor Green 65 | } catch { 66 | Write-Host "Warning: Failed to flush DNS cache: $_" -ForegroundColor Yellow 67 | Write-Host "You may need to restart your computer for changes to take effect." -ForegroundColor Yellow 68 | } 69 | 70 | # Test localhost resolution 71 | try { 72 | Write-Host "`nTesting localhost resolution..." -ForegroundColor Yellow 73 | $resolvedIp = [System.Net.Dns]::GetHostAddresses("localhost") | 74 | Where-Object { $_.AddressFamily -eq 'InterNetwork' } | 75 | Select-Object -ExpandProperty IPAddressToString 76 | 77 | if ($resolvedIp -eq "127.0.0.1") { 78 | Write-Host "SUCCESS: 'localhost' resolves to 127.0.0.1" -ForegroundColor Green 79 | } else { 80 | Write-Host "WARNING: 'localhost' resolves to $resolvedIp instead of 127.0.0.1" -ForegroundColor Yellow 81 | Write-Host "You may need to restart your computer for changes to take effect." -ForegroundColor Yellow 82 | } 83 | } catch { 84 | Write-Host "ERROR: Failed to resolve 'localhost': $_" -ForegroundColor Red 85 | Write-Host "You may need to restart your computer." -ForegroundColor Yellow 86 | } 87 | 88 | Write-Host "`nNext steps:" -ForegroundColor Cyan 89 | Write-Host "1. Try running the server: .\start-server.ps1" -ForegroundColor White 90 | Write-Host "2. Test connectivity: .\server-test.ps1" -ForegroundColor White 91 | Write-Host "3. Access the application at: http://127.0.0.1:3000 or http://localhost:3000" -ForegroundColor White -------------------------------------------------------------------------------- /ui/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. 7 | -------------------------------------------------------------------------------- /ui/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | async rewrites() { 6 | return [ 7 | { 8 | source: '/:path*', 9 | destination: '/:path*', 10 | }, 11 | ]; 12 | }, 13 | // Configure webpack options for development 14 | webpack: (config, { dev, isServer }) => { 15 | // Add polling for file watching (previously in webpackDevMiddleware) 16 | if (dev && !isServer) { 17 | config.watchOptions = { 18 | poll: 1000, 19 | aggregateTimeout: 300, 20 | } 21 | } 22 | 23 | // Avoid localhost resolution issues (moved from conditional webpack config) 24 | config.resolve.alias = { 25 | ...config.resolve.alias, 26 | }; 27 | 28 | return config; 29 | }, 30 | }; 31 | 32 | module.exports = nextConfig; -------------------------------------------------------------------------------- /ui/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | // Add any experimental features here 5 | }, 6 | // Use a different port to avoid conflicts 7 | devServer: { 8 | port: 3000, 9 | }, 10 | }; 11 | 12 | export default nextConfig; -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pinescript-mcp-ui", 3 | "version": "0.1.0", 4 | "description": "User interface for PineScript MCP project", 5 | "private": true, 6 | "main": "electron/main.js", 7 | "scripts": { 8 | "dev": "next dev -H 127.0.0.1 -p 3001", 9 | "build": "next build", 10 | "start": "next start -H 127.0.0.1 -p 3001", 11 | "lint": "next lint", 12 | "test": "npx playwright test", 13 | "test:headless": "npx playwright test --headed", 14 | "electron:dev": "electron .", 15 | "electron:build": "electron-builder", 16 | "electron:start": "electron ." 17 | }, 18 | "keywords": [ 19 | "pinescript", 20 | "trading", 21 | "analysis", 22 | "ui" 23 | ], 24 | "author": "", 25 | "license": "ISC", 26 | "dependencies": { 27 | "@heroicons/react": "^2.1.1", 28 | "@types/node": "^20", 29 | "@types/react": "^18", 30 | "@types/react-dom": "^18", 31 | "autoprefixer": "^10.4.17", 32 | "chart.js": "^4.4.1", 33 | "next": "14.2.0", 34 | "postcss": "^8.4.35", 35 | "react": "^18", 36 | "react-dom": "^18", 37 | "tailwindcss": "^3.4.1", 38 | "react-chartjs-2": "^5.2.0", 39 | "react-syntax-highlighter": "^15.5.0" 40 | }, 41 | "devDependencies": { 42 | "@playwright/test": "^1.42.1", 43 | "electron": "^30.0.0", 44 | "electron-builder": "^24.13.3", 45 | "eslint": "^8", 46 | "eslint-config-next": "14.2.0", 47 | "typescript": "^5" 48 | }, 49 | "build": { 50 | "appId": "com.pinescriptmcp.app", 51 | "productName": "PineScript MCP", 52 | "directories": { 53 | "output": "dist" 54 | }, 55 | "win": { 56 | "target": [ 57 | "portable" 58 | ], 59 | "icon": "public/favicon.ico" 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ui/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return ; 5 | } 6 | 7 | export default MyApp; -------------------------------------------------------------------------------- /ui/pages/backtest-results.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BacktestResultsVisualization } from '../components/backtesting'; 3 | import Layout from '../components/Layout'; 4 | 5 | const BacktestResultsPage = () => { 6 | return ( 7 | 8 |
9 |

Strategy Backtest Results

10 |

11 | This page displays the backtest results for the improved Gold Scalping BOS & CHoCH strategy, 12 | showcasing performance metrics, equity curve, drawdowns, and monthly returns compared to the original strategy. 13 |

14 | 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default BacktestResultsPage; -------------------------------------------------------------------------------- /ui/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | export default defineConfig({ 4 | testDir: './tests', 5 | timeout: 30 * 1000, 6 | expect: { 7 | timeout: 5000 8 | }, 9 | fullyParallel: true, 10 | forbidOnly: !!process.env.CI, 11 | retries: process.env.CI ? 2 : 0, 12 | workers: process.env.CI ? 1 : undefined, 13 | reporter: 'html', 14 | use: { 15 | baseURL: 'http://localhost:5001', 16 | trace: 'on-first-retry', 17 | screenshot: 'only-on-failure', 18 | }, 19 | projects: [ 20 | { 21 | name: 'chromium', 22 | use: { ...devices['Desktop Chrome'] }, 23 | }, 24 | ], 25 | webServer: { 26 | command: 'npm run dev', 27 | url: 'http://localhost:5001', 28 | reuseExistingServer: !process.env.CI, 29 | stdout: 'pipe', 30 | stderr: 'pipe', 31 | }, 32 | }); -------------------------------------------------------------------------------- /ui/simple-check.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Simple PowerShell script test" -ForegroundColor Green 2 | Write-Host "Current directory: $(Get-Location)" -ForegroundColor Green 3 | 4 | # Check if port 3000 is in use 5 | $connections = Get-NetTCPConnection -LocalPort 3000 -ErrorAction SilentlyContinue 6 | if ($null -ne $connections -and $connections.Count -gt 0) { 7 | Write-Host "Port 3000 is in use" -ForegroundColor Red 8 | } else { 9 | Write-Host "Port 3000 is available" -ForegroundColor Green 10 | } 11 | 12 | # Check Node.js installation 13 | try { 14 | $nodeVersion = node -v 15 | Write-Host "Node.js installed: $nodeVersion" -ForegroundColor Green 16 | } catch { 17 | Write-Host "Node.js not found" -ForegroundColor Red 18 | } 19 | 20 | Write-Host "Test completed" -ForegroundColor Green -------------------------------------------------------------------------------- /ui/simple-express-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple Express Server for PineScript MCP UI 3 | * 4 | * This server provides a fallback option if Next.js server isn't working. 5 | * It can serve: 6 | * 1. Static files from the Next.js build output 7 | * 2. The test HTML page 8 | * 3. React build output if using a standard React build 9 | */ 10 | 11 | const express = require('express'); 12 | const path = require('path'); 13 | const fs = require('fs'); 14 | const app = express(); 15 | const PORT = process.env.PORT || 3001; 16 | 17 | // Middleware to log requests 18 | app.use((req, res, next) => { 19 | console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); 20 | next(); 21 | }); 22 | 23 | // Check if we have a Next.js build output 24 | const nextBuildPath = path.join(__dirname, '.next/server/pages'); 25 | const hasBuildOutput = fs.existsSync(nextBuildPath); 26 | 27 | if (hasBuildOutput) { 28 | console.log('Next.js build detected - serving Next.js static output'); 29 | app.use('/_next', express.static(path.join(__dirname, '.next'))); 30 | 31 | // Serve Next.js static files 32 | app.use(express.static(path.join(__dirname, 'public'))); 33 | } else { 34 | console.log('No Next.js build detected - serving basic static files'); 35 | } 36 | 37 | // Always serve the test page 38 | app.get('/test', (req, res) => { 39 | res.sendFile(path.join(__dirname, 'test-page.html')); 40 | }); 41 | 42 | // Serve test-page.html at the root as a fallback 43 | app.get('/', (req, res) => { 44 | if (hasBuildOutput) { 45 | // Try to send the Next.js index page if available 46 | const indexPath = path.join(__dirname, '.next/server/pages/index.html'); 47 | if (fs.existsSync(indexPath)) { 48 | return res.sendFile(indexPath); 49 | } 50 | } 51 | 52 | // Otherwise, send the test page 53 | res.sendFile(path.join(__dirname, 'test-page.html')); 54 | }); 55 | 56 | // Start the server 57 | app.listen(PORT, '127.0.0.1', () => { 58 | console.log(` 59 | ======================================== 60 | SIMPLE EXPRESS SERVER 61 | ======================================== 62 | 63 | Server running at: 64 | - http://localhost:${PORT}/ 65 | - http://localhost:${PORT}/test 66 | 67 | Press Ctrl+C to stop the server 68 | `); 69 | }); -------------------------------------------------------------------------------- /ui/simple-http-server.js: -------------------------------------------------------------------------------- 1 | // Ultra-simple HTTP server with minimal configuration 2 | const http = require('http'); 3 | 4 | // Create a basic server with minimal options 5 | const server = http.createServer((req, res) => { 6 | console.log(`Received request: ${req.method} ${req.url}`); 7 | 8 | // Set headers to prevent caching issues 9 | res.setHeader('Content-Type', 'text/html; charset=utf-8'); 10 | res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); 11 | res.setHeader('Pragma', 'no-cache'); 12 | res.setHeader('Expires', '0'); 13 | 14 | // Send a very simple response 15 | res.end(` 16 | 17 | 18 | 19 | Simple Server 20 | 24 | 25 | 26 |

Server is working!

27 |

This simple server is running on port 8080.

28 |

Current time: ${new Date().toLocaleString()}

29 |

Request URL: ${req.url}

30 |

User Agent: ${req.headers['user-agent'] || 'Unknown'}

31 | 32 | 33 | `); 34 | }); 35 | 36 | // Use port 8080 instead as it's less likely to have conflicts 37 | const PORT = 8080; 38 | 39 | // Listen on all interfaces 40 | server.listen(PORT, '0.0.0.0', () => { 41 | console.log('=================================='); 42 | console.log(`Simple server running on port ${PORT}`); 43 | console.log(`Try accessing:`); 44 | console.log(`http://localhost:${PORT}`); 45 | console.log(`http://127.0.0.1:${PORT}`); 46 | console.log('=================================='); 47 | }); 48 | 49 | // Handle server errors 50 | server.on('error', (err) => { 51 | console.error('Server error:', err.message); 52 | 53 | if (err.code === 'EADDRINUSE') { 54 | console.error(`Port ${PORT} is already in use.`); 55 | console.error(`Try changing the PORT value in the script.`); 56 | } 57 | }); -------------------------------------------------------------------------------- /ui/simple-server.ps1: -------------------------------------------------------------------------------- 1 | # Ultra-simple HTTP server in PowerShell 2 | # This script starts a very basic HTTP listener 3 | 4 | Write-Host "Simple PowerShell HTTP Server" -ForegroundColor Green 5 | Write-Host "=============================" -ForegroundColor Green 6 | Write-Host "" 7 | 8 | # Check if port 8080 is already in use 9 | try { 10 | $connections = Get-NetTCPConnection -LocalPort 8080 -ErrorAction SilentlyContinue 11 | if ($connections) { 12 | foreach ($connection in $connections) { 13 | try { 14 | $process = Get-Process -Id $connection.OwningProcess -ErrorAction Stop 15 | Write-Host "Stopping process on port 8080: $($process.ProcessName) (PID: $($connection.OwningProcess))" -ForegroundColor Yellow 16 | Stop-Process -Id $connection.OwningProcess -Force -ErrorAction Stop 17 | Write-Host "Process stopped" -ForegroundColor Green 18 | } 19 | catch { 20 | Write-Host "Failed to stop process: $_" -ForegroundColor Red 21 | } 22 | } 23 | } 24 | } 25 | catch { 26 | Write-Host "Error checking port: $_" -ForegroundColor Red 27 | } 28 | 29 | # Create a simple HTTP listener 30 | $listener = New-Object System.Net.HttpListener 31 | $listener.Prefixes.Add("http://+:8080/") 32 | 33 | try { 34 | # Start the listener 35 | $listener.Start() 36 | 37 | Write-Host "HTTP server is running on port 8080" -ForegroundColor Green 38 | Write-Host "Try accessing: http://localhost:8080" -ForegroundColor Cyan 39 | Write-Host "Press Ctrl+C to stop the server" -ForegroundColor Yellow 40 | Write-Host "" 41 | 42 | # Handle requests until stopped 43 | while ($listener.IsListening) { 44 | $context = $listener.GetContext() 45 | $request = $context.Request 46 | $response = $context.Response 47 | 48 | # Log the request 49 | Write-Host "$($request.HttpMethod) $($request.Url.PathAndQuery) from $($request.RemoteEndPoint.Address)" -ForegroundColor Magenta 50 | 51 | # Create a simple HTML response 52 | $html = @" 53 | 54 | 55 | 56 | PowerShell HTTP Server 57 | 62 | 63 | 64 |

PowerShell HTTP Server is Working!

65 |

This page is being served from a PowerShell HTTP listener.

66 |

Current time: $(Get-Date)

67 |

Request path: $($request.Url.PathAndQuery)

68 |

Client IP: $($request.RemoteEndPoint.Address)

69 | 70 |

Request Headers

71 |
 72 | $($request.Headers | Format-Table | Out-String)
 73 |     
74 | 75 | 76 | "@ 77 | 78 | # Convert the HTML to bytes 79 | $buffer = [System.Text.Encoding]::UTF8.GetBytes($html) 80 | 81 | # Configure response 82 | $response.ContentType = "text/html; charset=utf-8" 83 | $response.ContentLength64 = $buffer.Length 84 | $response.StatusCode = 200 85 | 86 | # Set cache prevention headers 87 | $response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate") 88 | $response.Headers.Add("Pragma", "no-cache") 89 | $response.Headers.Add("Expires", "0") 90 | 91 | # Send the response 92 | $response.OutputStream.Write($buffer, 0, $buffer.Length) 93 | $response.OutputStream.Close() 94 | } 95 | } 96 | catch { 97 | Write-Host "Server error: $_" -ForegroundColor Red 98 | 99 | if ($_.Exception.Message -like "*Access is denied*") { 100 | Write-Host "`nPermission issue detected. Try running this script as Administrator." -ForegroundColor Yellow 101 | } 102 | 103 | if ($_.Exception.Message -like "*conflicts with an existing registration*") { 104 | Write-Host "`nPort 8080 is already in use by another application." -ForegroundColor Yellow 105 | } 106 | } 107 | finally { 108 | # Stop and dispose of the listener 109 | if ($listener -and $listener.IsListening) { 110 | $listener.Stop() 111 | $listener.Close() 112 | Write-Host "`nServer stopped" -ForegroundColor Yellow 113 | } 114 | } -------------------------------------------------------------------------------- /ui/simple-test-server.js: -------------------------------------------------------------------------------- 1 | // Simple HTTP server for testing connectivity 2 | const http = require('http'); 3 | 4 | // Create server 5 | const server = http.createServer((req, res) => { 6 | res.writeHead(200, {'Content-Type': 'text/html'}); 7 | res.end(` 8 | 9 | 10 | 11 | Test Server 12 | 18 | 19 | 20 |
21 |

Test Server is Running!

22 |

✓ Connection successful

23 |

If you can see this page, your server is properly accessible at:

24 |
    25 |
  • URL: http://127.0.0.1:3000
  • 26 |
  • Server time: ${new Date().toLocaleString()}
  • 27 |
28 |

Next Steps

29 |

Now that we've verified basic connectivity, you can:

30 |
    31 |
  1. Stop this test server (Ctrl+C in terminal)
  2. 32 |
  3. Start your Next.js application properly
  4. 33 |
  5. Troubleshoot any remaining issues
  6. 34 |
35 |
36 | 37 | 38 | `); 39 | }); 40 | 41 | // Start server 42 | const PORT = 3000; 43 | const HOST = '127.0.0.1'; 44 | 45 | server.listen(PORT, HOST, () => { 46 | console.log(`Test server running at http://${HOST}:${PORT}/`); 47 | console.log('Press Ctrl+C to stop the server'); 48 | }); -------------------------------------------------------------------------------- /ui/src/app/api/uploads/log/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | 3 | /** 4 | * API route to log file uploads in Supabase 5 | * 6 | * This endpoint records information about uploaded files for auditing 7 | * and analytics purposes. It tracks the type of file, size, name, 8 | * timestamp, and other relevant metadata. 9 | */ 10 | 11 | interface UploadLogRequest { 12 | fileName: string; 13 | fileSize: number; 14 | fileType: string; 15 | userId?: string; 16 | uploadType: 'trade_results' | 'historical_data' | 'strategy_code'; 17 | metadata?: Record; 18 | } 19 | 20 | export async function POST(request: Request) { 21 | try { 22 | const body: UploadLogRequest = await request.json(); 23 | 24 | // Validate required fields 25 | if (!body.fileName || !body.fileSize || !body.uploadType) { 26 | return NextResponse.json( 27 | { error: 'Missing required fields' }, 28 | { status: 400 } 29 | ); 30 | } 31 | 32 | // In a production environment, this would connect to Supabase 33 | // and insert a record into an 'uploads' table 34 | console.log('Logging upload to Supabase:', { 35 | file_name: body.fileName, 36 | file_size: body.fileSize, 37 | file_type: body.fileType || 'text/csv', 38 | user_id: body.userId || 'anonymous', 39 | upload_type: body.uploadType, 40 | metadata: body.metadata || {}, 41 | created_at: new Date().toISOString() 42 | }); 43 | 44 | // This is a placeholder for the actual Supabase implementation 45 | // const { data, error } = await supabaseClient 46 | // .from('uploads') 47 | // .insert([ 48 | // { 49 | // file_name: body.fileName, 50 | // file_size: body.fileSize, 51 | // file_type: body.fileType || 'text/csv', 52 | // user_id: body.userId || 'anonymous', 53 | // upload_type: body.uploadType, 54 | // metadata: body.metadata || {}, 55 | // } 56 | // ]); 57 | 58 | // if (error) { 59 | // console.error('Error logging upload to Supabase:', error); 60 | // return NextResponse.json( 61 | // { error: 'Failed to log upload' }, 62 | // { status: 500 } 63 | // ); 64 | // } 65 | 66 | // Mock successful response 67 | return NextResponse.json( 68 | { 69 | success: true, 70 | message: 'Upload logged successfully', 71 | uploadId: `upload_${Date.now()}` 72 | }, 73 | { status: 200 } 74 | ); 75 | 76 | } catch (error) { 77 | console.error('Error processing upload log request:', error); 78 | return NextResponse.json( 79 | { error: 'Internal server error' }, 80 | { status: 500 } 81 | ); 82 | } 83 | } -------------------------------------------------------------------------------- /ui/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import '@/styles/globals.css'; 2 | import type { Metadata } from 'next'; 3 | 4 | export const metadata: Metadata = { 5 | title: 'PineScript MCP - Strategy Analysis & Optimization', 6 | description: 'Analyze and optimize your PineScript trading strategies with AI-powered insights', 7 | }; 8 | 9 | export default function RootLayout({ 10 | children, 11 | }: { 12 | children: React.ReactNode; 13 | }) { 14 | return ( 15 | 16 | 17 |
18 |
19 |
20 |
21 |

PineScript MCP

22 | 30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | {children} 39 |
40 | 41 |
42 |
43 |
44 |

© 2025 PineScript MCP Project

45 |
46 |
47 | 52 |
53 |
54 |
55 |
56 | 57 | 58 | ); 59 | } -------------------------------------------------------------------------------- /ui/src/app/test/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | 5 | export default function TestPage() { 6 | const [counter, setCounter] = useState(0); 7 | 8 | return ( 9 |
10 |

Test Page

11 |

This is a simple test page to verify Next.js rendering.

12 | 13 |
14 |

Counter: {counter}

15 | 21 |
22 | 23 |

24 | If you're seeing this page, basic Next.js rendering is working correctly. 25 |

26 |
27 | ); 28 | } -------------------------------------------------------------------------------- /ui/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | html { 7 | @apply text-secondary-900; 8 | } 9 | 10 | body { 11 | @apply bg-white; 12 | } 13 | 14 | h1 { 15 | @apply text-3xl font-bold mb-4; 16 | } 17 | 18 | h2 { 19 | @apply text-2xl font-bold mb-3; 20 | } 21 | 22 | h3 { 23 | @apply text-xl font-bold mb-2; 24 | } 25 | 26 | p { 27 | @apply mb-4; 28 | } 29 | } 30 | 31 | @layer components { 32 | .btn { 33 | @apply px-4 py-2 rounded-md font-medium transition-colors; 34 | } 35 | 36 | .btn-primary { 37 | @apply bg-primary-600 text-white hover:bg-primary-700; 38 | } 39 | 40 | .btn-secondary { 41 | @apply bg-secondary-200 text-secondary-800 hover:bg-secondary-300; 42 | } 43 | 44 | .btn-danger { 45 | @apply bg-danger-600 text-white hover:bg-danger-700; 46 | } 47 | 48 | .card { 49 | @apply bg-white rounded-lg shadow-md p-6; 50 | } 51 | 52 | .input { 53 | @apply w-full px-3 py-2 border border-secondary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent; 54 | } 55 | 56 | .select { 57 | @apply w-full px-3 py-2 border border-secondary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent bg-white; 58 | } 59 | } 60 | 61 | @layer utilities { 62 | .flex-center { 63 | @apply flex items-center justify-center; 64 | } 65 | 66 | .flex-between { 67 | @apply flex items-center justify-between; 68 | } 69 | } -------------------------------------------------------------------------------- /ui/start-server-fixed.ps1: -------------------------------------------------------------------------------- 1 | # PineScript Project UI Server Launcher - Simplified Version 2 | # This script provides a more direct approach to starting the Next.js server 3 | 4 | Write-Host "PineScript Project - Starting Next.js Server" -ForegroundColor Cyan 5 | Write-Host "===========================================" -ForegroundColor Cyan 6 | Write-Host "" 7 | 8 | # Don't use Set-PSDebug as it can cause issues 9 | # Set-PSDebug -Trace 0 10 | 11 | # Try to kill any existing process on port 3000 12 | try { 13 | Write-Host "Checking for processes using port 3000..." -ForegroundColor Yellow 14 | $existingProcess = Get-NetTCPConnection -LocalPort 3000 -ErrorAction SilentlyContinue | 15 | Select-Object -ExpandProperty OwningProcess -ErrorAction SilentlyContinue 16 | 17 | if ($existingProcess) { 18 | Write-Host "Killing process ID $existingProcess using port 3000..." -ForegroundColor Yellow 19 | Stop-Process -Id $existingProcess -Force -ErrorAction SilentlyContinue 20 | Start-Sleep -Seconds 2 21 | } 22 | } catch { 23 | Write-Host "Note: Could not check for existing processes. This is OK." -ForegroundColor Yellow 24 | } 25 | 26 | # Ensure we're in the right directory 27 | Write-Host "Setting working directory..." -ForegroundColor Yellow 28 | try { 29 | Set-Location -Path $PSScriptRoot -ErrorAction Stop 30 | $currentPath = Get-Location 31 | Write-Host "Current directory: $currentPath" -ForegroundColor Green 32 | } catch { 33 | Write-Host "Failed to set working directory, using current location instead" -ForegroundColor Yellow 34 | } 35 | 36 | # Make sure .env.local exists with correct settings 37 | if (-not (Test-Path -Path ".env.local")) { 38 | Write-Host "Creating .env.local file..." -ForegroundColor Yellow 39 | @" 40 | # Local development server settings 41 | HOSTNAME=127.0.0.1 42 | PORT=3000 43 | NEXT_PUBLIC_BASE_URL=http://127.0.0.1:3000 44 | 45 | # Server configuration 46 | NODE_OPTIONS='--max-http-header-size=12288' 47 | NEXT_TELEMETRY_DISABLED=1 48 | "@ | Out-File -FilePath ".env.local" -Encoding utf8 49 | } 50 | 51 | # Check for node_modules 52 | if (-not (Test-Path -Path "node_modules")) { 53 | Write-Host "Installing dependencies (this may take a while)..." -ForegroundColor Yellow 54 | npm install 55 | } 56 | 57 | # Start the Next.js development server directly 58 | Write-Host "" 59 | Write-Host "Starting Next.js server at http://127.0.0.1:3000" -ForegroundColor Green 60 | Write-Host "Press Ctrl+C to stop the server" -ForegroundColor Yellow 61 | Write-Host "" 62 | 63 | # Different ways to start the server - try each one 64 | try { 65 | # Option 1: Use npm script 66 | npm run dev 67 | } catch { 68 | try { 69 | # Option 2: Use npx directly with explicit host and port 70 | npx next dev -H 127.0.0.1 -p 3000 71 | } catch { 72 | # Option 3: Most basic way to start Next.js 73 | Write-Host "Falling back to basic Next.js start method..." -ForegroundColor Yellow 74 | node node_modules/next/dist/bin/next dev -H 127.0.0.1 -p 3000 75 | } 76 | } -------------------------------------------------------------------------------- /ui/start-server.ps1: -------------------------------------------------------------------------------- 1 | # PineScript Project UI Server Launcher 2 | # This script ensures reliable startup of the Next.js development server 3 | 4 | # Show script execution 5 | Set-PSDebug -Trace 1 6 | 7 | # Stop any existing node processes that might be using port 3000 8 | Write-Host "Checking for existing processes on port 3000..." -ForegroundColor Yellow 9 | $process = Get-NetTCPConnection -LocalPort 3000 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess 10 | if ($process) { 11 | Write-Host "Killing process using port 3000..." -ForegroundColor Yellow 12 | Stop-Process -Id $process -Force 13 | } 14 | 15 | # Navigate to UI directory 16 | try { 17 | Set-Location -Path "$PSScriptRoot" 18 | Write-Host "Current directory: $PSScriptRoot" -ForegroundColor Green 19 | } catch { 20 | Write-Host "Failed to navigate to UI directory: $_" -ForegroundColor Red 21 | exit 1 22 | } 23 | 24 | # Verify node and npm are installed 25 | Write-Host "Checking Node.js installation..." -ForegroundColor Yellow 26 | try { 27 | $nodeVersion = node -v 28 | $npmVersion = npm -v 29 | Write-Host "Node.js: $nodeVersion" -ForegroundColor Green 30 | Write-Host "npm: $npmVersion" -ForegroundColor Green 31 | } catch { 32 | Write-Host "Node.js or npm not found. Please install Node.js: $_" -ForegroundColor Red 33 | exit 1 34 | } 35 | 36 | # Clean Next.js cache if needed 37 | if (Test-Path -Path ".next") { 38 | Write-Host "Cleaning Next.js cache..." -ForegroundColor Yellow 39 | Remove-Item -Recurse -Force .next 40 | } 41 | 42 | # Verify dependencies 43 | Write-Host "Verifying dependencies..." -ForegroundColor Yellow 44 | if (-not (Test-Path -Path "node_modules/next")) { 45 | Write-Host "Installing dependencies..." -ForegroundColor Yellow 46 | npm install 47 | if ($LASTEXITCODE -ne 0) { 48 | Write-Host "Failed to install dependencies. Error code: $LASTEXITCODE" -ForegroundColor Red 49 | exit 1 50 | } 51 | } 52 | 53 | # Create or verify .env.local file 54 | if (-not (Test-Path -Path ".env.local")) { 55 | Write-Host "Creating .env.local file..." -ForegroundColor Yellow 56 | @" 57 | # Local development server settings 58 | HOSTNAME=127.0.0.1 59 | PORT=3000 60 | NEXT_PUBLIC_BASE_URL=http://127.0.0.1:3000 61 | 62 | # Server configuration 63 | NODE_OPTIONS='--max-http-header-size=12288' 64 | NEXT_TELEMETRY_DISABLED=1 65 | "@ | Out-File -FilePath ".env.local" 66 | } 67 | 68 | # Verify local hosts file has localhost entry (requires admin) 69 | # Uncomment this section when running as administrator 70 | <# 71 | $hostsFile = "$env:SystemRoot\System32\drivers\etc\hosts" 72 | $hostsContent = Get-Content -Path $hostsFile -Raw 73 | 74 | if (-not ($hostsContent -match "127.0.0.1\s+localhost")) { 75 | Write-Host "Updating hosts file with localhost entry..." -ForegroundColor Yellow 76 | Add-Content -Path $hostsFile -Value "`n127.0.0.1 localhost" 77 | } 78 | #> 79 | 80 | # Start the development server 81 | Write-Host "Starting Next.js development server at http://127.0.0.1:3000" -ForegroundColor Green 82 | npm run dev 83 | 84 | # Provide feedback on server exit 85 | if ($LASTEXITCODE -ne 0) { 86 | Write-Host "Server exited with error code: $LASTEXITCODE" -ForegroundColor Red 87 | } else { 88 | Write-Host "Server shutdown successfully" -ForegroundColor Green 89 | } -------------------------------------------------------------------------------- /ui/start-ui.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo PineScript MCP - UI Server Launcher 3 | echo =================================== 4 | echo. 5 | 6 | :: Execute the PowerShell script with bypass execution policy 7 | powershell -ExecutionPolicy Bypass -File "%~dp0\start-ui-server.ps1" 8 | 9 | :: If PowerShell script fails, pause to show error 10 | if %errorlevel% neq 0 ( 11 | echo. 12 | echo Failed to start UI server. 13 | echo Please check the error message above. 14 | pause 15 | exit /b %errorlevel% 16 | ) 17 | 18 | exit /b 0 -------------------------------------------------------------------------------- /ui/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | padding: 0; 8 | margin: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 10 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 11 | } 12 | 13 | a { 14 | color: inherit; 15 | text-decoration: none; 16 | } 17 | 18 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | @media (prefers-color-scheme: dark) { 23 | html { 24 | color-scheme: dark; 25 | } 26 | body { 27 | color: white; 28 | background: black; 29 | } 30 | } -------------------------------------------------------------------------------- /ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './src/pages/**/*.{js,ts,jsx,tsx}', 5 | './src/components/**/*.{js,ts,jsx,tsx}', 6 | './src/app/**/*.{js,ts,jsx,tsx}', 7 | ], 8 | darkMode: 'class', 9 | theme: { 10 | extend: { 11 | colors: { 12 | primary: { 13 | 50: '#f0f9ff', 14 | 100: '#e0f2fe', 15 | 200: '#bae6fd', 16 | 300: '#7dd3fc', 17 | 400: '#38bdf8', 18 | 500: '#0ea5e9', 19 | 600: '#0284c7', 20 | 700: '#0369a1', 21 | 800: '#075985', 22 | 900: '#0c4a6e', 23 | }, 24 | secondary: { 25 | 50: '#f8fafc', 26 | 100: '#f1f5f9', 27 | 200: '#e2e8f0', 28 | 300: '#cbd5e1', 29 | 400: '#94a3b8', 30 | 500: '#64748b', 31 | 600: '#475569', 32 | 700: '#334155', 33 | 800: '#1e293b', 34 | 900: '#0f172a', 35 | }, 36 | success: { 37 | 50: '#f0fdf4', 38 | 100: '#dcfce7', 39 | 200: '#bbf7d0', 40 | 300: '#86efac', 41 | 400: '#4ade80', 42 | 500: '#22c55e', 43 | 600: '#16a34a', 44 | 700: '#15803d', 45 | 800: '#166534', 46 | 900: '#14532d', 47 | }, 48 | warning: { 49 | 50: '#fffbeb', 50 | 100: '#fef3c7', 51 | 200: '#fde68a', 52 | 300: '#fcd34d', 53 | 400: '#fbbf24', 54 | 500: '#f59e0b', 55 | 600: '#d97706', 56 | 700: '#b45309', 57 | 800: '#92400e', 58 | 900: '#78350f', 59 | }, 60 | danger: { 61 | 50: '#fef2f2', 62 | 100: '#fee2e2', 63 | 200: '#fecaca', 64 | 300: '#fca5a5', 65 | 400: '#f87171', 66 | 500: '#ef4444', 67 | 600: '#dc2626', 68 | 700: '#b91c1c', 69 | 800: '#991b1b', 70 | 900: '#7f1d1d', 71 | }, 72 | blue: { 73 | 50: '#f0f9ff', 74 | 100: '#e0f2fe', 75 | 200: '#bae6fd', 76 | 300: '#7dd3fc', 77 | 400: '#38bdf8', 78 | 500: '#0ea5e9', 79 | 600: '#0284c7', 80 | 700: '#0369a1', 81 | 800: '#075985', 82 | 900: '#0c4a6e', 83 | }, 84 | }, 85 | fontFamily: { 86 | sans: ['Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', 'sans-serif'], 87 | mono: ['JetBrains Mono', 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', 'monospace'], 88 | }, 89 | }, 90 | }, 91 | plugins: [], 92 | }; -------------------------------------------------------------------------------- /ui/test-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Connection Test Page 7 | 48 | 49 | 50 |

Connection Test Page

51 | 52 |
53 | Success! If you can see this page, your browser is successfully connecting to the server. 54 |
55 | 56 |
57 |

Browser info:

58 |

Date/Time:

59 |

Connection:

60 |
61 | 62 |

Test JavaScript Execution

63 |

Click the button below to test JavaScript execution:

64 | 65 |

66 | 67 | 83 | 84 | -------------------------------------------------------------------------------- /ui/test-python-server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple HTTP Server to test connectivity 3 | Run with: python test-python-server.py 4 | Access at: http://localhost:8000 5 | """ 6 | 7 | import http.server 8 | import socketserver 9 | import socket 10 | import time 11 | import webbrowser 12 | from datetime import datetime 13 | import os 14 | 15 | PORT = 8000 16 | 17 | class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler): 18 | def do_GET(self): 19 | self.send_response(200) 20 | self.send_header("Content-type", "text/html") 21 | self.send_header("Cache-Control", "no-store, no-cache, must-revalidate") 22 | self.send_header("Pragma", "no-cache") 23 | self.end_headers() 24 | 25 | hostname = socket.gethostname() 26 | ip = socket.gethostbyname(hostname) 27 | now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 28 | 29 | html = f""" 30 | 31 | 32 | 33 | Python Test Server 34 | 40 | 41 | 42 |

Python HTTP Server Test Page

43 | 44 |

✓ CONNECTION SUCCESSFUL! The Python HTTP server is running and responding.

45 | 46 |
47 |

Server Information

48 |

Server Time: {now}

49 |

Hostname: {hostname}

50 |

Local IP: {ip}

51 |

Port: {PORT}

52 |

Request Path: {self.path}

53 |

Your Browser: {self.headers.get('User-Agent')}

54 |
55 | 56 |

Since you can view this page, your network connection is working correctly for this server.

57 |

This is a simple Python HTTP server, which is often easier to set up than Node.js or Next.js servers.

58 | 59 |

To stop this server: Press Ctrl+C in the terminal window where it's running.

60 | 61 | 62 | """ 63 | 64 | self.wfile.write(bytes(html, "utf8")) 65 | return 66 | 67 | def check_port_in_use(port): 68 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 69 | return s.connect_ex(('localhost', port)) == 0 70 | 71 | if __name__ == "__main__": 72 | # Check if port is already in use 73 | if check_port_in_use(PORT): 74 | print(f"WARNING: Port {PORT} is already in use!") 75 | print("Try changing the PORT variable in the script or close the application using this port.") 76 | time.sleep(5) 77 | exit(1) 78 | 79 | print(f"Starting Python HTTP server on port {PORT}...") 80 | 81 | # Create the server 82 | Handler = MyHttpRequestHandler 83 | 84 | try: 85 | with socketserver.TCPServer(("", PORT), Handler) as httpd: 86 | print(f"Server started at http://localhost:{PORT}") 87 | print("You can access it in your browser at:") 88 | print(f" → http://localhost:{PORT}") 89 | print(f" → http://127.0.0.1:{PORT}") 90 | 91 | # Try to open the browser automatically 92 | try: 93 | webbrowser.open(f"http://localhost:{PORT}") 94 | except: 95 | pass 96 | 97 | print("\nPress Ctrl+C to stop the server") 98 | httpd.serve_forever() 99 | except KeyboardInterrupt: 100 | print("\nServer stopped.") 101 | except Exception as e: 102 | print(f"Error starting server: {e}") 103 | 104 | # If permission denied, suggest running as admin 105 | if "permission" in str(e).lower(): 106 | print("\nTIP: Try running this script as administrator if you're getting permission errors.") 107 | 108 | time.sleep(5) -------------------------------------------------------------------------------- /ui/test-results/.last-run.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "failed", 3 | "failedTests": [] 4 | } -------------------------------------------------------------------------------- /ui/test-server.js: -------------------------------------------------------------------------------- 1 | // Simple test server to verify connectivity 2 | const http = require('http'); 3 | 4 | const server = http.createServer((req, res) => { 5 | console.log(`Request received: ${req.method} ${req.url}`); 6 | 7 | res.writeHead(200, {'Content-Type': 'text/html'}); 8 | res.end(` 9 | 10 | 11 | 12 | PineScript MCP Test Server 13 | 19 | 20 | 21 |
22 |

PineScript MCP Test Server is Running!

23 |

✓ Connection successful

24 |

If you can see this page, your server is properly accessible at:

25 |
    26 |
  • URL: http://127.0.0.1:3001
  • 27 |
  • Server time: ${new Date().toLocaleString()}
  • 28 |
29 |

This is a simple test server to verify that your system can:

30 |
    31 |
  1. Bind to the correct IP/port
  2. 32 |
  3. Accept incoming connections
  4. 33 |
  5. Serve content through HTTP
  6. 34 |
35 |
36 | 37 | 38 | `); 39 | }); 40 | 41 | const PORT = 3001; 42 | const HOST = '127.0.0.1'; 43 | 44 | server.listen(PORT, HOST, () => { 45 | console.log(`Test server running at http://${HOST}:${PORT}/`); 46 | console.log('Press Ctrl+C to stop the server'); 47 | }); 48 | 49 | // Log any errors 50 | server.on('error', (err) => { 51 | console.error('Server error:', err); 52 | if (err.code === 'EADDRINUSE') { 53 | console.error(`Port ${PORT} is already in use. Please close the application using this port or try a different port.`); 54 | } 55 | }); -------------------------------------------------------------------------------- /ui/testing-plan.md: -------------------------------------------------------------------------------- 1 | # Local Web Server Testing Plan 2 | 3 | ## Overview 4 | This testing plan ensures the PineScript Project web UI runs consistently as a local web server with a stable connection. It addresses IP address consistency, server configuration, and validation methods. 5 | 6 | ## Prerequisites 7 | - Node.js (v14.0.0 or higher) 8 | - npm (v6.0.0 or higher) 9 | - Windows PowerShell 10 | - Modern web browser (Chrome, Firefox, or Edge) 11 | 12 | ## Environment Setup 13 | 14 | ### 1. Server Configuration 15 | - **Fixed IP and Port**: 127.0.0.1:3000 16 | - **Environment Variables**: Set in `.env.local` 17 | - **Next.js Configuration**: Modified in `next.config.js` 18 | 19 | ### 2. Initial Setup Tests 20 | - [ ] Verify Node.js version: `node -v` 21 | - [ ] Verify npm version: `npm -v` 22 | - [ ] Verify package installation: `cd ui; npm list next` 23 | - [ ] Verify network interface: `ipconfig` 24 | 25 | ## Test Execution 26 | 27 | ### 1. Server Startup Tests 28 | ```powershell 29 | # Navigate to the ui directory 30 | cd ui 31 | 32 | # Install dependencies if needed 33 | npm install 34 | 35 | # Start the development server 36 | npm run dev 37 | ``` 38 | 39 | - [ ] Verify server starts without errors 40 | - [ ] Verify server binds to 127.0.0.1:3000 41 | - [ ] Verify console shows no connection errors 42 | 43 | ### 2. Connection Tests 44 | - [ ] Access http://127.0.0.1:3000 in browser 45 | - [ ] Access http://localhost:3000 in browser 46 | - [ ] Test all major routes: 47 | - [ ] Home page (/) 48 | - [ ] Strategies page (/strategies) 49 | - [ ] Backtest Results page (/backtest-results) 50 | - [ ] Analyze page (/analyze) 51 | 52 | ### 3. Stability Tests 53 | - [ ] Leave server running for 30 minutes 54 | - [ ] Refresh pages multiple times 55 | - [ ] Navigate between pages rapidly 56 | - [ ] Open multiple browser tabs to different routes 57 | 58 | ### 4. Cross-Browser Testing 59 | - [ ] Test in Chrome 60 | - [ ] Test in Firefox 61 | - [ ] Test in Edge 62 | 63 | ## Troubleshooting Procedures 64 | 65 | ### Connection Refused Issues 66 | 1. **Check if server is running**: 67 | ```powershell 68 | Get-Process -Name node 69 | ``` 70 | 71 | 2. **Check if port is already in use**: 72 | ```powershell 73 | netstat -ano | findstr :3000 74 | ``` 75 | 76 | 3. **Kill process using the port if needed**: 77 | ```powershell 78 | taskkill /PID [PID] /F 79 | ``` 80 | 81 | 4. **Check Windows Firewall**: 82 | - Ensure port 3000 is allowed in Windows Firewall 83 | - Add exception if needed 84 | 85 | ### Server Start Failure 86 | 1. **Clean Next.js cache**: 87 | ```powershell 88 | cd ui 89 | rm -r -force .next 90 | npm run dev 91 | ``` 92 | 93 | 2. **Reinstall dependencies**: 94 | ```powershell 95 | cd ui 96 | rm -r -force node_modules 97 | npm install 98 | npm run dev 99 | ``` 100 | 101 | ## Hosts File Configuration (Optional) 102 | If localhost resolution is inconsistent, update the hosts file: 103 | 104 | 1. Open PowerShell as Administrator 105 | 2. Edit hosts file: 106 | ```powershell 107 | notepad C:\Windows\System32\drivers\etc\hosts 108 | ``` 109 | 3. Add or ensure the following entry: 110 | ``` 111 | 127.0.0.1 localhost 112 | ``` 113 | 114 | ## Performance Monitoring 115 | - Monitor CPU/Memory usage during server operation 116 | - Monitor network traffic and response times 117 | - Check for memory leaks during extended operation 118 | 119 | ## Documentation 120 | Record all test results, including: 121 | - Screenshots of successful connections 122 | - Error messages if encountered 123 | - System configuration details 124 | - Browser version information 125 | 126 | ## Reporting 127 | Create a test report with: 128 | - Summary of all tests performed 129 | - Pass/fail status for each test 130 | - Observed issues and their resolution 131 | - Recommendations for improving reliability -------------------------------------------------------------------------------- /ui/tests/ai_testing/README.md: -------------------------------------------------------------------------------- 1 | # AI-Powered UI Testing 2 | 3 | This module provides a framework for automated UI testing of the PineScript MCP application using OpenAI's Assistants API and Playwright for browser automation. 4 | 5 | ## Overview 6 | 7 | The AI-Powered UI Testing framework allows you to: 8 | 9 | - Define tests in natural language 10 | - Automatically interact with UI elements 11 | - Run test scenarios without writing explicit test code 12 | - Generate detailed test reports 13 | - Adapt to UI changes with minimal test maintenance 14 | 15 | ## Components 16 | 17 | - `assistant_test_agent.py`: Core agent that integrates with OpenAI and Playwright 18 | - `run_tests.py`: CLI script for running predefined test scenarios 19 | - `__init__.py`: Package exports and documentation 20 | 21 | ## Getting Started 22 | 23 | ### Prerequisites 24 | 25 | - Python 3.8+ 26 | - OpenAI API key with access to Assistants API 27 | - Playwright installed and browsers installed 28 | 29 | ### Installation 30 | 31 | 1. Install Python dependencies: 32 | ``` 33 | pip install openai playwright 34 | ``` 35 | 36 | 2. Install Playwright browsers: 37 | ``` 38 | playwright install 39 | ``` 40 | 41 | ### Running Tests 42 | 43 | Basic usage: 44 | 45 | ```sh 46 | # Run all tests 47 | python -m ui.tests.ai_testing.run_tests --url http://localhost:5001 --api-key YOUR_OPENAI_API_KEY 48 | 49 | # Run a specific test 50 | python -m ui.tests.ai_testing.run_tests --test counter --url http://localhost:5001 --api-key YOUR_OPENAI_API_KEY 51 | 52 | # Use environment variable for API key 53 | export OPENAI_API_KEY=YOUR_OPENAI_API_KEY 54 | python -m ui.tests.ai_testing.run_tests 55 | ``` 56 | 57 | ### Test Scenarios 58 | 59 | The following test scenarios are predefined: 60 | 61 | - `counter`: Tests the counter functionality on the test page 62 | - `strategies-listing`: Tests the strategies listing page 63 | - `file-upload`: Tests the file upload functionality 64 | - `analyze-code`: Tests the strategy code input 65 | - `navigation`: Tests the main navigation links 66 | 67 | ### Customizing Tests 68 | 69 | To add new test scenarios, edit the `TEST_SCENARIOS` dictionary in `run_tests.py`. 70 | 71 | ## How It Works 72 | 73 | 1. The assistant is initialized with UI testing capabilities 74 | 2. Natural language test instructions are sent to the assistant 75 | 3. The assistant breaks down the test into a sequence of browser operations 76 | 4. Playwright executes these operations and returns results 77 | 5. The assistant evaluates success criteria and reports results 78 | 79 | ## Example Test 80 | 81 | Example of a natural language test instruction: 82 | 83 | ``` 84 | Test the counter functionality on the test page. Navigate to /test, 85 | verify the initial counter is 0, click the increment button, and 86 | verify the counter increases to 1. 87 | ``` 88 | 89 | This will be automatically translated to UI operations without manual coding. -------------------------------------------------------------------------------- /ui/tests/ai_testing/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | OpenAI Assistants API test framework for UI testing. 3 | 4 | This package provides tools for automating UI testing using OpenAI's Assistants API 5 | and Playwright for browser automation. 6 | """ 7 | 8 | from .assistant_test_agent import AssistantTestAgent -------------------------------------------------------------------------------- /ui/tests/ai_testing/requirements.txt: -------------------------------------------------------------------------------- 1 | openai>=1.3.0 2 | playwright>=1.40.0 3 | pytest>=7.0.0 4 | pytest-asyncio>=0.20.0 5 | rich>=13.0.0 6 | python-dotenv>=1.0.0 -------------------------------------------------------------------------------- /ui/tests/ai_testing/test_examples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example test cases using the AI-powered UI testing framework. 3 | 4 | This module demonstrates how to use the AI testing framework 5 | with both pytest integration and standalone scripts. 6 | """ 7 | 8 | import os 9 | import asyncio 10 | import pytest 11 | 12 | from .assistant_test_agent import AssistantTestAgent 13 | from .pytest_integration import ai_test_case, parameterized_ai_tests 14 | 15 | # Basic counter test using the AI testing framework with pytest 16 | @ai_test_case("counter") 17 | async def test_counter_functionality(agent, result): 18 | """Test the basic counter functionality using AI assistant.""" 19 | # We can add additional verification if needed 20 | assert "counter increases" in result["message"].lower() 21 | return True 22 | 23 | # Test navigation using parameterized tests 24 | @parameterized_ai_tests({ 25 | "navigation_home": "Test navigation to the home page. Verify the page title contains 'PineScript MCP'.", 26 | "navigation_strategies": "Test navigation to the strategies page. Verify strategy cards are displayed." 27 | }) 28 | async def test_navigation(agent, result, scenario_name): 29 | """Test navigation between different pages.""" 30 | # Custom verification based on the scenario 31 | if scenario_name == "navigation_home": 32 | assert "page title" in result["message"].lower() 33 | elif scenario_name == "navigation_strategies": 34 | assert "strategy cards" in result["message"].lower() 35 | return True 36 | 37 | # File upload test using custom instructions 38 | @ai_test_case("Test the file upload functionality on the analyze page. Navigate to /analyze, upload the test file 'tests/data/trades.csv', and verify the file name appears.") 39 | async def test_file_upload(agent, result): 40 | """Test the file upload functionality on the analyze page.""" 41 | assert "file" in result["message"].lower() and "upload" in result["message"].lower() 42 | return True 43 | 44 | # Custom test with specific verification 45 | @pytest.mark.asyncio 46 | async def test_custom_workflow(ai_test_agent): 47 | """Test a custom workflow with specific verification steps.""" 48 | instructions = """ 49 | Test the analyze page functionality: 50 | 1. Navigate to /analyze 51 | 2. Enter 'strategy(\"My Test Strategy\")' in the code editor 52 | 3. Click the 'Analyze' button 53 | 4. Verify that results are displayed 54 | """ 55 | 56 | result = await ai_test_agent.run_test(instructions) 57 | 58 | # Custom verification 59 | assert result["success"], f"Test failed: {result.get('message')}" 60 | assert "analyze" in result["message"].lower() 61 | assert "results" in result["message"].lower() 62 | 63 | # Return additional test information 64 | return { 65 | "test_name": "custom_workflow", 66 | "success": result["success"], 67 | "details": result["message"] 68 | } 69 | 70 | # Example of a standalone test script (can be run directly) 71 | async def run_standalone_tests(): 72 | """Run standalone tests without pytest.""" 73 | api_key = os.environ.get("OPENAI_API_KEY") 74 | if not api_key: 75 | print("Error: OPENAI_API_KEY environment variable not set") 76 | return False 77 | 78 | base_url = os.environ.get("TEST_BASE_URL", "http://localhost:5001") 79 | agent = AssistantTestAgent(api_key=api_key, base_url=base_url) 80 | 81 | try: 82 | await agent.setup() 83 | 84 | # Test counter functionality 85 | counter_result = await agent.run_test( 86 | "Test the counter functionality on the test page. " 87 | "Navigate to /test, verify the initial counter is 0, " 88 | "click the increment button, and verify the counter increases to 1." 89 | ) 90 | print(f"Counter test result: {'SUCCESS' if counter_result['success'] else 'FAILURE'}") 91 | print(f"Message: {counter_result['message']}") 92 | 93 | # Test strategies page 94 | strategies_result = await agent.run_test( 95 | "Test the strategies listing page. Navigate to /strategies and " 96 | "verify that strategy cards are displayed with names and descriptions." 97 | ) 98 | print(f"\nStrategies test result: {'SUCCESS' if strategies_result['success'] else 'FAILURE'}") 99 | print(f"Message: {strategies_result['message']}") 100 | 101 | return counter_result["success"] and strategies_result["success"] 102 | finally: 103 | await agent.teardown() 104 | 105 | if __name__ == "__main__": 106 | # Run the standalone tests when executed directly 107 | asyncio.run(run_standalone_tests()) -------------------------------------------------------------------------------- /ui/tests/basic.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | // Basic navigation tests 4 | test.describe('Basic Navigation Tests', () => { 5 | test('NAV-001: Homepage loads correctly', async ({ page }) => { 6 | await page.goto('http://localhost:5001'); 7 | 8 | // Check page title 9 | await expect(page).toHaveTitle(/MCP|PineScript/); 10 | 11 | // Check main content exists 12 | const mainContent = page.locator('main'); 13 | await expect(mainContent).toBeVisible(); 14 | 15 | // Check navigation exists 16 | const nav = page.locator('nav'); 17 | await expect(nav).toBeVisible(); 18 | }); 19 | 20 | test('NAV-002: Navigation links work correctly', async ({ page }) => { 21 | await page.goto('http://localhost:5001'); 22 | 23 | // Click on Strategies link and verify navigation 24 | const strategiesLink = page.getByRole('link', { name: /strategies/i }); 25 | if (await strategiesLink.isVisible()) { 26 | await strategiesLink.click(); 27 | await expect(page).toHaveURL(/strategies/); 28 | } 29 | 30 | // Click on Templates link and verify navigation 31 | await page.goto('http://localhost:5001'); 32 | const templatesLink = page.getByRole('link', { name: /templates/i }); 33 | if (await templatesLink.isVisible()) { 34 | await templatesLink.click(); 35 | await expect(page).toHaveURL(/templates/); 36 | } 37 | 38 | // Click on Analyze link and verify navigation 39 | await page.goto('http://localhost:5001'); 40 | const analyzeLink = page.getByRole('link', { name: /analyze/i }); 41 | if (await analyzeLink.isVisible()) { 42 | await analyzeLink.click(); 43 | await expect(page).toHaveURL(/analyze/); 44 | } 45 | }); 46 | }); 47 | 48 | // Strategies page tests 49 | test.describe('Strategies Page Tests', () => { 50 | test('STRAT-001: Strategies page displays list of strategies', async ({ page }) => { 51 | await page.goto('http://localhost:5001/strategies'); 52 | 53 | // Check for strategy cards 54 | const strategyCards = page.locator('.card'); 55 | 56 | // There should be at least one strategy card 57 | const count = await strategyCards.count(); 58 | expect(count).toBeGreaterThan(0); 59 | 60 | // Check that each card has necessary content 61 | const firstCard = strategyCards.first(); 62 | await expect(firstCard.locator('h2')).toBeVisible(); // Title 63 | await expect(firstCard.locator('p')).toBeVisible(); // Description 64 | }); 65 | }); 66 | 67 | // Analysis page tests 68 | test.describe('Analysis Page Tests', () => { 69 | test('ANALYZE-001: Strategy code can be entered and persists', async ({ page }) => { 70 | await page.goto('http://localhost:5001/analyze'); 71 | 72 | // Enter strategy code 73 | const textarea = page.locator('textarea'); 74 | await expect(textarea).toBeVisible(); 75 | await textarea.fill('strategy("My Test Strategy") => { }'); 76 | 77 | // Change another option to trigger state update 78 | const analysisType = page.locator('select').first(); 79 | await analysisType.selectOption('backtest'); 80 | 81 | // Verify text persists 82 | await expect(textarea).toHaveValue('strategy("My Test Strategy") => { }'); 83 | }); 84 | 85 | test('ANALYZE-005: Analysis options can be selected', async ({ page }) => { 86 | await page.goto('http://localhost:5001/analyze'); 87 | 88 | // Select a different analysis type 89 | const analysisType = page.locator('select').filter({ hasText: 'Analysis Type' }); 90 | await analysisType.selectOption('backtest'); 91 | 92 | // Verify selection was made 93 | await expect(analysisType).toHaveValue('backtest'); 94 | }); 95 | }); 96 | 97 | // Test page tests 98 | test.describe('Test Page Tests', () => { 99 | test('TEST-001: Counter functionality works correctly', async ({ page }) => { 100 | await page.goto('http://localhost:5001/test'); 101 | 102 | // Get initial counter value 103 | const counterText = page.locator('p', { hasText: 'Counter:' }); 104 | await expect(counterText).toContainText('Counter: 0'); 105 | 106 | // Click the button 107 | const button = page.locator('button', { hasText: 'Increment' }); 108 | await button.click(); 109 | 110 | // Verify counter increased 111 | await expect(counterText).toContainText('Counter: 1'); 112 | 113 | // Click multiple times 114 | await button.click(); 115 | await button.click(); 116 | 117 | // Verify counter increased accordingly 118 | await expect(counterText).toContainText('Counter: 3'); 119 | }); 120 | }); -------------------------------------------------------------------------------- /ui/tests/data/history.csv: -------------------------------------------------------------------------------- 1 | date,open,high,low,close,volume 2 | 2023-01-01,40000,41000,39500,40500,1000 3 | 2023-01-02,40500,43000,40400,42000,1500 4 | 2023-01-03,42000,42500,41800,42200,1200 5 | 2023-01-04,42200,42300,41500,41700,900 6 | 2023-01-05,41700,42000,41000,41200,1100 7 | 2023-01-06,41200,41500,38500,39000,2000 -------------------------------------------------------------------------------- /ui/tests/data/trades.csv: -------------------------------------------------------------------------------- 1 | date,symbol,side,quantity,price,profit_loss 2 | 2023-01-01,AAPL,BUY,100,140.50,0 3 | 2023-01-05,AAPL,SELL,100,142.75,225.00 4 | 2023-01-10,MSFT,BUY,50,235.80,0 5 | 2023-01-15,MSFT,SELL,50,240.25,222.50 6 | 2023-01-20,GOOGL,BUY,25,2100.00,0 7 | 2023-01-25,GOOGL,SELL,25,2150.00,1250.00 8 | 2023-02-01,AMZN,BUY,30,3200.00,0 9 | 2023-02-05,AMZN,SELL,30,3250.00,1500.00 10 | 2023-02-10,TSLA,BUY,75,750.00,0 11 | 2023-02-15,TSLA,SELL,75,800.00,3750.00 -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | "baseUrl": ".", 22 | "paths": { 23 | "@/*": [ 24 | "./src/*" 25 | ] 26 | }, 27 | "plugins": [ 28 | { 29 | "name": "next" 30 | } 31 | ] 32 | }, 33 | "include": [ 34 | "**/*.ts", 35 | "**/*.tsx", 36 | "next-env.d.ts", 37 | ".next/types/**/*.ts" 38 | ], 39 | "exclude": [ 40 | "node_modules" 41 | ] 42 | } 43 | --------------------------------------------------------------------------------