diff --git a/README.md b/README.md index c7a4d12..c1db7ef 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ QwenClaw runs as a background daemon, executing scheduled tasks, responding to Telegram messages, and providing a web dashboard for monitoring and management. It automatically starts with your system and persists across all restarts. -![Version](https://img.shields.io/badge/version-1.4.1-blue) +![Version](https://img.shields.io/badge/version-1.4.2-blue) ![License](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey) ![Rust](https://img.shields.io/badge/Rust-1.70+-orange) -![Skills](https://img.shields.io/badge/skills-76-green) +![Skills](https://img.shields.io/badge/skills-77-green) --- @@ -478,6 +478,17 @@ rm -rf QwenClaw-with-Auth QwenClaw follows [Semantic Versioning](https://semver.org/) (MAJOR.MINOR.PATCH). +### [1.4.2] - 2026-02-26 + +#### Added +- **QwenBot Integration** - AI assistant tool for Qwen Code: + - Code analysis and review + - Documentation generation + - Test generation + - Code explanation + - Chat assistance + - Configurable via ~/.qwen/qwenbot/config.json + ### [1.4.1] - 2026-02-26 #### Added diff --git a/skills/metatrader5-trading/SKILL.md b/skills/metatrader5-trading/SKILL.md new file mode 100644 index 0000000..1a08c3e --- /dev/null +++ b/skills/metatrader5-trading/SKILL.md @@ -0,0 +1,445 @@ +# MetaTrader 5 Trading Skill for QwenClaw + +## Overview + +This skill enables QwenClaw to interact with MetaTrader 5 (MT5) for placing and managing trades on demo or live accounts. + +**Integration:** Python MetaTrader5 package + MQL5 + +--- + +## Prerequisites + +### 1. Install MetaTrader 5 Python Package + +```bash +pip install MetaTrader5 +``` + +### 2. Ensure MT5 Terminal is Running + +The MT5 terminal must be logged in and running before executing trades. + +### 3. Configure Account + +- Demo account recommended for testing +- Ensure auto-trading is enabled (Alt+A) + +--- + +## Usage + +### Place a Buy Order + +``` +Place a BUY order for EURUSD: +- Lot size: 0.1 +- Stop Loss: 1.0800 +- Take Profit: 1.0900 +``` + +### Place a Sell Order + +``` +Place a SELL order for GBPUSD: +- Lot size: 0.05 +- Stop Loss: 1.2700 +- Take Profit: 1.2500 +``` + +### Close All Positions + +``` +Close all open positions +``` + +### Get Account Info + +``` +Show my MT5 account balance and equity +``` + +### Get Open Positions + +``` +Show all open positions +``` + +--- + +## Python Implementation + +```python +import MetaTrader5 as mt5 +from datetime import datetime + +class MT5Trader: + def __init__(self): + if not mt5.initialize(): + raise Exception("Failed to initialize MT5 connection") + + def get_account_info(self): + account_info = mt5.account_info() + return { + "balance": account_info.balance, + "equity": account_info.equity, + "margin": account_info.margin, + "profit": account_info.profit, + "currency": account_info.currency, + } + + def get_positions(self): + positions = mt5.positions_get() + if positions is None: + return [] + return [ + { + "symbol": pos.symbol, + "type": "BUY" if pos.type == mt5.ORDER_TYPE_BUY else "SELL", + "volume": pos.volume, + "price_open": pos.price_open, + "price_current": pos.price_current, + "sl": pos.sl, + "tp": pos.tp, + "profit": pos.profit, + } + for pos in positions + ] + + def place_order( + self, + symbol: str, + order_type: str, + volume: float, + sl: float = None, + tp: float = None, + comment: str = "QwenClaw Trade" + ): + # Get symbol info + symbol_info = mt5.symbol_info(symbol) + if symbol_info is None: + return {"success": False, "error": f"Symbol {symbol} not found"} + + # Check if symbol is visible + if not symbol_info.visible: + if not mt5.symbol_select(symbol, True): + return {"success": False, "error": f"Failed to select symbol {symbol}"} + + # Get current price + tick = mt5.symbol_info_tick(symbol) + if tick is None: + return {"success": False, "error": "Failed to get current price"} + + # Determine order type and price + if order_type.upper() == "BUY": + trade_type = mt5.ORDER_TYPE_BUY + price = tick.ask + else: + trade_type = mt5.ORDER_TYPE_SELL + price = tick.bid + + # Prepare order request + request = { + "action": mt5.TRADE_ACTION_DEAL, + "symbol": symbol, + "volume": volume, + "type": trade_type, + "price": price, + "sl": sl if sl else 0, + "tp": tp if tp else 0, + "deviation": 20, + "magic": 234000, + "comment": comment, + "type_time": mt5.ORDER_TIME_GTC, + "type_filling": mt5.ORDER_FILLING_IOC, + } + + # Send order + result = mt5.order_send(request) + + if result.retcode != mt5.TRADE_RETCODE_DONE: + return { + "success": False, + "error": f"Order failed: {result.comment}", + "retcode": result.retcode, + } + + return { + "success": True, + "order": result.order, + "deal": result.deal, + "volume": result.volume, + "price": result.price, + "comment": result.comment, + } + + def close_position(self, ticket: int): + position = mt5.positions_get(ticket=ticket) + if not position: + return {"success": False, "error": f"Position {ticket} not found"} + + pos = position[0] + + # Prepare close request + if pos.type == mt5.ORDER_TYPE_BUY: + trade_type = mt5.ORDER_TYPE_SELL + price = mt5.symbol_info_tick(pos.symbol).bid + else: + trade_type = mt5.ORDER_TYPE_BUY + price = mt5.symbol_info_tick(pos.symbol).ask + + request = { + "action": mt5.TRADE_ACTION_DEAL, + "symbol": pos.symbol, + "volume": pos.volume, + "type": trade_type, + "position": ticket, + "price": price, + "deviation": 20, + "magic": 234000, + "comment": "Close position", + "type_time": mt5.ORDER_TIME_GTC, + "type_filling": mt5.ORDER_FILLING_IOC, + } + + result = mt5.order_send(request) + + if result.retcode != mt5.TRADE_RETCODE_DONE: + return { + "success": False, + "error": f"Close failed: {result.comment}", + } + + return { + "success": True, + "deal": result.deal, + "profit": result.profit, + } + + def close_all_positions(self): + positions = mt5.positions_get() + if positions is None: + return {"success": True, "closed": 0} + + closed = 0 + for pos in positions: + result = self.close_position(pos.ticket) + if result["success"]: + closed += 1 + + return {"success": True, "closed": closed} + + def disconnect(self): + mt5.shutdown() + + +# Example usage +if __name__ == "__main__": + trader = MT5Trader() + + # Get account info + info = trader.get_account_info() + print(f"Balance: {info['balance']} {info['currency']}") + print(f"Equity: {info['equity']}") + + # Place a demo trade + result = trader.place_order( + symbol="EURUSD", + order_type="BUY", + volume=0.1, + sl=1.0800, + tp=1.0900 + ) + + if result["success"]: + print(f"Order placed: {result['order']} at {result['price']}") + else: + print(f"Order failed: {result['error']}") + + trader.disconnect() +``` + +--- + +## QwenClaw Integration + +### Rig Agent Setup + +```typescript +import { RigClient } from "./rig"; + +const rig = new RigClient({ host: "127.0.0.1", port: 8080 }); + +// Create trading agent +const traderAgentId = await rig.createAgent({ + name: "mt5-trader", + preamble: `You are a MetaTrader 5 trading assistant. + + When placing trades: + 1. Always confirm symbol, direction, lot size + 2. Set appropriate stop loss and take profit + 3. Use demo account for testing + 4. Report trade results clearly`, + provider: "qwen", + model: "qwen-plus", +}); + +// Place trade +const result = await rig.executePrompt(traderAgentId, ` + Place a BUY order for EURUSD with: + - Lot size: 0.1 + - Stop Loss: 50 pips + - Take Profit: 100 pips +`); +``` + +--- + +## Trade Examples + +### Example 1: Simple Buy Order + +**Request:** +``` +Buy 0.1 lot EURUSD at market +``` + +**Expected Result:** +```json +{ + "success": true, + "order": 123456789, + "deal": 987654321, + "volume": 0.1, + "price": 1.0850, + "symbol": "EURUSD", + "type": "BUY" +} +``` + +### Example 2: Sell Order with SL/TP + +**Request:** +``` +Sell 0.05 lot GBPUSD +Stop Loss: 1.2700 +Take Profit: 1.2500 +``` + +**Expected Result:** +```json +{ + "success": true, + "order": 123456790, + "volume": 0.05, + "price": 1.2600, + "sl": 1.2700, + "tp": 1.2500, + "symbol": "GBPUSD", + "type": "SELL" +} +``` + +### Example 3: Close All Positions + +**Request:** +``` +Close all open positions +``` + +**Expected Result:** +```json +{ + "success": true, + "closed": 3 +} +``` + +--- + +## Risk Management + +### Position Sizing + +| Account Balance | Recommended Max Lot | +|-----------------|---------------------| +| $1,000 | 0.01 - 0.05 | +| $5,000 | 0.05 - 0.10 | +| $10,000 | 0.10 - 0.25 | +| $50,000+ | 0.25 - 0.50 | + +### Stop Loss Guidelines + +- **EURUSD/GBPUSD:** 20-50 pips typical +- **Gold (XAUUSD):** 50-100 pips typical +- **Indices:** 100-200 points typical + +### Risk Per Trade + +- **Conservative:** 1-2% of account per trade +- **Moderate:** 2-3% of account per trade +- **Aggressive:** 3-5% of account per trade (not recommended) + +--- + +## Error Codes + +| Retcode | Description | +|---------|-------------| +| 10006 | No connection | +| 10013 | Invalid request | +| 10014 | Invalid volume | +| 10015 | Invalid price | +| 10016 | Invalid stops | +| 10019 | Insufficient funds | +| 10023 | Request rejected | +| 10032 | Market closed | + +--- + +## Safety Features + +### Demo Mode (Default) +- Always use demo account first +- Verify account type before trading + +### Confirmation Required +- Always confirm trade details before execution +- Show symbol, direction, volume, SL, TP + +### Logging +- Log all trade requests +- Log all trade results +- Keep audit trail + +--- + +## Resources + +- **MetaTrader 5 Python Docs:** https://www.mql5.com/en/docs/python +- **MetaTrader 5 Download:** https://www.metatrader5.com/en/download +- **MQL5 Community:** https://www.mql5.com/ + +--- + +## Skill Metadata + +```yaml +name: metatrader5-trading +version: 1.0.0 +category: trading +description: MetaTrader 5 trading integration for placing and managing forex/CFD trades +author: QwenClaw Team +license: MIT +tags: + - trading + - metatrader + - mt5 + - forex + - cfd + - demo +``` + +--- + +**Skill ready for QwenClaw integration!** šŸ“ŠšŸ’¹ diff --git a/skills/qwenbot-integration/SKILL.md b/skills/qwenbot-integration/SKILL.md new file mode 100644 index 0000000..f1ffd64 --- /dev/null +++ b/skills/qwenbot-integration/SKILL.md @@ -0,0 +1,274 @@ +# QwenBot Integration for Qwen Code + +## Overview + +QwenBot is an AI assistant tool that integrates with Qwen Code to provide enhanced capabilities through the Qwen API. + +--- + +## Installation + +### Option 1: Install as Qwen Code Plugin + +```bash +# Navigate to Qwen Code plugins directory +cd ~/.qwen/plugins + +# Clone qwenbot plugin +git clone https://github.com/QwenLM/qwenbot.git + +# Install dependencies +cd qwenbot +npm install +``` + +### Option 2: Manual Setup + +1. **Create plugin directory:** +```bash +mkdir -p ~/.qwen/plugins/qwenbot +``` + +2. **Create package.json:** +```json +{ + "name": "qwenbot", + "version": "1.0.0", + "description": "QwenBot AI assistant for Qwen Code", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@qwen/sdk": "^1.0.0" + } +} +``` + +3. **Install dependencies:** +```bash +cd ~/.qwen/plugins/qwenbot +npm install @qwen/sdk +``` + +--- + +## Configuration + +### 1. Get Qwen API Key + +Visit: https://platform.qwen.ai/api-keys + +### 2. Create Configuration File + +Create `~/.qwen/plugins/qwenbot/config.json`: + +```json +{ + "apiKey": "your-qwen-api-key-here", + "model": "qwen-plus", + "temperature": 0.7, + "maxTokens": 2048, + "baseUrl": "https://api.qwen.ai/v1" +} +``` + +### 3. Enable in Qwen Code Settings + +Edit `~/.qwen/settings.json`: + +```json +{ + "enabledPlugins": [ + "qwenbot" + ], + "qwenbot": { + "autoStart": true, + "defaultModel": "qwen-plus" + } +} +``` + +--- + +## Usage + +### Basic Commands + +Once installed, you can use qwenbot in Qwen Code: + +``` +/qwenbot +``` + +### Examples + +``` +/qwenbot Explain how async/await works in JavaScript + +/qwenbot Review this code for potential bugs + +/qwenbot Suggest improvements for this function + +/qwenbot Generate unit tests for this module +``` + +### Advanced Usage + +``` +/qwenbot --model qwen-max Analyze this architecture + +/qwenbot --temperature 0.9 Generate creative solutions for... + +/qwenbot --context file:src/utils.js Explain this file +``` + +--- + +## API Reference + +### QwenBot Class + +```javascript +const QwenBot = require('./qwenbot'); + +const bot = new QwenBot({ + apiKey: 'your-api-key', + model: 'qwen-plus', +}); + +// Chat with bot +const response = await bot.chat('Hello, how are you?'); + +// Chat with context +const response = await bot.chat('Explain this code', { + context: 'const x = 10;', + temperature: 0.5, +}); + +// Stream response +const stream = await bot.stream('Write a function that...'); +for await (const chunk of stream) { + process.stdout.write(chunk); +} +``` + +--- + +## Integration with QwenClaw + +QwenBot can also integrate with QwenClaw daemon: + +### 1. Add to QwenClaw Skills + +Create `skills/qwenbot-integration/SKILL.md`: + +```markdown +--- +name: qwenbot-integration +description: Use QwenBot API for enhanced AI capabilities +--- + +# QwenBot Integration + +This skill enables QwenClaw to use QwenBot API for: +- Enhanced code analysis +- Multi-turn conversations +- Specialized task handling + +## Usage + +```typescript +import { QwenBotClient } from './qwenbot'; + +const client = new QwenBotClient('api-key'); + +// Analyze code +const analysis = await client.analyzeCode(code); + +// Generate documentation +const docs = await client.generateDocs(code); + +// Chat assistance +const response = await client.chat('Help me with...'); +``` +``` + +### 2. Add to Rig Service + +Update `rig-service/src/agent.rs` to include QwenBot provider: + +```rust +fn get_provider_config(&self, provider: &str) -> Result<(String, Option)> { + match provider.to_lowercase().as_str() { + "qwenbot" => { + let api_key = std::env::var("QWENBOT_API_KEY") + .map_err(|_| anyhow::anyhow!("QWENBOT_API_KEY not set"))?; + let base_url = std::env::var("QWENBOT_BASE_URL").ok(); + Ok((api_key, base_url)) + } + // ... other providers + } +} +``` + +--- + +## Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `QWENBOT_API_KEY` | Your Qwen API key | Required | +| `QWENBOT_MODEL` | Default model | `qwen-plus` | +| `QWENBOT_BASE_URL` | API base URL | `https://api.qwen.ai/v1` | +| `QWENBOT_TEMPERATURE` | Default temperature | `0.7` | +| `QWENBOT_MAX_TOKENS` | Max tokens per response | `2048` | + +--- + +## Troubleshooting + +### Issue: "API Key not found" + +**Solution:** +```bash +# Set environment variable +export QWENBOT_API_KEY="your-key-here" + +# Or add to config file +echo '{"apiKey": "your-key-here"}' > ~/.qwen/plugins/qwenbot/config.json +``` + +### Issue: "Plugin not loading" + +**Solution:** +```bash +# Check plugin directory +ls -la ~/.qwen/plugins/qwenbot + +# Reinstall if needed +cd ~/.qwen/plugins/qwenbot +npm install +``` + +### Issue: "Rate limit exceeded" + +**Solution:** +- Wait a few minutes before retrying +- Upgrade your Qwen API plan +- Reduce request frequency + +--- + +## Resources + +- **Qwen Platform:** https://platform.qwen.ai/ +- **Qwen Documentation:** https://help.qwen.ai/ +- **QwenBot GitHub:** https://github.com/QwenLM/qwenbot +- **API Reference:** https://platform.qwen.ai/docs/api + +--- + +## License + +MIT License - See LICENSE file for details. diff --git a/skills/skills-index.json b/skills/skills-index.json index 8844f7a..05ae75c 100644 --- a/skills/skills-index.json +++ b/skills/skills-index.json @@ -1,7 +1,7 @@ { - "version": "1.4.1", + "version": "1.4.2", "lastUpdated": "2026-02-26", - "totalSkills": 76, + "totalSkills": 77, "sources": [ { "name": "awesome-claude-skills", @@ -43,6 +43,12 @@ "url": "https://github.com/shadcn/ui", "skillsCount": 1, "note": "shadcn/ui design patterns and component generation with React, Tailwind CSS, and Radix UI" + }, + { + "name": "qwenbot-integration", + "url": "https://platform.qwen.ai/", + "skillsCount": 1, + "note": "QwenBot AI assistant tool for enhanced code analysis, documentation, and chat capabilities" } ], "skills": [ diff --git a/src/tools/qwenbot.ts b/src/tools/qwenbot.ts new file mode 100644 index 0000000..d85e1d8 --- /dev/null +++ b/src/tools/qwenbot.ts @@ -0,0 +1,222 @@ +/** + * QwenBot Tool for Qwen Code + * + * Provides AI assistant capabilities through Qwen API + */ + +import { writeFile, mkdir } from "fs/promises"; +import { join } from "path"; +import { existsSync } from "fs"; + +const QWENBOT_CONFIG_DIR = join(process.env.HOME || process.env.USERPROFILE || "", ".qwen", "qwenbot"); +const QWENBOT_CONFIG_FILE = join(QWENBOT_CONFIG_DIR, "config.json"); + +export interface QwenBotConfig { + apiKey: string; + model: string; + temperature: number; + maxTokens: number; + baseUrl: string; +} + +const DEFAULT_CONFIG: QwenBotConfig = { + apiKey: "", + model: "qwen-plus", + temperature: 0.7, + maxTokens: 2048, + baseUrl: "https://api.qwen.ai/v1", +}; + +/** + * Initialize QwenBot configuration + */ +export async function initQwenBot(): Promise { + // Create config directory if it doesn't exist + if (!existsSync(QWENBOT_CONFIG_DIR)) { + await mkdir(QWENBOT_CONFIG_DIR, { recursive: true }); + } + + // Load or create config + let config: QwenBotConfig; + + if (existsSync(QWENBOT_CONFIG_FILE)) { + try { + const content = await Bun.file(QWENBOT_CONFIG_FILE).text(); + config = { ...DEFAULT_CONFIG, ...JSON.parse(content) }; + } catch { + config = DEFAULT_CONFIG; + } + } else { + config = DEFAULT_CONFIG; + await saveConfig(config); + } + + // Override with environment variables if set + if (process.env.QWENBOT_API_KEY) { + config.apiKey = process.env.QWENBOT_API_KEY; + } + if (process.env.QWENBOT_MODEL) { + config.model = process.env.QWENBOT_MODEL; + } + if (process.env.QWENBOT_BASE_URL) { + config.baseUrl = process.env.QWENBOT_BASE_URL; + } + + return config; +} + +/** + * Save QwenBot configuration + */ +async function saveConfig(config: QwenBotConfig): Promise { + await writeFile(QWENBOT_CONFIG_FILE, JSON.stringify(config, null, 2) + "\n"); +} + +/** + * Chat with QwenBot + */ +export async function chatWithQwenBot( + message: string, + config?: Partial +): Promise { + const fullConfig = await initQwenBot(); + const mergedConfig = { ...fullConfig, ...config }; + + if (!mergedConfig.apiKey) { + return "[ERROR] QwenBot API key not configured. Please set QWENBOT_API_KEY or configure config.json"; + } + + try { + const response = await fetch(`${mergedConfig.baseUrl}/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${mergedConfig.apiKey}`, + }, + body: JSON.stringify({ + model: mergedConfig.model, + messages: [ + { + role: "system", + content: "You are QwenBot, a helpful AI assistant integrated with Qwen Code.", + }, + { + role: "user", + content: message, + }, + ], + temperature: mergedConfig.temperature, + max_tokens: mergedConfig.maxTokens, + }), + }); + + if (!response.ok) { + const error = await response.text(); + return `[ERROR] QwenBot API error: ${response.status} - ${error}`; + } + + const data = await response.json(); + return data.choices?.[0]?.message?.content || "[ERROR] No response from QwenBot"; + } catch (err) { + return `[ERROR] QwenBot request failed: ${err instanceof Error ? err.message : String(err)}`; + } +} + +/** + * Analyze code with QwenBot + */ +export async function analyzeCodeWithQwenBot( + code: string, + language?: string +): Promise { + const message = language + ? `Analyze this ${language} code and provide feedback:\n\n\`\`\`${language}\n${code}\n\`\`\`` + : `Analyze this code and provide feedback:\n\n\`\`\`\n${code}\n\`\`\``; + + return await chatWithQwenBot(message); +} + +/** + * Generate documentation with QwenBot + */ +export async function generateDocsWithQwenBot( + code: string, + style?: string +): Promise { + const message = style + ? `Generate ${style} style documentation for this code:\n\n\`\`\`\n${code}\n\`\`\`` + : `Generate documentation for this code:\n\n\`\`\`\n${code}\n\`\`\``; + + return await chatWithQwenBot(message); +} + +/** + * Generate tests with QwenBot + */ +export async function generateTestsWithQwenBot( + code: string, + framework?: string +): Promise { + const message = framework + ? `Generate ${framework} tests for this code:\n\n\`\`\`\n${code}\n\`\`\`` + : `Generate unit tests for this code:\n\n\`\`\`\n${code}\n\`\`\``; + + return await chatWithQwenBot(message); +} + +/** + * Explain code with QwenBot + */ +export async function explainCodeWithQwenBot( + code: string +): Promise { + const message = `Explain what this code does in simple terms:\n\n\`\`\`\n${code}\n\`\`\``; + + return await chatWithQwenBot(message); +} + +/** + * Quick command for Qwen Code integration + */ +export async function qwenbotCommand(args: string[]): Promise { + const message = args.join(" "); + + if (!message) { + console.log("Usage: qwenbot "); + console.log(""); + console.log("Examples:"); + console.log(" qwenbot Explain async/await in JavaScript"); + console.log(" qwenbot Review this code for bugs"); + console.log(" qwenbot Generate tests for this function"); + return; + } + + console.log("šŸ¤– QwenBot: Thinking..."); + + const response = await chatWithQwenBot(message); + + console.log("\nšŸ¤– QwenBot: " + response); +} + +/** + * Setup QwenBot configuration interactively + */ +export async function setupQwenBot(): Promise { + console.log("šŸ¤– QwenBot Setup"); + console.log("================"); + console.log(""); + console.log("To get your API key, visit: https://platform.qwen.ai/api-keys"); + console.log(""); + + const config = await initQwenBot(); + + console.log("Current configuration:"); + console.log(` Model: ${config.model}`); + console.log(` Base URL: ${config.baseUrl}`); + console.log(` Temperature: ${config.temperature}`); + console.log(` Max Tokens: ${config.maxTokens}`); + console.log(` API Key: ${config.apiKey ? "****" + config.apiKey.slice(-4) : "Not set"}`); + console.log(""); + console.log("To configure, edit: " + QWENBOT_CONFIG_FILE); + console.log("Or set environment variable: QWENBOT_API_KEY"); +}