Merge branch 'dev' into quote-tweet-link
93
.github/ISSUE_TEMPLATE/bounty_claim.md
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
name: Bounty Claim
|
||||
about: Claim a bounty task or propose a new bounty
|
||||
title: '[BOUNTY CLAIM] '
|
||||
labels: bounty
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 💰 Bounty Information
|
||||
|
||||
**Claiming existing bounty?**
|
||||
- Issue #: <!-- Link to existing bounty issue -->
|
||||
- Bounty amount: <!-- If specified -->
|
||||
|
||||
**OR proposing new bounty?**
|
||||
- Proposed feature/fix: <!-- Brief description -->
|
||||
- Estimated effort: [Small / Medium / Large]
|
||||
|
||||
---
|
||||
|
||||
## 👤 About You
|
||||
|
||||
**Name/Username:** <!-- Your name or GitHub username -->
|
||||
|
||||
**Contact:**
|
||||
- GitHub: @your_username
|
||||
- Telegram: @your_telegram (optional)
|
||||
- Email: your@email.com (optional)
|
||||
|
||||
**Relevant Experience:**
|
||||
- <!-- Link to your GitHub profile -->
|
||||
- <!-- Previous contributions or similar projects -->
|
||||
- <!-- Relevant skills (Go, React, trading systems, etc.) -->
|
||||
|
||||
---
|
||||
|
||||
## 📋 Implementation Plan
|
||||
|
||||
### 1. Approach
|
||||
<!-- Describe your technical approach -->
|
||||
- How will you implement this?
|
||||
- What components will be affected?
|
||||
- Any dependencies or libraries needed?
|
||||
|
||||
### 2. Timeline
|
||||
- **Start date:** <!-- When you plan to start -->
|
||||
- **Estimated completion:** <!-- How long will it take? -->
|
||||
- **Milestones:**
|
||||
- [ ] Week 1: ...
|
||||
- [ ] Week 2: ...
|
||||
- [ ] Week 3: ...
|
||||
|
||||
### 3. Deliverables
|
||||
- [ ] Working code (merged PR)
|
||||
- [ ] Unit tests (if applicable)
|
||||
- [ ] Documentation updates
|
||||
- [ ] Demo video/screenshots
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Questions for Maintainers
|
||||
|
||||
<!-- Any questions you have about the requirements? -->
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
---
|
||||
|
||||
## 📚 References
|
||||
|
||||
<!-- Any relevant links, documentation, or examples -->
|
||||
|
||||
-
|
||||
|
||||
---
|
||||
|
||||
## ✅ Acknowledgment
|
||||
|
||||
By claiming this bounty, I acknowledge that:
|
||||
- [ ] I have read the [Contributing Guide](../../CONTRIBUTING.md)
|
||||
- [ ] I will follow the [Code of Conduct](../../CODE_OF_CONDUCT.md)
|
||||
- [ ] I understand the acceptance criteria
|
||||
- [ ] My contribution will be licensed under MIT License
|
||||
- [ ] Payment is subject to successful PR merge
|
||||
|
||||
---
|
||||
|
||||
**For maintainers:**
|
||||
- [ ] Bounty claim approved
|
||||
- [ ] Issue assigned to claimant
|
||||
- [ ] Timeline agreed upon
|
||||
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug to help us improve NOFX
|
||||
title: '[BUG] '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 🐛 Bug Description
|
||||
<!-- A clear and concise description of what the bug is -->
|
||||
|
||||
## 📋 Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Run command '...'
|
||||
4. See error
|
||||
|
||||
## ✅ Expected Behavior
|
||||
<!-- What you expected to happen -->
|
||||
|
||||
## ❌ Actual Behavior
|
||||
<!-- What actually happened -->
|
||||
|
||||
## 📸 Screenshots / Logs
|
||||
<!-- If applicable, add screenshots or error logs to help explain your problem -->
|
||||
|
||||
```
|
||||
Paste logs here
|
||||
```
|
||||
|
||||
## 💻 Environment
|
||||
- **OS:** [e.g. macOS 13, Ubuntu 22.04, Windows 11]
|
||||
- **Go Version:** [e.g. 1.21.5]
|
||||
- **Node.js Version:** [e.g. 18.17.0]
|
||||
- **NOFX Version/Commit:** [e.g. v3.0.0 or commit hash]
|
||||
- **Deployment Method:** [Docker / Manual / PM2]
|
||||
- **Exchange:** [Binance / Hyperliquid / Aster]
|
||||
|
||||
## 🔧 Additional Context
|
||||
<!-- Add any other context about the problem here -->
|
||||
|
||||
- Does this happen consistently or intermittently?
|
||||
- Did it work before? When did it break?
|
||||
- Any recent configuration changes?
|
||||
|
||||
## ✋ Possible Solution
|
||||
<!-- Optional: If you have ideas on how to fix this -->
|
||||
153
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
# Pull Request
|
||||
|
||||
## 📝 Description
|
||||
|
||||
<!-- Provide a brief summary of your changes -->
|
||||
|
||||
## 🎯 Type of Change
|
||||
|
||||
<!-- Mark the relevant option with an "x" -->
|
||||
|
||||
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] ✨ New feature (non-breaking change which adds functionality)
|
||||
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] 📝 Documentation update
|
||||
- [ ] 🎨 Code style update (formatting, renaming)
|
||||
- [ ] ♻️ Refactoring (no functional changes)
|
||||
- [ ] ⚡ Performance improvement
|
||||
- [ ] ✅ Test update
|
||||
- [ ] 🔧 Build/config change
|
||||
|
||||
## 🔗 Related Issues
|
||||
|
||||
<!-- Link related issues below. Use "Closes #123" to auto-close issues when PR is merged -->
|
||||
|
||||
- Closes #
|
||||
- Related to #
|
||||
|
||||
## 📋 Changes Made
|
||||
|
||||
<!-- List the specific changes you made -->
|
||||
|
||||
- Change 1
|
||||
- Change 2
|
||||
- Change 3
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Manual Testing
|
||||
|
||||
<!-- Describe how you tested your changes -->
|
||||
|
||||
- [ ] Tested locally (manual verification)
|
||||
- [ ] Tested on testnet (for exchange integrations)
|
||||
- [ ] Tested with different configurations
|
||||
- [ ] Verified no existing functionality broke
|
||||
|
||||
### Test Environment
|
||||
|
||||
- **OS:** [e.g. macOS, Ubuntu]
|
||||
- **Go Version:** [e.g. 1.21.5]
|
||||
- **Exchange:** [if applicable]
|
||||
|
||||
### Test Results
|
||||
|
||||
<!-- Paste relevant test output or describe results -->
|
||||
|
||||
```
|
||||
Test output here
|
||||
```
|
||||
|
||||
## 📸 Screenshots / Demo
|
||||
|
||||
<!-- If applicable, add screenshots or video demo -->
|
||||
|
||||
<!-- For UI changes, include before/after screenshots -->
|
||||
|
||||
**Before:**
|
||||
|
||||
|
||||
**After:**
|
||||
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
<!-- Mark completed items with an "x" -->
|
||||
|
||||
### Code Quality
|
||||
|
||||
- [ ] My code follows the project's code style ([Contributing Guide](../CONTRIBUTING.md))
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] My changes generate no new warnings or errors
|
||||
- [ ] Code compiles successfully (`go build` / `npm run build`)
|
||||
- [ ] I have run `go fmt` (for Go code)
|
||||
|
||||
### Testing
|
||||
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally
|
||||
- [ ] I have tested on testnet (for trading/exchange changes)
|
||||
|
||||
### Documentation
|
||||
|
||||
- [ ] I have updated the documentation accordingly
|
||||
- [ ] I have updated the README if needed
|
||||
- [ ] I have added inline code comments where necessary
|
||||
- [ ] I have updated type definitions (for TypeScript changes)
|
||||
|
||||
### Git
|
||||
|
||||
- [ ] My commits follow the conventional commits format (`feat:`, `fix:`, etc.)
|
||||
- [ ] I have rebased my branch on the latest `dev` branch
|
||||
- [ ] There are no merge conflicts
|
||||
|
||||
## 🔒 Security Considerations
|
||||
|
||||
<!-- Answer these questions for security-sensitive changes -->
|
||||
|
||||
- [ ] No API keys or secrets are hardcoded
|
||||
- [ ] User inputs are properly validated
|
||||
- [ ] No SQL injection vulnerabilities introduced
|
||||
- [ ] Authentication/authorization properly handled
|
||||
- [ ] N/A (not security-related)
|
||||
|
||||
## ⚡ Performance Impact
|
||||
|
||||
<!-- Describe any performance implications -->
|
||||
|
||||
- [ ] No significant performance impact
|
||||
- [ ] Performance improved
|
||||
- [ ] Performance may be impacted (explain below)
|
||||
|
||||
<!-- If performance impacted, explain: -->
|
||||
|
||||
## 📚 Additional Notes
|
||||
|
||||
<!-- Any additional information for reviewers -->
|
||||
|
||||
---
|
||||
|
||||
## For Bounty Claims
|
||||
|
||||
<!-- Fill this section only if claiming a bounty -->
|
||||
|
||||
- [ ] This PR is for bounty issue #
|
||||
- [ ] All acceptance criteria from the bounty issue are met
|
||||
- [ ] I have included a demo video/screenshots
|
||||
- [ ] I am ready for payment upon merge
|
||||
|
||||
**Payment Details:** <!-- Discuss privately with maintainers -->
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Reviewer Notes
|
||||
|
||||
<!-- Optional: anything specific you want reviewers to focus on? -->
|
||||
|
||||
---
|
||||
|
||||
**By submitting this PR, I confirm that:**
|
||||
- [ ] I have read the [Contributing Guidelines](../CONTRIBUTING.md)
|
||||
- [ ] I agree to the [Code of Conduct](../CODE_OF_CONDUCT.md)
|
||||
- [ ] My contribution is licensed under the MIT License
|
||||
127
.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
# Auto-labeler configuration
|
||||
# Automatically adds labels based on changed files
|
||||
|
||||
# Area: Frontend
|
||||
'area: frontend':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'web/**/*'
|
||||
- '*.tsx'
|
||||
- '*.ts'
|
||||
- '*.jsx'
|
||||
- '*.js'
|
||||
- '*.css'
|
||||
|
||||
# Area: Backend
|
||||
'area: backend':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*.go'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'cmd/**/*'
|
||||
- 'internal/**/*'
|
||||
- 'pkg/**/*'
|
||||
|
||||
# Area: Exchange
|
||||
'area: exchange':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'internal/exchange/**/*'
|
||||
- 'pkg/exchange/**/*'
|
||||
- '**/binance*.go'
|
||||
- '**/hyperliquid*.go'
|
||||
- '**/aster*.go'
|
||||
- '**/okx*.go'
|
||||
- '**/bybit*.go'
|
||||
|
||||
# Area: AI
|
||||
'area: ai':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'internal/ai/**/*'
|
||||
- 'pkg/ai/**/*'
|
||||
- '**/deepseek*.go'
|
||||
- '**/qwen*.go'
|
||||
- '**/openai*.go'
|
||||
- '**/claude*.go'
|
||||
|
||||
# Area: API
|
||||
'area: api':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'internal/api/**/*'
|
||||
- 'pkg/api/**/*'
|
||||
- '**/handler*.go'
|
||||
- '**/router*.go'
|
||||
|
||||
# Area: Security
|
||||
'area: security':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/auth*.go'
|
||||
- '**/jwt*.go'
|
||||
- '**/encryption*.go'
|
||||
- '**/crypto*.go'
|
||||
- 'SECURITY.md'
|
||||
|
||||
# Area: Database
|
||||
'area: database':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'internal/database/**/*'
|
||||
- 'internal/db/**/*'
|
||||
- '**/migration*.go'
|
||||
- '**/*.sql'
|
||||
- '**/schema*.go'
|
||||
|
||||
# Area: UI/UX
|
||||
'area: ui/ux':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'web/src/components/**/*'
|
||||
- 'web/src/pages/**/*'
|
||||
- '**/*.css'
|
||||
- '**/style*.ts'
|
||||
|
||||
# Area: Deployment
|
||||
'area: deployment':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'Dockerfile'
|
||||
- 'docker-compose*.yml'
|
||||
- '.github/workflows/**/*'
|
||||
- 'start.sh'
|
||||
- '**/*deploy*.md'
|
||||
|
||||
# Type: Documentation
|
||||
'type: documentation':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'docs/**/*'
|
||||
- '*.md'
|
||||
- 'README*'
|
||||
- 'CHANGELOG*'
|
||||
- 'CONTRIBUTING.md'
|
||||
- 'CODE_OF_CONDUCT.md'
|
||||
|
||||
# Type: Test
|
||||
'type: test':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*_test.go'
|
||||
- 'test/**/*'
|
||||
- '**/*.test.ts'
|
||||
- '**/*.test.tsx'
|
||||
- '**/*.spec.ts'
|
||||
|
||||
# Dependencies
|
||||
'dependencies':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'web/package.json'
|
||||
- 'web/package-lock.json'
|
||||
180
.github/labels.yml
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
# GitHub Labels Configuration
|
||||
# Use https://github.com/crazy-max/ghaction-github-labeler to sync labels
|
||||
|
||||
# Priority Labels
|
||||
- name: "priority: critical"
|
||||
color: "d73a4a"
|
||||
description: "Critical priority - requires immediate attention"
|
||||
|
||||
- name: "priority: high"
|
||||
color: "ff6b6b"
|
||||
description: "High priority - should be addressed soon"
|
||||
|
||||
- name: "priority: medium"
|
||||
color: "fbca04"
|
||||
description: "Medium priority - normal queue"
|
||||
|
||||
- name: "priority: low"
|
||||
color: "0e8a16"
|
||||
description: "Low priority - nice to have"
|
||||
|
||||
# Type Labels
|
||||
- name: "type: bug"
|
||||
color: "d73a4a"
|
||||
description: "Something isn't working"
|
||||
|
||||
- name: "type: feature"
|
||||
color: "a2eeef"
|
||||
description: "New feature or request"
|
||||
|
||||
- name: "type: enhancement"
|
||||
color: "84b6eb"
|
||||
description: "Improvement to existing feature"
|
||||
|
||||
- name: "type: documentation"
|
||||
color: "0075ca"
|
||||
description: "Documentation improvements"
|
||||
|
||||
- name: "type: security"
|
||||
color: "ee0701"
|
||||
description: "Security-related changes"
|
||||
|
||||
- name: "type: performance"
|
||||
color: "f9d0c4"
|
||||
description: "Performance improvements"
|
||||
|
||||
- name: "type: refactor"
|
||||
color: "fbca04"
|
||||
description: "Code refactoring"
|
||||
|
||||
- name: "type: test"
|
||||
color: "c5def5"
|
||||
description: "Test-related changes"
|
||||
|
||||
# Status Labels
|
||||
- name: "status: needs review"
|
||||
color: "fbca04"
|
||||
description: "PR is ready for review"
|
||||
|
||||
- name: "status: needs changes"
|
||||
color: "d93f0b"
|
||||
description: "PR needs changes based on review"
|
||||
|
||||
- name: "status: on hold"
|
||||
color: "fef2c0"
|
||||
description: "PR/issue is on hold"
|
||||
|
||||
- name: "status: in progress"
|
||||
color: "0e8a16"
|
||||
description: "Currently being worked on"
|
||||
|
||||
- name: "status: blocked"
|
||||
color: "d93f0b"
|
||||
description: "Blocked by another issue/PR"
|
||||
|
||||
# Area Labels (aligned with roadmap)
|
||||
- name: "area: security"
|
||||
color: "ee0701"
|
||||
description: "Security enhancements (Phase 1.1)"
|
||||
|
||||
- name: "area: ai"
|
||||
color: "7057ff"
|
||||
description: "AI capabilities and models (Phase 1.2)"
|
||||
|
||||
- name: "area: exchange"
|
||||
color: "0075ca"
|
||||
description: "Exchange integrations (Phase 1.3)"
|
||||
|
||||
- name: "area: architecture"
|
||||
color: "d4c5f9"
|
||||
description: "Project structure refactoring (Phase 1.4)"
|
||||
|
||||
- name: "area: ui/ux"
|
||||
color: "c2e0c6"
|
||||
description: "User experience improvements (Phase 1.5)"
|
||||
|
||||
- name: "area: frontend"
|
||||
color: "bfdadc"
|
||||
description: "Frontend (React/TypeScript)"
|
||||
|
||||
- name: "area: backend"
|
||||
color: "c5def5"
|
||||
description: "Backend (Go)"
|
||||
|
||||
- name: "area: api"
|
||||
color: "0e8a16"
|
||||
description: "API endpoints"
|
||||
|
||||
- name: "area: database"
|
||||
color: "f9d0c4"
|
||||
description: "Database changes"
|
||||
|
||||
- name: "area: deployment"
|
||||
color: "fbca04"
|
||||
description: "Deployment and CI/CD"
|
||||
|
||||
# Special Labels
|
||||
- name: "good first issue"
|
||||
color: "7057ff"
|
||||
description: "Good for newcomers"
|
||||
|
||||
- name: "help wanted"
|
||||
color: "008672"
|
||||
description: "Extra attention is needed"
|
||||
|
||||
- name: "bounty"
|
||||
color: "1d76db"
|
||||
description: "Bounty available for this issue"
|
||||
|
||||
- name: "bounty: claimed"
|
||||
color: "5319e7"
|
||||
description: "Bounty has been claimed"
|
||||
|
||||
- name: "bounty: paid"
|
||||
color: "0e8a16"
|
||||
description: "Bounty has been paid"
|
||||
|
||||
- name: "RFC"
|
||||
color: "d4c5f9"
|
||||
description: "Request for Comments - needs discussion"
|
||||
|
||||
- name: "breaking change"
|
||||
color: "d73a4a"
|
||||
description: "Includes breaking changes"
|
||||
|
||||
- name: "duplicate"
|
||||
color: "cfd3d7"
|
||||
description: "This issue or pull request already exists"
|
||||
|
||||
- name: "invalid"
|
||||
color: "e4e669"
|
||||
description: "This doesn't seem right"
|
||||
|
||||
- name: "wontfix"
|
||||
color: "ffffff"
|
||||
description: "This will not be worked on"
|
||||
|
||||
- name: "dependencies"
|
||||
color: "0366d6"
|
||||
description: "Dependency updates"
|
||||
|
||||
# Roadmap Phases
|
||||
- name: "roadmap: phase-1"
|
||||
color: "0e8a16"
|
||||
description: "Core Infrastructure Enhancement"
|
||||
|
||||
- name: "roadmap: phase-2"
|
||||
color: "fbca04"
|
||||
description: "Testing & Stability"
|
||||
|
||||
- name: "roadmap: phase-3"
|
||||
color: "0075ca"
|
||||
description: "Universal Market Expansion"
|
||||
|
||||
- name: "roadmap: phase-4"
|
||||
color: "7057ff"
|
||||
description: "Advanced AI & Automation"
|
||||
|
||||
- name: "roadmap: phase-5"
|
||||
color: "d73a4a"
|
||||
description: "Enterprise & Scaling"
|
||||
331
.github/workflows/pr-checks-advisory.yml
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
name: PR Checks (Advisory)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: [main, dev]
|
||||
|
||||
# These checks are advisory only - they won't block PR merging
|
||||
# Results will be posted as comments to help contributors improve their PRs
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
checks: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
pr-info:
|
||||
name: PR Information
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR title format
|
||||
id: check-title
|
||||
run: |
|
||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||
|
||||
# Check if title follows conventional commits
|
||||
if echo "$PR_TITLE" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|chore|ci|security)(\(.+\))?: .+"; then
|
||||
echo "status=✅ Good" >> $GITHUB_OUTPUT
|
||||
echo "message=PR title follows Conventional Commits format" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=⚠️ Suggestion" >> $GITHUB_OUTPUT
|
||||
echo "message=Consider using Conventional Commits format: type(scope): description" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Calculate PR size
|
||||
id: pr-size
|
||||
run: |
|
||||
ADDITIONS=${{ github.event.pull_request.additions }}
|
||||
DELETIONS=${{ github.event.pull_request.deletions }}
|
||||
TOTAL=$((ADDITIONS + DELETIONS))
|
||||
|
||||
if [ $TOTAL -lt 100 ]; then
|
||||
echo "size=🟢 Small" >> $GITHUB_OUTPUT
|
||||
echo "label=size: small" >> $GITHUB_OUTPUT
|
||||
elif [ $TOTAL -lt 500 ]; then
|
||||
echo "size=🟡 Medium" >> $GITHUB_OUTPUT
|
||||
echo "label=size: medium" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "size=🔴 Large" >> $GITHUB_OUTPUT
|
||||
echo "label=size: large" >> $GITHUB_OUTPUT
|
||||
echo "suggestion=Consider breaking this into smaller PRs for easier review" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "lines=$TOTAL" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Post advisory comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const titleStatus = '${{ steps.check-title.outputs.status }}';
|
||||
const titleMessage = '${{ steps.check-title.outputs.message }}';
|
||||
const prSize = '${{ steps.pr-size.outputs.size }}';
|
||||
const prLines = '${{ steps.pr-size.outputs.lines }}';
|
||||
const sizeSuggestion = '${{ steps.pr-size.outputs.suggestion }}' || '';
|
||||
|
||||
let comment = '## 🤖 PR Advisory Feedback\n\n';
|
||||
comment += 'Thank you for your contribution! Here\'s some automated feedback to help improve your PR:\n\n';
|
||||
comment += '### PR Title\n';
|
||||
comment += titleStatus + ' ' + titleMessage + '\n\n';
|
||||
comment += '### PR Size\n';
|
||||
comment += prSize + ' (' + prLines + ' lines changed)\n';
|
||||
if (sizeSuggestion) {
|
||||
comment += '\n💡 **Suggestion:** ' + sizeSuggestion + '\n';
|
||||
}
|
||||
comment += '\n---\n\n';
|
||||
comment += '### 📖 New PR Management System\n\n';
|
||||
comment += 'We\'re introducing a new PR management system! These checks are **advisory only** and won\'t block your PR.\n\n';
|
||||
comment += '**Want to check your PR against new standards?**\n';
|
||||
comment += '```bash\n';
|
||||
comment += '# Run the PR health check tool\n';
|
||||
comment += './scripts/pr-check.sh\n';
|
||||
comment += '```\n\n';
|
||||
comment += 'This tool will:\n';
|
||||
comment += '- 🔍 Analyze your PR (doesn\'t modify anything)\n';
|
||||
comment += '- ✅ Show what\'s already good\n';
|
||||
comment += '- ⚠️ Point out issues\n';
|
||||
comment += '- 💡 Give specific suggestions on how to fix\n\n';
|
||||
comment += '**Learn more:**\n';
|
||||
comment += '- [Migration Guide](https://github.com/tinkle-community/nofx/blob/dev/docs/community/MIGRATION_ANNOUNCEMENT.md)\n';
|
||||
comment += '- [Contributing Guidelines](https://github.com/tinkle-community/nofx/blob/dev/CONTRIBUTING.md)\n\n';
|
||||
comment += '**Questions?** Just ask in the comments! We\'re here to help. 🙏\n\n';
|
||||
comment += '---\n\n';
|
||||
comment += '*This is an automated message. It won\'t affect your PR being merged.*';
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
|
||||
backend-checks:
|
||||
name: Backend Checks (Advisory)
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libta-lib-dev || true
|
||||
go mod download || true
|
||||
|
||||
- name: Check Go formatting
|
||||
id: go-fmt
|
||||
continue-on-error: true
|
||||
run: |
|
||||
UNFORMATTED=$(gofmt -l . 2>/dev/null || echo "")
|
||||
if [ -n "$UNFORMATTED" ]; then
|
||||
echo "status=⚠️ Needs formatting" >> $GITHUB_OUTPUT
|
||||
echo "files<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$UNFORMATTED" | head -10 >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=✅ Good" >> $GITHUB_OUTPUT
|
||||
echo "files=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run go vet
|
||||
id: go-vet
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if go vet ./... 2>&1 | tee vet-output.txt; then
|
||||
echo "status=✅ Good" >> $GITHUB_OUTPUT
|
||||
echo "output=" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=⚠️ Issues found" >> $GITHUB_OUTPUT
|
||||
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||
cat vet-output.txt | head -20 >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run tests
|
||||
id: go-test
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if go test ./... -v 2>&1 | tee test-output.txt; then
|
||||
echo "status=✅ Passed" >> $GITHUB_OUTPUT
|
||||
echo "output=" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=⚠️ Failed" >> $GITHUB_OUTPUT
|
||||
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||
cat test-output.txt | tail -30 >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Post backend feedback
|
||||
if: always()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fmtStatus = '${{ steps.go-fmt.outputs.status }}' || '⚠️ Skipped';
|
||||
const vetStatus = '${{ steps.go-vet.outputs.status }}' || '⚠️ Skipped';
|
||||
const testStatus = '${{ steps.go-test.outputs.status }}' || '⚠️ Skipped';
|
||||
const fmtFiles = `${{ steps.go-fmt.outputs.files }}`;
|
||||
const vetOutput = `${{ steps.go-vet.outputs.output }}`;
|
||||
const testOutput = `${{ steps.go-test.outputs.output }}`;
|
||||
|
||||
let comment = '## 🔧 Backend Checks (Advisory)\n\n';
|
||||
comment += '### Go Formatting\n';
|
||||
comment += fmtStatus + '\n';
|
||||
if (fmtFiles) {
|
||||
comment += '\nFiles needing formatting:\n```\n' + fmtFiles + '\n```\n';
|
||||
}
|
||||
comment += '\n### Go Vet\n';
|
||||
comment += vetStatus + '\n';
|
||||
if (vetOutput) {
|
||||
comment += '\n```\n' + vetOutput.substring(0, 500) + '\n```\n';
|
||||
}
|
||||
comment += '\n### Tests\n';
|
||||
comment += testStatus + '\n';
|
||||
if (testOutput) {
|
||||
comment += '\n```\n' + testOutput.substring(0, 1000) + '\n```\n';
|
||||
}
|
||||
comment += '\n---\n\n';
|
||||
comment += '💡 **To fix locally:**\n';
|
||||
comment += '```bash\n';
|
||||
comment += '# Format code\n';
|
||||
comment += 'go fmt ./...\n\n';
|
||||
comment += '# Check for issues\n';
|
||||
comment += 'go vet ./...\n\n';
|
||||
comment += '# Run tests\n';
|
||||
comment += 'go test ./...\n';
|
||||
comment += '```\n\n';
|
||||
comment += '*These checks are advisory and won\'t block merging. Need help? Just ask!*';
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
|
||||
frontend-checks:
|
||||
name: Frontend Checks (Advisory)
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Check if web directory exists
|
||||
id: check-web
|
||||
run: |
|
||||
if [ -d "web" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.check-web.outputs.exists == 'true'
|
||||
working-directory: ./web
|
||||
continue-on-error: true
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
if: steps.check-web.outputs.exists == 'true'
|
||||
id: lint
|
||||
working-directory: ./web
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if npm run lint 2>&1 | tee lint-output.txt; then
|
||||
echo "status=✅ Good" >> $GITHUB_OUTPUT
|
||||
echo "output=" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=⚠️ Issues found" >> $GITHUB_OUTPUT
|
||||
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||
cat lint-output.txt | head -20 >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Type check
|
||||
if: steps.check-web.outputs.exists == 'true'
|
||||
id: typecheck
|
||||
working-directory: ./web
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if npm run type-check 2>&1 | tee typecheck-output.txt; then
|
||||
echo "status=✅ Good" >> $GITHUB_OUTPUT
|
||||
echo "output=" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=⚠️ Issues found" >> $GITHUB_OUTPUT
|
||||
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||
cat typecheck-output.txt | head -20 >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
if: steps.check-web.outputs.exists == 'true'
|
||||
id: build
|
||||
working-directory: ./web
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if npm run build 2>&1 | tee build-output.txt; then
|
||||
echo "status=✅ Success" >> $GITHUB_OUTPUT
|
||||
echo "output=" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=⚠️ Failed" >> $GITHUB_OUTPUT
|
||||
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||
cat build-output.txt | tail -20 >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Post frontend feedback
|
||||
if: always() && steps.check-web.outputs.exists == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const lintStatus = '${{ steps.lint.outputs.status }}' || '⚠️ Skipped';
|
||||
const typecheckStatus = '${{ steps.typecheck.outputs.status }}' || '⚠️ Skipped';
|
||||
const buildStatus = '${{ steps.build.outputs.status }}' || '⚠️ Skipped';
|
||||
const lintOutput = `${{ steps.lint.outputs.output }}`;
|
||||
const typecheckOutput = `${{ steps.typecheck.outputs.output }}`;
|
||||
const buildOutput = `${{ steps.build.outputs.output }}`;
|
||||
|
||||
let comment = '## ⚛️ Frontend Checks (Advisory)\n\n';
|
||||
comment += '### Linting\n';
|
||||
comment += lintStatus + '\n';
|
||||
if (lintOutput) {
|
||||
comment += '\n```\n' + lintOutput.substring(0, 500) + '\n```\n';
|
||||
}
|
||||
comment += '\n### Type Checking\n';
|
||||
comment += typecheckStatus + '\n';
|
||||
if (typecheckOutput) {
|
||||
comment += '\n```\n' + typecheckOutput.substring(0, 500) + '\n```\n';
|
||||
}
|
||||
comment += '\n### Build\n';
|
||||
comment += buildStatus + '\n';
|
||||
if (buildOutput) {
|
||||
comment += '\n```\n' + buildOutput.substring(0, 500) + '\n```\n';
|
||||
}
|
||||
comment += '\n---\n\n';
|
||||
comment += '💡 **To fix locally:**\n';
|
||||
comment += '```bash\n';
|
||||
comment += 'cd web\n\n';
|
||||
comment += '# Fix linting issues\n';
|
||||
comment += 'npm run lint -- --fix\n\n';
|
||||
comment += '# Check types\n';
|
||||
comment += 'npm run type-check\n\n';
|
||||
comment += '# Test build\n';
|
||||
comment += 'npm run build\n';
|
||||
comment += '```\n\n';
|
||||
comment += '*These checks are advisory and won\'t block merging. Need help? Just ask!*';
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
237
.github/workflows/pr-checks.yml
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
name: PR Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
branches:
|
||||
- dev
|
||||
- main
|
||||
|
||||
jobs:
|
||||
# Validate PR title and description
|
||||
validate-pr:
|
||||
name: Validate PR Format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR title format
|
||||
uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: |
|
||||
feat
|
||||
fix
|
||||
docs
|
||||
style
|
||||
refactor
|
||||
perf
|
||||
test
|
||||
chore
|
||||
ci
|
||||
security
|
||||
scopes: |
|
||||
exchange
|
||||
trader
|
||||
ai
|
||||
api
|
||||
ui
|
||||
frontend
|
||||
backend
|
||||
security
|
||||
deps
|
||||
requireScope: false
|
||||
|
||||
- name: Check PR size
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const additions = pr.additions;
|
||||
const deletions = pr.deletions;
|
||||
const total = additions + deletions;
|
||||
|
||||
let label = '';
|
||||
let comment = '';
|
||||
|
||||
if (total < 300) {
|
||||
label = 'size: small';
|
||||
comment = '✅ This PR is **small** and easy to review!';
|
||||
} else if (total < 1000) {
|
||||
label = 'size: medium';
|
||||
comment = '⚠️ This PR is **medium** sized. Consider breaking it into smaller PRs if possible.';
|
||||
} else {
|
||||
label = 'size: large';
|
||||
comment = '🚨 This PR is **large** (>' + total + ' lines changed). Please consider breaking it into smaller, focused PRs for easier review.';
|
||||
}
|
||||
|
||||
// Add size label
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: [label]
|
||||
});
|
||||
|
||||
// Add comment for large PRs
|
||||
if (total >= 1000) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
# Backend tests
|
||||
backend-tests:
|
||||
name: Backend Tests (Go)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Install TA-Lib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libta-lib-dev
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Download dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Run go fmt
|
||||
run: |
|
||||
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
|
||||
echo "Please run 'go fmt' on your code"
|
||||
gofmt -s -l .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v -race -coverprofile=coverage.out ./...
|
||||
|
||||
- name: Build
|
||||
run: go build -v -o nofx
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.out
|
||||
flags: backend
|
||||
|
||||
# Frontend tests
|
||||
frontend-tests:
|
||||
name: Frontend Tests (React/TypeScript)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Cache Node modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: web/node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('web/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./web
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
working-directory: ./web
|
||||
run: npm run lint
|
||||
|
||||
- name: Run type check
|
||||
working-directory: ./web
|
||||
run: npm run type-check || true # Don't fail on type errors for now
|
||||
|
||||
- name: Build
|
||||
working-directory: ./web
|
||||
run: npm run build
|
||||
|
||||
# Auto-label based on files changed
|
||||
auto-label:
|
||||
name: Auto Label PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
configuration-path: .github/labeler.yml
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Check for security issues
|
||||
security-check:
|
||||
name: Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
scan-ref: '.'
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
|
||||
- name: Upload Trivy results
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
|
||||
# Check for secrets in code
|
||||
secrets-check:
|
||||
name: Check for Secrets
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Gitleaks
|
||||
uses: gitleaks/gitleaks-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# All checks passed
|
||||
all-checks:
|
||||
name: All Checks Passed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [validate-pr, backend-tests, frontend-tests, security-check, secrets-check]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check all jobs
|
||||
run: |
|
||||
if [ "${{ contains(needs.*.result, 'failure') }}" == "true" ]; then
|
||||
echo "Some checks failed"
|
||||
exit 1
|
||||
else
|
||||
echo "All checks passed!"
|
||||
fi
|
||||
4
.gitignore
vendored
@@ -3,6 +3,9 @@
|
||||
*.iml
|
||||
*.xml
|
||||
|
||||
# AI 工具
|
||||
.claude/
|
||||
|
||||
# 编译产物
|
||||
nofx-auto
|
||||
*.exe
|
||||
@@ -26,6 +29,7 @@ Thumbs.db
|
||||
# 环境变量
|
||||
.env
|
||||
config.json
|
||||
config.db
|
||||
|
||||
# 决策日志
|
||||
decision_logs/
|
||||
|
||||
203
CHANGELOG.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the NOFX project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
**Languages:** [English](CHANGELOG.md) | [中文](CHANGELOG.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Documentation system with multi-language support (EN/CN/RU/UK)
|
||||
- Complete getting-started guides (Docker, PM2, Custom API)
|
||||
- Architecture documentation with system design details
|
||||
- User guides with FAQ and troubleshooting
|
||||
- Community documentation with bounty programs
|
||||
|
||||
### Changed
|
||||
- Reorganized documentation structure into logical categories
|
||||
- Updated all README files with proper navigation links
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0] - 2025-10-30
|
||||
|
||||
### Added - Major Architecture Transformation 🚀
|
||||
|
||||
**Complete System Redesign - Web-Based Configuration Platform**
|
||||
|
||||
This is a **major breaking update** that completely transforms NOFX from a static config-based system to a modern web-based trading platform.
|
||||
|
||||
#### Database-Driven Architecture
|
||||
- SQLite integration replacing static JSON config
|
||||
- Persistent storage with automatic timestamps
|
||||
- Foreign key relationships and triggers for data consistency
|
||||
- Separate tables for AI models, exchanges, traders, and system config
|
||||
|
||||
#### Web-Based Configuration Interface
|
||||
- Complete web-based configuration management (no more JSON editing)
|
||||
- AI Model setup through web interface (DeepSeek/Qwen API keys)
|
||||
- Exchange management (Binance/Hyperliquid credentials)
|
||||
- Dynamic trader creation (combine any AI model with any exchange)
|
||||
- Real-time control (start/stop traders without system restart)
|
||||
|
||||
#### Flexible Architecture
|
||||
- Separation of concerns (AI models and exchanges independent)
|
||||
- Mix & match capability (unlimited combinations)
|
||||
- Scalable design (support for unlimited traders)
|
||||
- Clean slate approach (no default traders)
|
||||
|
||||
#### Enhanced API Layer
|
||||
- RESTful design with complete CRUD operations
|
||||
- New endpoints:
|
||||
- `GET/PUT /api/models` - AI model configuration
|
||||
- `GET/PUT /api/exchanges` - Exchange configuration
|
||||
- `POST/DELETE /api/traders` - Trader management
|
||||
- `POST /api/traders/:id/start|stop` - Trader control
|
||||
- Updated documentation for all API endpoints
|
||||
|
||||
#### Modernized Codebase
|
||||
- Type safety with proper separation of configuration types
|
||||
- Database abstraction with prepared statements
|
||||
- Comprehensive error handling and validation
|
||||
- Better code organization (database, API, business logic)
|
||||
|
||||
### Changed
|
||||
- **BREAKING**: Old `config.json` files no longer used
|
||||
- Configuration must be done through web interface
|
||||
- Much easier setup and better UX
|
||||
- No more server restarts for configuration changes
|
||||
|
||||
### Why This Matters
|
||||
- 🎯 **User Experience**: Much easier to configure and manage
|
||||
- 🔧 **Flexibility**: Create any combination of AI models and exchanges
|
||||
- 📊 **Scalability**: Support for complex multi-trader setups
|
||||
- 🔒 **Reliability**: Database ensures data persistence and consistency
|
||||
- 🚀 **Future-Proof**: Foundation for advanced features
|
||||
|
||||
---
|
||||
|
||||
## [2.0.2] - 2025-10-29
|
||||
|
||||
### Fixed - Critical Bug Fixes: Trade History & Performance Analysis
|
||||
|
||||
#### PnL Calculation - Major Error Fixed
|
||||
- **Fixed**: PnL now calculated as actual USDT amount instead of percentage only
|
||||
- Previously ignored position size and leverage (e.g., 100 USDT @ 5% = 1000 USDT @ 5%)
|
||||
- Now: `PnL (USDT) = Position Value × Price Change % × Leverage`
|
||||
- Impact: Win rate, profit factor, and Sharpe ratio now accurate
|
||||
|
||||
#### Position Tracking - Missing Critical Data
|
||||
- **Fixed**: Open position records now store quantity and leverage
|
||||
- Previously only stored price and time
|
||||
- Essential for accurate PnL calculations
|
||||
|
||||
#### Position Key Logic - Long/Short Conflict
|
||||
- **Fixed**: Changed from `symbol` to `symbol_side` format
|
||||
- Now properly distinguishes between long and short positions
|
||||
- Example: `BTCUSDT_long` vs `BTCUSDT_short`
|
||||
|
||||
#### Sharpe Ratio Calculation - Code Optimization
|
||||
- **Changed**: Replaced custom Newton's method with `math.Sqrt`
|
||||
- More reliable, maintainable, and efficient
|
||||
|
||||
### Why This Matters
|
||||
- Historical trade statistics now show real USDT profit/loss
|
||||
- Performance comparison between different leverage trades is accurate
|
||||
- AI self-learning mechanism receives correct feedback
|
||||
- Multi-position tracking (long + short simultaneously) works correctly
|
||||
|
||||
---
|
||||
|
||||
## [2.0.2] - 2025-10-29
|
||||
|
||||
### Fixed - Aster Exchange Precision Error
|
||||
|
||||
- Fixed Aster exchange precision error (code -1111)
|
||||
- Improved price and quantity formatting to match exchange requirements
|
||||
- Added detailed precision processing logs for debugging
|
||||
- Enhanced all order functions with proper precision handling
|
||||
|
||||
#### Technical Details
|
||||
- Added `formatFloatWithPrecision` function
|
||||
- Price and quantity formatted according to exchange specifications
|
||||
- Trailing zeros removed to optimize API requests
|
||||
|
||||
---
|
||||
|
||||
## [2.0.1] - 2025-10-29
|
||||
|
||||
### Fixed - ComparisonChart Data Processing
|
||||
|
||||
- Fixed ComparisonChart data processing logic
|
||||
- Switched from cycle_number to timestamp grouping
|
||||
- Resolved chart freezing issue when backend restarts
|
||||
- Improved chart data display (shows all historical data chronologically)
|
||||
- Enhanced debugging logs
|
||||
|
||||
---
|
||||
|
||||
## [2.0.0] - 2025-10-28
|
||||
|
||||
### Added - Major Updates
|
||||
|
||||
- AI self-learning mechanism (historical feedback, performance analysis)
|
||||
- Multi-trader competition mode (Qwen vs DeepSeek)
|
||||
- Binance-style UI (complete interface imitation)
|
||||
- Performance comparison charts (real-time ROI comparison)
|
||||
- Risk control optimization (per-coin position limit adjustment)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed hardcoded initial balance issue
|
||||
- Fixed multi-trader data sync issue
|
||||
- Optimized chart data alignment (using cycle_number)
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2025-10-27
|
||||
|
||||
### Added - Initial Release
|
||||
|
||||
- Basic AI trading functionality
|
||||
- Decision logging system
|
||||
- Simple Web interface
|
||||
- Support for Binance Futures
|
||||
- DeepSeek and Qwen AI model integration
|
||||
|
||||
---
|
||||
|
||||
## How to Use This Changelog
|
||||
|
||||
### For Users
|
||||
- Check the [Unreleased] section for upcoming features
|
||||
- Review version sections to understand what changed
|
||||
- Follow migration guides for breaking changes
|
||||
|
||||
### For Contributors
|
||||
When making changes, add them to the [Unreleased] section under appropriate categories:
|
||||
- **Added** - New features
|
||||
- **Changed** - Changes to existing functionality
|
||||
- **Deprecated** - Features that will be removed
|
||||
- **Removed** - Features that were removed
|
||||
- **Fixed** - Bug fixes
|
||||
- **Security** - Security fixes
|
||||
|
||||
When releasing a new version, move [Unreleased] items to a new version section with date.
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
- [Documentation](docs/README.md)
|
||||
- [Contributing Guidelines](CONTRIBUTING.md)
|
||||
- [Security Policy](SECURITY.md)
|
||||
- [GitHub Repository](https://github.com/tinkle-community/nofx)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-11-01
|
||||
203
CHANGELOG.zh-CN.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# 更新日志
|
||||
|
||||
NOFX 项目的所有重要更改都将记录在此文件中。
|
||||
|
||||
本文件格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
**语言:** [English](CHANGELOG.md) | [中文](CHANGELOG.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## [未发布]
|
||||
|
||||
### 新增
|
||||
- 多语言文档系统(英文/中文/俄语/乌克兰语)
|
||||
- 完整的快速开始指南(Docker、PM2、自定义 API)
|
||||
- 架构文档,包含系统设计细节
|
||||
- 用户指南,包含 FAQ 和故障排除
|
||||
- 社区文档,包含悬赏计划
|
||||
|
||||
### 变更
|
||||
- 重组文档结构为逻辑分类
|
||||
- 更新所有 README 文件,添加适当的导航链接
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0] - 2025-10-30
|
||||
|
||||
### 新增 - 重大架构变革 🚀
|
||||
|
||||
**系统完全重新设计 - 基于 Web 的配置平台**
|
||||
|
||||
这是一个**重大破坏性更新**,将 NOFX 从基于静态配置的系统完全转变为现代化的 Web 交易平台。
|
||||
|
||||
#### 数据库驱动架构
|
||||
- SQLite 集成,取代静态 JSON 配置
|
||||
- 持久化存储,自动时间戳
|
||||
- 外键关系和触发器确保数据一致性
|
||||
- 为 AI 模型、交易所、交易员和系统配置分离表结构
|
||||
|
||||
#### 基于 Web 的配置界面
|
||||
- 完整的 Web 配置管理(无需编辑 JSON)
|
||||
- 通过 Web 界面设置 AI 模型(DeepSeek/Qwen API 密钥)
|
||||
- 交易所管理(Binance/Hyperliquid 凭证)
|
||||
- 动态创建交易员(结合任意 AI 模型和交易所)
|
||||
- 实时控制(无需重启即可启动/停止交易员)
|
||||
|
||||
#### 灵活架构
|
||||
- 关注点分离(AI 模型和交易所独立)
|
||||
- 混合搭配能力(无限组合)
|
||||
- 可扩展设计(支持无限交易员)
|
||||
- 清洁起点(无默认交易员)
|
||||
|
||||
#### 增强的 API 层
|
||||
- RESTful 设计,完整的 CRUD 操作
|
||||
- 新端点:
|
||||
- `GET/PUT /api/models` - AI 模型配置
|
||||
- `GET/PUT /api/exchanges` - 交易所配置
|
||||
- `POST/DELETE /api/traders` - 交易员管理
|
||||
- `POST /api/traders/:id/start|stop` - 交易员控制
|
||||
- 更新所有 API 端点文档
|
||||
|
||||
#### 现代化代码库
|
||||
- 类型安全,适当分离配置类型
|
||||
- 数据库抽象,使用预处理语句
|
||||
- 全面的错误处理和验证
|
||||
- 更好的代码组织(数据库、API、业务逻辑)
|
||||
|
||||
### 变更
|
||||
- **破坏性变更**:不再使用旧的 `config.json` 文件
|
||||
- 必须通过 Web 界面进行配置
|
||||
- 设置更简单,用户体验更好
|
||||
- 配置更改无需重启服务器
|
||||
|
||||
### 为什么重要
|
||||
- 🎯 **用户体验**:配置和管理更容易
|
||||
- 🔧 **灵活性**:创建 AI 模型和交易所的任意组合
|
||||
- 📊 **可扩展性**:支持复杂的多交易员设置
|
||||
- 🔒 **可靠性**:数据库确保数据持久性和一致性
|
||||
- 🚀 **面向未来**:为高级功能奠定基础
|
||||
|
||||
---
|
||||
|
||||
## [2.0.2] - 2025-10-29
|
||||
|
||||
### 修复 - 关键错误修复:交易历史和性能分析
|
||||
|
||||
#### 盈亏计算 - 重大错误修复
|
||||
- **修复**:盈亏现在计算为实际 USDT 金额,而不是仅百分比
|
||||
- 之前忽略了仓位大小和杠杆(例如,100 USDT @ 5% = 1000 USDT @ 5%)
|
||||
- 现在:`盈亏 (USDT) = 仓位价值 × 价格变化 % × 杠杆`
|
||||
- 影响:胜率、盈利因子和夏普比率现在准确
|
||||
|
||||
#### 仓位跟踪 - 缺失关键数据
|
||||
- **修复**:持仓记录现在存储数量和杠杆
|
||||
- 之前只存储价格和时间
|
||||
- 这对准确的盈亏计算至关重要
|
||||
|
||||
#### 仓位键逻辑 - 多空冲突
|
||||
- **修复**:从 `symbol` 改为 `symbol_side` 格式
|
||||
- 现在正确区分多头和空头仓位
|
||||
- 示例:`BTCUSDT_long` vs `BTCUSDT_short`
|
||||
|
||||
#### 夏普比率计算 - 代码优化
|
||||
- **变更**:用 `math.Sqrt` 替换自定义牛顿法
|
||||
- 更可靠、可维护和高效
|
||||
|
||||
### 为什么重要
|
||||
- 历史交易统计现在显示真实的 USDT 盈亏
|
||||
- 不同杠杆交易之间的性能比较准确
|
||||
- AI 自学习机制接收正确的反馈
|
||||
- 多仓位跟踪(同时多空)正常工作
|
||||
|
||||
---
|
||||
|
||||
## [2.0.2] - 2025-10-29
|
||||
|
||||
### 修复 - Aster 交易所精度错误
|
||||
|
||||
- 修复 Aster 交易所精度错误(代码 -1111)
|
||||
- 改进价格和数量格式化以匹配交易所要求
|
||||
- 添加详细的精度处理日志用于调试
|
||||
- 增强所有订单函数的精度处理
|
||||
|
||||
#### 技术细节
|
||||
- 添加 `formatFloatWithPrecision` 函数
|
||||
- 根据交易所规范格式化价格和数量
|
||||
- 删除尾随零以优化 API 请求
|
||||
|
||||
---
|
||||
|
||||
## [2.0.1] - 2025-10-29
|
||||
|
||||
### 修复 - ComparisonChart 数据处理
|
||||
|
||||
- 修复 ComparisonChart 数据处理逻辑
|
||||
- 从 cycle_number 切换到时间戳分组
|
||||
- 解决后端重启时图表冻结问题
|
||||
- 改进图表数据显示(按时间顺序显示所有历史数据)
|
||||
- 增强调试日志
|
||||
|
||||
---
|
||||
|
||||
## [2.0.0] - 2025-10-28
|
||||
|
||||
### 新增 - 重大更新
|
||||
|
||||
- AI 自学习机制(历史反馈、性能分析)
|
||||
- 多交易员竞赛模式(Qwen vs DeepSeek)
|
||||
- 币安风格 UI(完整界面仿制)
|
||||
- 性能比较图表(实时 ROI 比较)
|
||||
- 风险控制优化(每币种仓位限制调整)
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复硬编码初始余额问题
|
||||
- 修复多交易员数据同步问题
|
||||
- 优化图表数据对齐(使用 cycle_number)
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2025-10-27
|
||||
|
||||
### 新增 - 初始版本
|
||||
|
||||
- 基础 AI 交易功能
|
||||
- 决策日志系统
|
||||
- 简单的 Web 界面
|
||||
- 支持币安合约
|
||||
- DeepSeek 和 Qwen AI 模型集成
|
||||
|
||||
---
|
||||
|
||||
## 如何使用本更新日志
|
||||
|
||||
### 用户
|
||||
- 查看 [未发布] 部分了解即将推出的功能
|
||||
- 查看版本部分了解变更内容
|
||||
- 遵循破坏性变更的迁移指南
|
||||
|
||||
### 贡献者
|
||||
进行更改时,将它们添加到 [未发布] 部分的相应类别下:
|
||||
- **新增** - 新功能
|
||||
- **变更** - 现有功能的变更
|
||||
- **弃用** - 即将删除的功能
|
||||
- **移除** - 已删除的功能
|
||||
- **修复** - 错误修复
|
||||
- **安全** - 安全修复
|
||||
|
||||
发布新版本时,将 [未发布] 项目移动到带日期的新版本部分。
|
||||
|
||||
---
|
||||
|
||||
## 链接
|
||||
|
||||
- [文档](docs/README.md)
|
||||
- [贡献指南](CONTRIBUTING.md)
|
||||
- [安全策略](SECURITY.md)
|
||||
- [GitHub 仓库](https://github.com/tinkle-community/nofx)
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2025-11-01
|
||||
237
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Contributor Covenant Code of Conduct / 贡献者公约行为准则
|
||||
|
||||
**Languages:** [English](#english) | [中文](#中文)
|
||||
|
||||
---
|
||||
|
||||
# English
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at:
|
||||
|
||||
|
||||
You can also report via:
|
||||
- **Telegram:** Direct message to [@Web3Tinkle](https://t.me/Web3Tinkle)
|
||||
- **Twitter:** DM to [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
|
||||
---
|
||||
|
||||
# 中文
|
||||
|
||||
## 我们的承诺
|
||||
|
||||
作为成员、贡献者和领导者,我们承诺使社区中的每个人都不受骚扰,无论其年龄、体型、明显或不明显的残疾、种族、性别特征、性别认同和表达、经验水平、教育程度、社会经济地位、国籍、个人外貌、种族、种姓、肤色、宗教或性认同和取向如何。
|
||||
|
||||
我们承诺以有助于开放、友好、多元、包容和健康社区的方式行事和互动。
|
||||
|
||||
## 我们的标准
|
||||
|
||||
有助于为我们的社区创造积极环境的行为示例包括:
|
||||
|
||||
* 对他人表现出同理心和善意
|
||||
* 尊重不同的意见、观点和经验
|
||||
* 给予并优雅地接受建设性反馈
|
||||
* 接受责任并向受我们错误影响的人道歉,并从经验中学习
|
||||
* 关注不仅对我们个人最好,而且对整个社区最好的事情
|
||||
|
||||
不可接受的行为示例包括:
|
||||
|
||||
* 使用性化的语言或图像,以及任何形式的性关注或性挑逗
|
||||
* 挑衅、侮辱性或贬损性评论,以及人身或政治攻击
|
||||
* 公开或私下骚扰
|
||||
* 未经他人明确许可,发布他人的私人信息,如物理地址或电子邮件地址
|
||||
* 在专业环境中可能被合理认为不适当的其他行为
|
||||
|
||||
## 执行责任
|
||||
|
||||
社区领导者负责阐明和执行我们可接受行为的标准,并将对他们认为不适当、威胁性、冒犯性或有害的任何行为采取适当和公平的纠正措施。
|
||||
|
||||
社区领导者有权利和责任删除、编辑或拒绝不符合本行为准则的评论、提交、代码、wiki 编辑、问题和其他贡献,并在适当时传达审核决定的原因。
|
||||
|
||||
## 范围
|
||||
|
||||
本行为准则适用于所有社区空间,也适用于个人在公共空间正式代表社区的情况。代表我们社区的示例包括使用官方电子邮件地址、通过官方社交媒体账户发布信息,或在线上或线下活动中担任指定代表。
|
||||
|
||||
## 执行
|
||||
|
||||
可以向负责执行的社区领导者报告滥用、骚扰或其他不可接受行为的实例:
|
||||
|
||||
|
||||
您也可以通过以下方式报告:
|
||||
- **Telegram:** 直接消息 [@Web3Tinkle](https://t.me/Web3Tinkle)
|
||||
- **Twitter:** 私信 [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
所有投诉都将得到迅速和公正的审查和调查。
|
||||
|
||||
所有社区领导者都有义务尊重任何事件报告者的隐私和安全。
|
||||
|
||||
## 执行指南
|
||||
|
||||
社区领导者将遵循这些社区影响指南来确定他们认为违反本行为准则的任何行动的后果:
|
||||
|
||||
### 1. 纠正
|
||||
|
||||
**社区影响**:使用不适当的语言或其他被认为在社区中不专业或不受欢迎的行为。
|
||||
|
||||
**后果**:社区领导者的私下书面警告,说明违规的性质和解释为什么行为不适当。可能要求公开道歉。
|
||||
|
||||
### 2. 警告
|
||||
|
||||
**社区影响**:通过单一事件或一系列行动违规。
|
||||
|
||||
**后果**:警告并说明持续行为的后果。在指定时间内不与相关人员互动,包括不主动与执行行为准则的人互动。这包括避免在社区空间以及外部渠道(如社交媒体)的互动。违反这些条款可能导致临时或永久禁令。
|
||||
|
||||
### 3. 临时禁令
|
||||
|
||||
**社区影响**:严重违反社区标准,包括持续的不当行为。
|
||||
|
||||
**后果**:在指定时间内临时禁止与社区进行任何形式的互动或公开交流。在此期间,不允许与相关人员进行公开或私下互动,包括不主动与执行行为准则的人互动。违反这些条款可能导致永久禁令。
|
||||
|
||||
### 4. 永久禁令
|
||||
|
||||
**社区影响**:表现出违反社区标准的模式,包括持续的不当行为、对个人的骚扰,或对个人类别的攻击或贬低。
|
||||
|
||||
**后果**:永久禁止在社区内进行任何形式的公开互动。
|
||||
|
||||
## 归属
|
||||
|
||||
本行为准则改编自 [贡献者公约][homepage] 2.1 版,可在
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1] 获取。
|
||||
|
||||
社区影响指南受到 [Mozilla 行为准则执行阶梯][Mozilla CoC] 的启发。
|
||||
|
||||
有关本行为准则的常见问题解答,请参阅 [https://www.contributor-covenant.org/faq][FAQ]。翻译版本可在 [https://www.contributor-covenant.org/translations][translations] 获取。
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
481
CONTRIBUTING.md
Normal file
@@ -0,0 +1,481 @@
|
||||
# 🤝 Contributing to NOFX
|
||||
|
||||
**Language:** [English](CONTRIBUTING.md) | [中文](docs/i18n/zh-CN/CONTRIBUTING.md)
|
||||
|
||||
Thank you for your interest in contributing to NOFX! This document provides guidelines and workflows for contributing to the project.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Table of Contents
|
||||
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [How Can I Contribute?](#how-can-i-contribute)
|
||||
- [Development Workflow](#development-workflow)
|
||||
- [PR Submission Guidelines](#pr-submission-guidelines)
|
||||
- [Coding Standards](#coding-standards)
|
||||
- [Commit Message Guidelines](#commit-message-guidelines)
|
||||
- [Review Process](#review-process)
|
||||
- [Bounty Program](#bounty-program)
|
||||
|
||||
---
|
||||
|
||||
## 📜 Code of Conduct
|
||||
|
||||
This project adheres to the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 How Can I Contribute?
|
||||
|
||||
### 1. Report Bugs 🐛
|
||||
|
||||
- Use the [Bug Report Template](.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
- Check if the bug has already been reported
|
||||
- Include detailed reproduction steps
|
||||
- Provide environment information (OS, Go version, etc.)
|
||||
|
||||
### 2. Suggest Features ✨
|
||||
|
||||
- Use the [Feature Request Template](.github/ISSUE_TEMPLATE/feature_request.md)
|
||||
- Explain the use case and benefits
|
||||
- Check if it aligns with the [project roadmap](docs/roadmap/README.md)
|
||||
|
||||
### 3. Submit Pull Requests 🔧
|
||||
|
||||
Before submitting a PR, please check the following:
|
||||
|
||||
#### ✅ **Accepted Contributions**
|
||||
|
||||
**High Priority** (aligned with roadmap):
|
||||
- 🔒 Security enhancements (encryption, authentication, RBAC)
|
||||
- 🧠 AI model integrations (GPT-4, Claude, Gemini Pro)
|
||||
- 🔗 Exchange integrations (OKX, Bybit, Lighter, EdgeX)
|
||||
- 📊 Trading data APIs (AI500, OI analysis, NetFlow)
|
||||
- 🎨 UI/UX improvements (mobile responsiveness, charts)
|
||||
- ⚡ Performance optimizations
|
||||
- 🐛 Bug fixes
|
||||
- 📝 Documentation improvements
|
||||
|
||||
**Medium Priority:**
|
||||
- ✅ Test coverage improvements
|
||||
- 🌐 Internationalization (new language support)
|
||||
- 🔧 Build/deployment tooling
|
||||
- 📈 Monitoring and logging enhancements
|
||||
|
||||
#### ❌ **Not Accepted** (without prior discussion)
|
||||
|
||||
- Major architectural changes without RFC (Request for Comments)
|
||||
- Features not aligned with project roadmap
|
||||
- Breaking changes without migration path
|
||||
- Code that introduces new dependencies without justification
|
||||
- Experimental features without opt-in flag
|
||||
|
||||
**⚠️ Important:** For major features, please open an issue for discussion **before** starting work.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Development Workflow
|
||||
|
||||
### 1. Fork and Clone
|
||||
|
||||
```bash
|
||||
# Fork the repository on GitHub
|
||||
# Then clone your fork
|
||||
git clone https://github.com/YOUR_USERNAME/nofx.git
|
||||
cd nofx
|
||||
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/tinkle-community/nofx.git
|
||||
```
|
||||
|
||||
### 2. Create a Feature Branch
|
||||
|
||||
```bash
|
||||
# Update your local dev branch
|
||||
git checkout dev
|
||||
git pull upstream dev
|
||||
|
||||
# Create a new branch
|
||||
git checkout -b feature/your-feature-name
|
||||
# or
|
||||
git checkout -b fix/your-bug-fix
|
||||
```
|
||||
|
||||
**Branch Naming Convention:**
|
||||
- `feature/` - New features
|
||||
- `fix/` - Bug fixes
|
||||
- `docs/` - Documentation updates
|
||||
- `refactor/` - Code refactoring
|
||||
- `perf/` - Performance improvements
|
||||
- `test/` - Test updates
|
||||
- `chore/` - Build/config changes
|
||||
|
||||
### 3. Set Up Development Environment
|
||||
|
||||
```bash
|
||||
# Install Go dependencies
|
||||
go mod download
|
||||
|
||||
# Install frontend dependencies
|
||||
cd web
|
||||
npm install
|
||||
cd ..
|
||||
|
||||
# Install TA-Lib (required)
|
||||
# macOS:
|
||||
brew install ta-lib
|
||||
|
||||
# Ubuntu/Debian:
|
||||
sudo apt-get install libta-lib0-dev
|
||||
```
|
||||
|
||||
### 4. Make Your Changes
|
||||
|
||||
- Follow the [coding standards](#coding-standards)
|
||||
- Write tests for new features
|
||||
- Update documentation as needed
|
||||
- Keep commits focused and atomic
|
||||
|
||||
### 5. Test Your Changes
|
||||
|
||||
```bash
|
||||
# Run backend tests
|
||||
go test ./...
|
||||
|
||||
# Build backend
|
||||
go build -o nofx
|
||||
|
||||
# Run frontend in dev mode
|
||||
cd web
|
||||
npm run dev
|
||||
|
||||
# Build frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 6. Commit Your Changes
|
||||
|
||||
Follow the [commit message guidelines](#commit-message-guidelines):
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: add support for OKX exchange integration"
|
||||
```
|
||||
|
||||
### 7. Push and Create PR
|
||||
|
||||
```bash
|
||||
# Push to your fork
|
||||
git push origin feature/your-feature-name
|
||||
|
||||
# Go to GitHub and create a Pull Request
|
||||
# Use the PR template and fill in all sections
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 PR Submission Guidelines
|
||||
|
||||
### Before Submitting
|
||||
|
||||
- [ ] Code compiles successfully (`go build` and `npm run build`)
|
||||
- [ ] All tests pass (`go test ./...`)
|
||||
- [ ] No linting errors (`go fmt`, `go vet`)
|
||||
- [ ] Documentation is updated
|
||||
- [ ] Commits follow conventional commits format
|
||||
- [ ] Branch is rebased on latest `dev`
|
||||
|
||||
### PR Title Format
|
||||
|
||||
Use [Conventional Commits](https://www.conventionalcommits.org/) format:
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
Examples:
|
||||
feat(exchange): add OKX exchange integration
|
||||
fix(trader): resolve position tracking bug
|
||||
docs(readme): update installation instructions
|
||||
perf(ai): optimize prompt generation
|
||||
refactor(core): extract common exchange interface
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat` - New feature
|
||||
- `fix` - Bug fix
|
||||
- `docs` - Documentation
|
||||
- `style` - Code style (formatting, no logic change)
|
||||
- `refactor` - Code refactoring
|
||||
- `perf` - Performance improvement
|
||||
- `test` - Test updates
|
||||
- `chore` - Build/config changes
|
||||
- `ci` - CI/CD changes
|
||||
- `security` - Security improvements
|
||||
|
||||
### PR Description
|
||||
|
||||
Use the [PR template](.github/PULL_REQUEST_TEMPLATE.md) and ensure:
|
||||
|
||||
1. **Clear description** of what and why
|
||||
2. **Type of change** is marked
|
||||
3. **Related issues** are linked
|
||||
4. **Testing steps** are documented
|
||||
5. **Screenshots** for UI changes
|
||||
6. **All checkboxes** are completed
|
||||
|
||||
### PR Size
|
||||
|
||||
Keep PRs focused and reasonably sized:
|
||||
|
||||
- ✅ **Small PR** (< 300 lines): Ideal, fast review
|
||||
- ⚠️ **Medium PR** (300-1000 lines): Acceptable, may take longer
|
||||
- ❌ **Large PR** (> 1000 lines): Please break into smaller PRs
|
||||
|
||||
---
|
||||
|
||||
## 💻 Coding Standards
|
||||
|
||||
### Go Code
|
||||
|
||||
```go
|
||||
// ✅ Good: Clear naming, proper error handling
|
||||
func ConnectToExchange(apiKey, secret string) (*Exchange, error) {
|
||||
if apiKey == "" || secret == "" {
|
||||
return nil, fmt.Errorf("API credentials are required")
|
||||
}
|
||||
|
||||
client, err := createClient(apiKey, secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create client: %w", err)
|
||||
}
|
||||
|
||||
return &Exchange{client: client}, nil
|
||||
}
|
||||
|
||||
// ❌ Bad: Poor naming, no error handling
|
||||
func ce(a, s string) *Exchange {
|
||||
c := createClient(a, s)
|
||||
return &Exchange{client: c}
|
||||
}
|
||||
```
|
||||
|
||||
**Best Practices:**
|
||||
- Use meaningful variable names
|
||||
- Handle all errors explicitly
|
||||
- Add comments for complex logic
|
||||
- Follow Go idioms and conventions
|
||||
- Run `go fmt` before committing
|
||||
- Use `go vet` and `golangci-lint`
|
||||
|
||||
### TypeScript/React Code
|
||||
|
||||
```typescript
|
||||
// ✅ Good: Type-safe, clear naming
|
||||
interface TraderConfig {
|
||||
id: string;
|
||||
exchange: 'binance' | 'hyperliquid' | 'aster';
|
||||
aiModel: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const TraderCard: React.FC<{ trader: TraderConfig }> = ({ trader }) => {
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
const handleStart = async () => {
|
||||
try {
|
||||
await startTrader(trader.id);
|
||||
setIsRunning(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to start trader:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return <div>...</div>;
|
||||
};
|
||||
|
||||
// ❌ Bad: No types, unclear naming
|
||||
const TC = (props) => {
|
||||
const [r, setR] = useState(false);
|
||||
const h = () => { startTrader(props.t.id); setR(true); };
|
||||
return <div>...</div>;
|
||||
};
|
||||
```
|
||||
|
||||
**Best Practices:**
|
||||
- Use TypeScript strict mode
|
||||
- Define interfaces for all data structures
|
||||
- Avoid `any` type
|
||||
- Use functional components with hooks
|
||||
- Follow React best practices
|
||||
- Run `npm run lint` before committing
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
NOFX/
|
||||
├── cmd/ # Main applications
|
||||
├── internal/ # Private code
|
||||
│ ├── exchange/ # Exchange adapters
|
||||
│ ├── trader/ # Trading logic
|
||||
│ ├── ai/ # AI integrations
|
||||
│ └── api/ # API handlers
|
||||
├── pkg/ # Public libraries
|
||||
├── web/ # Frontend
|
||||
│ ├── src/
|
||||
│ │ ├── components/
|
||||
│ │ ├── pages/
|
||||
│ │ ├── hooks/
|
||||
│ │ └── utils/
|
||||
│ └── public/
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Commit Message Guidelines
|
||||
|
||||
### Format
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
feat(exchange): add OKX futures API integration
|
||||
|
||||
- Implement order placement and cancellation
|
||||
- Add balance and position retrieval
|
||||
- Support leverage configuration
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
```
|
||||
fix(trader): prevent duplicate position opening
|
||||
|
||||
The trader was opening multiple positions in the same direction
|
||||
for the same symbol. Added check to prevent this behavior.
|
||||
|
||||
Fixes #456
|
||||
```
|
||||
|
||||
```
|
||||
docs: update Docker deployment guide
|
||||
|
||||
- Add troubleshooting section
|
||||
- Update environment variables
|
||||
- Add examples for common scenarios
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- Use present tense ("add" not "added")
|
||||
- Use imperative mood ("move" not "moves")
|
||||
- First line ≤ 72 characters
|
||||
- Reference issues and PRs
|
||||
- Explain "what" and "why", not "how"
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Review Process
|
||||
|
||||
### Timeline
|
||||
|
||||
- **Initial review:** Within 2-3 business days
|
||||
- **Follow-up reviews:** Within 1-2 business days
|
||||
- **Bounty PRs:** Priority review within 1 business day
|
||||
|
||||
### Review Criteria
|
||||
|
||||
Reviewers will check:
|
||||
|
||||
1. **Functionality**
|
||||
- Does it work as intended?
|
||||
- Are edge cases handled?
|
||||
- No regression in existing features?
|
||||
|
||||
2. **Code Quality**
|
||||
- Follows coding standards?
|
||||
- Well-structured and readable?
|
||||
- Proper error handling?
|
||||
|
||||
3. **Testing**
|
||||
- Adequate test coverage?
|
||||
- Tests pass in CI?
|
||||
- Manual testing documented?
|
||||
|
||||
4. **Documentation**
|
||||
- Code comments where needed?
|
||||
- README/docs updated?
|
||||
- API changes documented?
|
||||
|
||||
5. **Security**
|
||||
- No hardcoded secrets?
|
||||
- Input validation?
|
||||
- No known vulnerabilities?
|
||||
|
||||
### Response to Feedback
|
||||
|
||||
- Address all review comments
|
||||
- Ask questions if unclear
|
||||
- Mark conversations as resolved
|
||||
- Re-request review after changes
|
||||
|
||||
### Approval and Merge
|
||||
|
||||
- Requires **1 approval** from maintainers
|
||||
- All CI checks must pass
|
||||
- No unresolved conversations
|
||||
- Maintainers will merge (squash merge for small PRs, merge commit for features)
|
||||
|
||||
---
|
||||
|
||||
## 💰 Bounty Program
|
||||
|
||||
### How It Works
|
||||
|
||||
1. Check [open bounty issues](https://github.com/tinkle-community/nofx/labels/bounty)
|
||||
2. Comment to claim (first come, first served)
|
||||
3. Complete work within deadline
|
||||
4. Submit PR with bounty claim section filled
|
||||
5. Get paid upon merge
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Read [Bounty Guide](docs/community/bounty-guide.md)
|
||||
- Meet all acceptance criteria
|
||||
- Include demo video/screenshots
|
||||
- Follow all contribution guidelines
|
||||
- Payment details discussed privately
|
||||
|
||||
---
|
||||
|
||||
## ❓ Questions?
|
||||
|
||||
- **General questions:** Join our [Telegram Community](https://t.me/nofx_dev_community)
|
||||
- **Technical questions:** Open a [Discussion](https://github.com/tinkle-community/nofx/discussions)
|
||||
- **Security issues:** See [Security Policy](SECURITY.md)
|
||||
- **Bug reports:** Use [Bug Report Template](.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
- [Project Roadmap](docs/roadmap/README.md)
|
||||
- [Architecture Documentation](docs/architecture/README.md)
|
||||
- [API Documentation](docs/api/README.md)
|
||||
- [Deployment Guide](docs/getting-started/docker-deploy.en.md)
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Thank You!
|
||||
|
||||
Your contributions make NOFX better for everyone. We appreciate your time and effort!
|
||||
|
||||
**Happy coding! 🚀**
|
||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Tinkle Community (NOFX Project)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
630
README.md
@@ -6,10 +6,36 @@
|
||||
[](LICENSE)
|
||||
[](https://amber.ac)
|
||||
|
||||
**Languages:** [English](README.md) | [中文](README.zh-CN.md) | [Українська](README.uk.md) | [Русский](README.ru.md)
|
||||
**Languages:** [English](README.md) | [中文](docs/i18n/zh-CN/README.md) | [Українська](docs/i18n/uk/README.md) | [Русский](docs/i18n/ru/README.md)
|
||||
|
||||
**Official Twitter:** [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
**📚 Documentation:** [Docs Home](docs/README.md) | [Getting Started](docs/getting-started/README.md) | [Changelog](CHANGELOG.md) | [Contributing](CONTRIBUTING.md) | [Security](SECURITY.md)
|
||||
|
||||
---
|
||||
|
||||
## 📑 Table of Contents
|
||||
|
||||
- [🚀 Universal AI Trading Operating System](#-universal-ai-trading-operating-system)
|
||||
- [👥 Developer Community](#-developer-community)
|
||||
- [🆕 What's New](#-whats-new-latest-update)
|
||||
- [📸 Screenshots](#-screenshots)
|
||||
- [✨ Current Implementation](#-current-implementation---crypto-markets)
|
||||
- [🔮 Roadmap](#-roadmap---universal-market-expansion)
|
||||
- [🏗️ Technical Architecture](#️-technical-architecture)
|
||||
- [💰 Register Binance Account](#-register-binance-account-save-on-fees)
|
||||
- [🚀 Quick Start](#-quick-start)
|
||||
- [📖 AI Decision Flow](#-ai-decision-flow)
|
||||
- [🧠 AI Self-Learning](#-ai-self-learning-example)
|
||||
- [📊 Web Interface Features](#-web-interface-features)
|
||||
- [🎛️ API Endpoints](#️-api-endpoints)
|
||||
- [⚠️ Important Risk Warnings](#️-important-risk-warnings)
|
||||
- [🛠️ Common Issues](#️-common-issues)
|
||||
- [📈 Performance Tips](#-performance-optimization-tips)
|
||||
- [🔄 Changelog](#-changelog)
|
||||
- [📄 License](#-license)
|
||||
- [🤝 Contributing](#-contributing)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Universal AI Trading Operating System
|
||||
@@ -66,19 +92,19 @@ A high-performance decentralized perpetual futures exchange!
|
||||
- ✅ Support for both mainnet and testnet
|
||||
- ✅ No API keys needed - just your Ethereum private key
|
||||
|
||||
**Why Hyperliquid?**
|
||||
- 🔥 Lower fees than centralized exchanges
|
||||
- 🔒 Non-custodial - you control your funds
|
||||
- ⚡ Fast execution with on-chain settlement
|
||||
- 🌍 No KYC required
|
||||
**New Workflow:**
|
||||
1. **Configure AI Models**: Add your DeepSeek/Qwen API keys through the web interface
|
||||
2. **Configure Exchanges**: Set up Binance/Hyperliquid API credentials
|
||||
3. **Create Traders**: Combine any AI model with any exchange to create custom traders
|
||||
4. **Monitor & Control**: Start/stop traders and monitor performance in real-time
|
||||
|
||||
**Quick Start:**
|
||||
1. Get your MetaMask private key (remove `0x` prefix)
|
||||
2. Set `"exchange": "hyperliquid"` in config.json
|
||||
3. Add `"hyperliquid_private_key": "your_key"`
|
||||
4. Start trading!
|
||||
**Why This Update?**
|
||||
- 🎯 **User-Friendly**: No more editing JSON files or server restarts
|
||||
- 🔧 **Flexible**: Mix and match different AI models with different exchanges
|
||||
- 📊 **Scalable**: Create unlimited trader combinations
|
||||
- 🔒 **Secure**: Database storage with proper data management
|
||||
|
||||
See [Configuration Guide](#-alternative-using-hyperliquid-exchange) for details.
|
||||
See [Quick Start](#-quick-start) for the new setup process!
|
||||
|
||||
#### **Aster DEX Exchange** (NEW! v2.0.2)
|
||||
|
||||
@@ -169,78 +195,50 @@ NOFX is currently **fully operational in cryptocurrency markets** with the follo
|
||||
|
||||
## 🔮 Roadmap - Universal Market Expansion
|
||||
|
||||
Our proven crypto infrastructure is being extended to:
|
||||
NOFX is on a mission to become the **Universal AI Trading Operating System** for all financial markets.
|
||||
|
||||
- **📈 Stock Markets**: US equities, A-shares, Hong Kong stocks
|
||||
- **📊 Futures Markets**: Commodity futures, index futures
|
||||
- **🎯 Options Trading**: Equity options, crypto options
|
||||
- **💱 Forex Markets**: Major currency pairs, cross rates
|
||||
**Vision:** Same architecture. Same agent framework. All markets.
|
||||
|
||||
**Same architecture. Same agent framework. All markets.**
|
||||
**Expansion Markets:**
|
||||
- 📈 **Stock Markets**: US equities, A-shares, Hong Kong stocks
|
||||
- 📊 **Futures Markets**: Commodity futures, index futures
|
||||
- 🎯 **Options Trading**: Equity options, crypto options
|
||||
- 💱 **Forex Markets**: Major currency pairs, cross rates
|
||||
|
||||
**Upcoming Features:**
|
||||
- Enhanced AI capabilities (GPT-4, Claude 3, Gemini Pro, flexible prompt templates)
|
||||
- New exchange integrations (OKX, Bybit, Lighter, EdgeX + CEX/Perp-DEX)
|
||||
- Project structure refactoring (high cohesion, low coupling, SOLID principles)
|
||||
- Security enhancements (AES-256 encryption for API keys, RBAC, 2FA improvements)
|
||||
- User experience improvements (mobile-responsive, TradingView charts, alert system)
|
||||
|
||||
📖 **For detailed roadmap and timeline, see:**
|
||||
- **English:** [Roadmap Documentation](docs/roadmap/README.md)
|
||||
- **中文:** [路线图文档](docs/roadmap/README.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Technical Architecture
|
||||
|
||||
```
|
||||
nofx/
|
||||
├── main.go # Program entry (multi-trader manager)
|
||||
├── config.json # Configuration file (API keys, multi-trader config)
|
||||
│
|
||||
├── api/ # HTTP API service
|
||||
│ └── server.go # Gin framework, RESTful API
|
||||
│
|
||||
├── trader/ # Trading core
|
||||
│ ├── auto_trader.go # Auto trading main controller (single trader)
|
||||
│ └── binance_futures.go # Binance futures API wrapper
|
||||
│
|
||||
├── manager/ # Multi-trader management
|
||||
│ └── trader_manager.go # Manages multiple trader instances
|
||||
│
|
||||
├── mcp/ # Model Context Protocol - AI communication
|
||||
│ └── client.go # AI API client (DeepSeek/Qwen integration)
|
||||
│
|
||||
├── decision/ # AI decision engine
|
||||
│ └── engine.go # Decision logic with historical feedback
|
||||
│
|
||||
├── market/ # Market data fetching
|
||||
│ └── data.go # Market data & technical indicators (K-line, RSI, MACD)
|
||||
│
|
||||
├── pool/ # Coin pool management
|
||||
│ └── coin_pool.go # AI500 + OI Top merged pool
|
||||
│
|
||||
├── logger/ # Logging system
|
||||
│ └── decision_logger.go # Decision recording + performance analysis
|
||||
│
|
||||
├── decision_logs/ # Decision log storage
|
||||
│ ├── qwen_trader/ # Qwen trader logs
|
||||
│ └── deepseek_trader/ # DeepSeek trader logs
|
||||
│
|
||||
└── web/ # React frontend
|
||||
├── src/
|
||||
│ ├── components/ # React components
|
||||
│ │ ├── EquityChart.tsx # Equity curve chart
|
||||
│ │ ├── ComparisonChart.tsx # Multi-AI comparison chart
|
||||
│ │ └── CompetitionPage.tsx # Competition leaderboard
|
||||
│ ├── lib/api.ts # API call wrapper
|
||||
│ ├── types/index.ts # TypeScript types
|
||||
│ ├── index.css # Binance-style CSS
|
||||
│ └── App.tsx # Main app
|
||||
└── package.json
|
||||
```
|
||||
NOFX is built with a modern, modular architecture:
|
||||
|
||||
### Core Dependencies
|
||||
- **Backend:** Go with Gin framework, SQLite database
|
||||
- **Frontend:** React 18 + TypeScript + Vite + TailwindCSS
|
||||
- **Multi-Exchange Support:** Binance, Hyperliquid, Aster DEX
|
||||
- **AI Integration:** DeepSeek, Qwen, and custom OpenAI-compatible APIs
|
||||
- **State Management:** Zustand for frontend, database-driven for backend
|
||||
- **Real-time Updates:** SWR with 5-10s polling intervals
|
||||
|
||||
**Backend (Go)**
|
||||
- `github.com/adshao/go-binance/v2` - Binance API client
|
||||
- `github.com/markcheno/go-talib` - Technical indicator calculation (TA-Lib)
|
||||
- `github.com/gin-gonic/gin` - HTTP API framework
|
||||
**Key Features:**
|
||||
- 🗄️ Database-driven configuration (no more JSON editing)
|
||||
- 🔐 JWT authentication with optional 2FA support
|
||||
- 📊 Real-time performance tracking and analytics
|
||||
- 🤖 Multi-AI competition mode with live comparison
|
||||
- 🔌 RESTful API for all configuration and monitoring
|
||||
|
||||
**Frontend (React + TypeScript)**
|
||||
- `react` + `react-dom` - UI framework
|
||||
- `recharts` - Chart library (equity curve, comparison charts)
|
||||
- `swr` - Data fetching and caching
|
||||
- `tailwindcss` - CSS framework
|
||||
📖 **For detailed architecture documentation, see:**
|
||||
- **English:** [Architecture Documentation](docs/architecture/README.md)
|
||||
- **中文:** [架构文档](docs/architecture/README.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -261,7 +259,7 @@ Before using this system, you need a Binance Futures account. **Use our referral
|
||||
5. **Create API Key**:
|
||||
- Go to Account → API Management
|
||||
- Create new API key, **enable "Futures" permission**
|
||||
- Save API Key and Secret Key (needed for config.json)
|
||||
- Save API Key and Secret Key (~~needed for config.json~~) *needed for web interface*
|
||||
- **Important**: Whitelist your IP address for security
|
||||
|
||||
### Fee Discount Benefits:
|
||||
@@ -274,21 +272,23 @@ Before using this system, you need a Binance Futures account. **Use our referral
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 🐳 Option A: Docker One-Click Deployment (EASIEST - Recommended for Beginners!)
|
||||
### 🐳 Option A: Docker One-Click Deployment (EASIEST - Recommended!)
|
||||
|
||||
**⚡ Start trading in 3 simple steps with Docker - No installation needed!**
|
||||
**⚡ Start the platform in 2 simple steps with Docker - No installation needed!**
|
||||
|
||||
Docker automatically handles all dependencies (Go, Node.js, TA-Lib) and environment setup. Perfect for beginners!
|
||||
Docker automatically handles all dependencies (Go, Node.js, TA-Lib, SQLite) and environment setup.
|
||||
|
||||
#### Step 1: Prepare Configuration
|
||||
```bash
|
||||
# Copy configuration template
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
|
||||
# Edit and fill in your API keys
|
||||
nano config.json # or use any editor
|
||||
```
|
||||
|
||||
⚠️ **Note**: Basic config.json is still needed for some settings, but ~~trader configurations~~ are now done through the web interface.
|
||||
|
||||
#### Step 2: One-Click Start
|
||||
```bash
|
||||
# Option 1: Use convenience script (Recommended)
|
||||
@@ -305,10 +305,16 @@ chmod +x start.sh
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
#### Step 3: Access Dashboard
|
||||
#### Step 2: Access Web Interface
|
||||
Open your browser and visit: **http://localhost:3000**
|
||||
|
||||
**That's it! 🎉** Your AI trading system is now running!
|
||||
**That's it! 🎉** Your AI trading platform is now running!
|
||||
|
||||
#### Initial Setup (Through Web Interface)
|
||||
1. **Configure AI Models**: Add your DeepSeek/Qwen API keys
|
||||
2. **Configure Exchanges**: Set up Binance/Hyperliquid credentials
|
||||
3. **Create Traders**: Combine AI models with exchanges
|
||||
4. **Start Trading**: Launch your configured traders
|
||||
|
||||
#### Manage Your System
|
||||
```bash
|
||||
@@ -319,8 +325,8 @@ Open your browser and visit: **http://localhost:3000**
|
||||
```
|
||||
|
||||
**📖 For detailed Docker deployment guide, troubleshooting, and advanced configuration:**
|
||||
- **English**: See [DOCKER_DEPLOY.en.md](DOCKER_DEPLOY.en.md)
|
||||
- **中文**: 查看 [DOCKER_DEPLOY.md](DOCKER_DEPLOY.md)
|
||||
- **English**: See [docs/getting-started/docker-deploy.en.md](docs/getting-started/docker-deploy.en.md)
|
||||
- **中文**: 查看 [docs/getting-started/docker-deploy.zh-CN.md](docs/getting-started/docker-deploy.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -401,7 +407,7 @@ Before configuring the system, you need to obtain AI API keys. Choose one of the
|
||||
|
||||
**How to get Qwen API Key:**
|
||||
|
||||
1. **Visit**: [https://dashscope.aliyuncs.com](https://dashscope.aliyuncs.com)
|
||||
1. **Visit**: [https://dashscope.console.aliyun.com](https://dashscope.console.aliyun.com)
|
||||
2. **Register**: Sign up with Alibaba Cloud account
|
||||
3. **Enable Service**: Activate DashScope service
|
||||
4. **Create API Key**:
|
||||
@@ -413,71 +419,73 @@ Before configuring the system, you need to obtain AI API keys. Choose one of the
|
||||
|
||||
---
|
||||
|
||||
### 5. System Configuration
|
||||
### 5. Start the System
|
||||
|
||||
**Two configuration modes available:**
|
||||
- **🌟 Beginner Mode**: Single trader + default coins (recommended!)
|
||||
- **⚔️ Expert Mode**: Multiple traders competition
|
||||
|
||||
#### 🌟 Beginner Mode Configuration (Recommended)
|
||||
|
||||
**Step 1**: Copy and rename the example config file
|
||||
#### **Step 1: Start the Backend**
|
||||
|
||||
```bash
|
||||
cp config.json.example config.json
|
||||
# Build the program (first time only, or after code changes)
|
||||
go build -o nofx
|
||||
|
||||
# Start the backend
|
||||
./nofx
|
||||
```
|
||||
|
||||
**Step 2**: Edit `config.json` with your API keys
|
||||
**What you should see:**
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "my_trader",
|
||||
"name": "My AI Trader",
|
||||
"ai_model": "deepseek",
|
||||
"binance_api_key": "YOUR_BINANCE_API_KEY",
|
||||
"binance_secret_key": "YOUR_BINANCE_SECRET_KEY",
|
||||
"use_qwen": false,
|
||||
"deepseek_key": "sk-xxxxxxxxxxxxx",
|
||||
"qwen_key": "",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"leverage": {
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 5
|
||||
},
|
||||
"use_default_coins": true,
|
||||
"coin_pool_api_url": "",
|
||||
"oi_top_api_url": "",
|
||||
"api_server_port": 8080
|
||||
}
|
||||
```
|
||||
╔════════════════════════════════════════════════════════════╗
|
||||
║ 🤖 AI多模型交易系统 - 支持 DeepSeek & Qwen ║
|
||||
╚════════════════════════════════════════════════════════════╝
|
||||
|
||||
🤖 数据库中的AI交易员配置:
|
||||
• 暂无配置的交易员,请通过Web界面创建
|
||||
|
||||
🌐 API服务器启动在 http://localhost:8081
|
||||
```
|
||||
|
||||
**Step 3**: Replace placeholders with your actual keys
|
||||
#### **Step 2: Start the Frontend**
|
||||
|
||||
| Placeholder | Replace With | Where to Get |
|
||||
|------------|--------------|--------------|
|
||||
| `YOUR_BINANCE_API_KEY` | Your Binance API Key | Binance → Account → API Management |
|
||||
| `YOUR_BINANCE_SECRET_KEY` | Your Binance Secret Key | Same as above |
|
||||
| `sk-xxxxxxxxxxxxx` | Your DeepSeek API Key | [platform.deepseek.com](https://platform.deepseek.com) |
|
||||
Open a **NEW terminal window**, then:
|
||||
|
||||
**Step 4**: Adjust initial balance (optional)
|
||||
```bash
|
||||
cd web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
- `initial_balance`: Set to your actual Binance futures account balance
|
||||
- Used to calculate profit/loss percentage
|
||||
- Example: If you have 500 USDT, set `"initial_balance": 500.0`
|
||||
#### **Step 3: Access the Web Interface**
|
||||
|
||||
**✅ Configuration Checklist:**
|
||||
Open your browser and visit: **🌐 http://localhost:3000**
|
||||
|
||||
- [ ] Binance API key filled in (no quotes issues)
|
||||
- [ ] Binance Secret key filled in (no quotes issues)
|
||||
- [ ] DeepSeek API key filled in (starts with `sk-`)
|
||||
- [ ] `use_default_coins` set to `true` (for beginners)
|
||||
- [ ] `initial_balance` matches your account balance
|
||||
- [ ] File saved as `config.json` (not `.example`)
|
||||
### 6. Configure Through Web Interface
|
||||
|
||||
**Now configure everything through the web interface - no more JSON editing!**
|
||||
|
||||
#### **Step 1: Configure AI Models**
|
||||
1. Click "AI模型配置" button
|
||||
2. Enable DeepSeek or Qwen (or both)
|
||||
3. Enter your API keys
|
||||
4. Save configuration
|
||||
|
||||
#### **Step 2: Configure Exchanges**
|
||||
1. Click "交易所配置" button
|
||||
2. Enable Binance or Hyperliquid (or both)
|
||||
3. Enter your API credentials
|
||||
4. Save configuration
|
||||
|
||||
#### **Step 3: Create Traders**
|
||||
1. Click "创建交易员" button
|
||||
2. Select an AI model (must be configured first)
|
||||
3. Select an exchange (must be configured first)
|
||||
4. Set initial balance and trader name
|
||||
5. Create trader
|
||||
|
||||
#### **Step 4: Start Trading**
|
||||
- Your traders will appear in the main interface
|
||||
- Use Start/Stop buttons to control them
|
||||
- Monitor performance in real-time
|
||||
|
||||
**✅ No more JSON file editing - everything is done through the web interface!**
|
||||
|
||||
---
|
||||
|
||||
@@ -492,7 +500,7 @@ cp config.json.example config.json
|
||||
3. **Remove the `0x` prefix** from the key
|
||||
4. Fund your wallet on [Hyperliquid](https://hyperliquid.xyz)
|
||||
|
||||
**Step 2**: Configure `config.json` for Hyperliquid
|
||||
**Step 2**: ~~Configure `config.json` for Hyperliquid~~ *Configure through web interface*
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -547,7 +555,7 @@ cp config.json.example config.json
|
||||
- API Wallet address (Signer)
|
||||
- API Wallet Private Key (⚠️ shown only once!)
|
||||
|
||||
**Step 2**: Configure `config.json` for Aster
|
||||
**Step 2**: ~~Configure `config.json` for Aster~~ *Configure through web interface*
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -665,8 +673,10 @@ For running multiple AI traders competing against each other:
|
||||
| `oi_top_api_url` | Open interest API<br>*Optional supplement data* | `""` (empty) | ❌ No |
|
||||
| `api_server_port` | Web dashboard port | `8080` | ✅ Yes |
|
||||
|
||||
**Default Trading Coins** (when `use_default_coins: true`):
|
||||
- BTC, ETH, SOL, BNB, XRP, DOGE, ADA, HYPE
|
||||
~~**Default Trading Coins** (when `use_default_coins: true`):
|
||||
- BTC, ETH, SOL, BNB, XRP, DOGE, ADA, HYPE~~
|
||||
|
||||
*Note: Trading coins are now configured through the web interface*
|
||||
|
||||
---
|
||||
|
||||
@@ -676,7 +686,7 @@ For running multiple AI traders competing against each other:
|
||||
|
||||
The leverage settings control the maximum leverage the AI can use for each trade. This is crucial for risk management, especially for Binance subaccounts which have leverage restrictions.
|
||||
|
||||
**Configuration format:**
|
||||
~~**Configuration format:**~~
|
||||
|
||||
```json
|
||||
"leverage": {
|
||||
@@ -685,6 +695,8 @@ The leverage settings control the maximum leverage the AI can use for each trade
|
||||
}
|
||||
```
|
||||
|
||||
*Note: Leverage is now configured through the web interface*
|
||||
|
||||
**⚠️ Important: Binance Subaccount Restrictions**
|
||||
|
||||
- **Subaccounts**: Limited to **≤5x leverage** by Binance
|
||||
@@ -702,7 +714,7 @@ The leverage settings control the maximum leverage the AI can use for each trade
|
||||
|
||||
**Examples:**
|
||||
|
||||
**Safe configuration (subaccount or conservative):**
|
||||
~~**Safe configuration (subaccount or conservative):**~~
|
||||
```json
|
||||
"leverage": {
|
||||
"btc_eth_leverage": 5,
|
||||
@@ -710,7 +722,7 @@ The leverage settings control the maximum leverage the AI can use for each trade
|
||||
}
|
||||
```
|
||||
|
||||
**Aggressive configuration (main account only):**
|
||||
~~**Aggressive configuration (main account only):**~~
|
||||
```json
|
||||
"leverage": {
|
||||
"btc_eth_leverage": 20,
|
||||
@@ -718,6 +730,8 @@ The leverage settings control the maximum leverage the AI can use for each trade
|
||||
}
|
||||
```
|
||||
|
||||
*Note: Leverage configuration is now done through the web interface*
|
||||
|
||||
**How AI uses leverage:**
|
||||
|
||||
- AI can choose **any leverage from 1x up to your configured maximum**
|
||||
@@ -799,7 +813,7 @@ go build -o nofx
|
||||
|--------------|----------|
|
||||
| `invalid API key` | Check your Binance API key in config.json |
|
||||
| `TA-Lib not found` | Run `brew install ta-lib` (macOS) |
|
||||
| `port 8080 already in use` | Change `api_server_port` in config.json |
|
||||
| `port 8080 already in use` | ~~Change `api_server_port` in config.json~~ *Change `API_PORT` in .env file* |
|
||||
| `DeepSeek API error` | Verify your DeepSeek API key and balance |
|
||||
|
||||
**✅ Backend is running correctly when you see:**
|
||||
@@ -873,7 +887,7 @@ Open your web browser and visit:
|
||||
|
||||
```bash
|
||||
# In a new terminal window
|
||||
curl http://localhost:8080/health
|
||||
curl http://localhost:8080/api/health
|
||||
```
|
||||
|
||||
Should return: `{"status":"ok"}`
|
||||
@@ -901,109 +915,100 @@ Should return: `{"status":"ok"}`
|
||||
|
||||
Each decision cycle (default 3 minutes), the system executes the following intelligent process:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 1. 📊 Analyze Historical Performance (last 20 cycles) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ✓ Calculate overall win rate, avg profit, P/L ratio │
|
||||
│ ✓ Per-coin statistics (win rate, avg P/L in USDT) │
|
||||
│ ✓ Identify best/worst performing coins │
|
||||
│ ✓ List last 5 trade details with accurate PnL │
|
||||
│ ✓ Calculate Sharpe ratio for risk-adjusted performance │
|
||||
│ 📌 NEW (v2.0.2): Accurate USDT PnL with leverage │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 2. 💰 Get Account Status │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Total equity & available balance │
|
||||
│ • Number of open positions & unrealized P/L │
|
||||
│ • Margin usage rate (AI manages up to 90%) │
|
||||
│ • Daily P/L tracking & drawdown monitoring │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 3. 🔍 Analyze Existing Positions (if any) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • For each position, fetch latest market data │
|
||||
│ • Calculate real-time technical indicators: │
|
||||
│ - 3min K-line: RSI(7), MACD, EMA20 │
|
||||
│ - 4hour K-line: RSI(14), EMA20/50, ATR │
|
||||
│ • Track position holding duration (e.g., "2h 15min") │
|
||||
│ 📌 NEW (v2.0.2): Shows how long each position held │
|
||||
│ • Display: Entry price, current price, P/L%, duration │
|
||||
│ • AI evaluates: Should hold or close? │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 4. 🎯 Evaluate New Opportunities (candidate coins) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Fetch coin pool (2 modes): │
|
||||
│ 🌟 Default Mode: BTC, ETH, SOL, BNB, XRP, etc. │
|
||||
│ ⚙️ Advanced Mode: AI500 (top 20) + OI Top (top 20) │
|
||||
│ • Merge & deduplicate candidate coins │
|
||||
│ • Filter: Remove low liquidity (<15M USD OI value) │
|
||||
│ • Batch fetch market data + technical indicators │
|
||||
│ • Calculate volatility, trend strength, volume surge │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 5. 🧠 AI Comprehensive Decision (DeepSeek/Qwen) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Review historical feedback: │
|
||||
│ - Recent win rate & profit factor │
|
||||
│ - Best/worst coins performance │
|
||||
│ - Avoid repeating mistakes │
|
||||
│ • Analyze all raw sequence data: │
|
||||
│ - 3min price序列, 4hour K-line序列 │
|
||||
│ - Complete indicator sequences (not just latest) │
|
||||
│ 📌 NEW (v2.0.2): AI has full freedom to analyze │
|
||||
│ • Chain of Thought (CoT) reasoning process │
|
||||
│ • Output structured decisions: │
|
||||
│ - Action: close_long/close_short/open_long/open_short│
|
||||
│ - Coin symbol, quantity, leverage │
|
||||
│ - Stop-loss & take-profit levels (≥1:2 ratio) │
|
||||
│ • Decision: Wait/Hold/Close/Open │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 6. ⚡ Execute Trades │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Priority order: Close existing → Then open new │
|
||||
│ • Risk checks before execution: │
|
||||
│ - Position size limits (1.5x for altcoins, 10x BTC) │
|
||||
│ - No duplicate positions (same coin + direction) │
|
||||
│ - Margin usage within 90% limit │
|
||||
│ • Auto-fetch & apply Binance LOT_SIZE precision │
|
||||
│ • Execute orders via Binance Futures API │
|
||||
│ • After closing: Auto-cancel all pending orders │
|
||||
│ • Record actual execution price & order ID │
|
||||
│ 📌 Track position open time for duration calculation │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 7. 📝 Record Complete Logs & Update Performance │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Save decision log to decision_logs/{trader_id}/ │
|
||||
│ • Log includes: │
|
||||
│ - Complete Chain of Thought (CoT) │
|
||||
│ - Input prompt with all market data │
|
||||
│ - Structured decision JSON │
|
||||
│ - Account snapshot (balance, positions, margin) │
|
||||
│ - Execution results (success/failure, prices) │
|
||||
│ • Update performance database: │
|
||||
│ - Match open/close pairs by symbol_side key │
|
||||
│ 📌 NEW: Prevents long/short conflicts │
|
||||
│ - Calculate accurate USDT PnL: │
|
||||
│ PnL = Position Value × Price Δ% × Leverage │
|
||||
│ 📌 NEW: Considers quantity + leverage │
|
||||
│ - Store: quantity, leverage, open time, close time │
|
||||
│ - Update win rate, profit factor, Sharpe ratio │
|
||||
│ • Performance data feeds back into next cycle │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
(Repeat every 3-5 min)
|
||||
```
|
||||
### Step 1: 📊 Analyze Historical Performance (last 20 cycles)
|
||||
- ✓ Calculate overall win rate, avg profit, P/L ratio
|
||||
- ✓ Per-coin statistics (win rate, avg P/L in USDT)
|
||||
- ✓ Identify best/worst performing coins
|
||||
- ✓ List last 5 trade details with accurate PnL
|
||||
- ✓ Calculate Sharpe ratio for risk-adjusted performance
|
||||
- 📌 **NEW (v2.0.2)**: Accurate USDT PnL with leverage
|
||||
|
||||
**↓**
|
||||
|
||||
### Step 2: 💰 Get Account Status
|
||||
- Total equity & available balance
|
||||
- Number of open positions & unrealized P/L
|
||||
- Margin usage rate (AI manages up to 90%)
|
||||
- Daily P/L tracking & drawdown monitoring
|
||||
|
||||
**↓**
|
||||
|
||||
### Step 3: 🔍 Analyze Existing Positions (if any)
|
||||
- For each position, fetch latest market data
|
||||
- Calculate real-time technical indicators:
|
||||
- 3min K-line: RSI(7), MACD, EMA20
|
||||
- 4hour K-line: RSI(14), EMA20/50, ATR
|
||||
- Track position holding duration (e.g., "2h 15min")
|
||||
- 📌 **NEW (v2.0.2)**: Shows how long each position held
|
||||
- Display: Entry price, current price, P/L%, duration
|
||||
- AI evaluates: Should hold or close?
|
||||
|
||||
**↓**
|
||||
|
||||
### Step 4: 🎯 Evaluate New Opportunities (candidate coins)
|
||||
- Fetch coin pool (2 modes):
|
||||
- 🌟 **Default Mode**: BTC, ETH, SOL, BNB, XRP, etc.
|
||||
- ⚙️ **Advanced Mode**: AI500 (top 20) + OI Top (top 20)
|
||||
- Merge & deduplicate candidate coins
|
||||
- Filter: Remove low liquidity (<15M USD OI value)
|
||||
- Batch fetch market data + technical indicators
|
||||
- Calculate volatility, trend strength, volume surge
|
||||
|
||||
**↓**
|
||||
|
||||
### Step 5: 🧠 AI Comprehensive Decision (DeepSeek/Qwen)
|
||||
- Review historical feedback:
|
||||
- Recent win rate & profit factor
|
||||
- Best/worst coins performance
|
||||
- Avoid repeating mistakes
|
||||
- Analyze all raw sequence data:
|
||||
- 3min price sequences, 4hour K-line sequences
|
||||
- Complete indicator sequences (not just latest)
|
||||
- 📌 **NEW (v2.0.2)**: AI has full freedom to analyze
|
||||
- Chain of Thought (CoT) reasoning process
|
||||
- Output structured decisions:
|
||||
- Action: `close_long` / `close_short` / `open_long` / `open_short`
|
||||
- Coin symbol, quantity, leverage
|
||||
- Stop-loss & take-profit levels (≥1:2 ratio)
|
||||
- Decision: Wait / Hold / Close / Open
|
||||
|
||||
**↓**
|
||||
|
||||
### Step 6: ⚡ Execute Trades
|
||||
- Priority order: Close existing → Then open new
|
||||
- Risk checks before execution:
|
||||
- Position size limits (1.5x for altcoins, 10x BTC)
|
||||
- No duplicate positions (same coin + direction)
|
||||
- Margin usage within 90% limit
|
||||
- Auto-fetch & apply Binance LOT_SIZE precision
|
||||
- Execute orders via Binance Futures API
|
||||
- After closing: Auto-cancel all pending orders
|
||||
- Record actual execution price & order ID
|
||||
- 📌 Track position open time for duration calculation
|
||||
|
||||
**↓**
|
||||
|
||||
### Step 7: 📝 Record Complete Logs & Update Performance
|
||||
- Save decision log to `decision_logs/{trader_id}/`
|
||||
- Log includes:
|
||||
- Complete Chain of Thought (CoT)
|
||||
- Input prompt with all market data
|
||||
- Structured decision JSON
|
||||
- Account snapshot (balance, positions, margin)
|
||||
- Execution results (success/failure, prices)
|
||||
- Update performance database:
|
||||
- Match open/close pairs by `symbol_side` key
|
||||
- 📌 **NEW**: Prevents long/short conflicts
|
||||
- Calculate accurate USDT PnL:
|
||||
- `PnL = Position Value × Price Δ% × Leverage`
|
||||
- 📌 **NEW**: Considers quantity + leverage
|
||||
- Store: quantity, leverage, open time, close time
|
||||
- Update win rate, profit factor, Sharpe ratio
|
||||
- Performance data feeds back into next cycle
|
||||
|
||||
**↓**
|
||||
|
||||
**🔄 (Repeat every 3-5 min)**
|
||||
|
||||
### Key Improvements in v2.0.2
|
||||
|
||||
@@ -1089,14 +1094,26 @@ Each decision cycle (default 3 minutes), the system executes the following intel
|
||||
|
||||
## 🎛️ API Endpoints
|
||||
|
||||
### Competition Related
|
||||
### Configuration Management
|
||||
|
||||
```bash
|
||||
GET /api/competition # Competition leaderboard (all traders)
|
||||
GET /api/traders # Trader list
|
||||
GET /api/models # Get AI model configurations
|
||||
PUT /api/models # Update AI model configurations
|
||||
GET /api/exchanges # Get exchange configurations
|
||||
PUT /api/exchanges # Update exchange configurations
|
||||
```
|
||||
|
||||
### Single Trader Related
|
||||
### Trader Management
|
||||
|
||||
```bash
|
||||
GET /api/traders # List all traders
|
||||
POST /api/traders # Create new trader
|
||||
DELETE /api/traders/:id # Delete trader
|
||||
POST /api/traders/:id/start # Start trader
|
||||
POST /api/traders/:id/stop # Stop trader
|
||||
```
|
||||
|
||||
### Trading Data & Monitoring
|
||||
|
||||
```bash
|
||||
GET /api/status?trader_id=xxx # System status
|
||||
@@ -1105,13 +1122,13 @@ GET /api/positions?trader_id=xxx # Position list
|
||||
GET /api/equity-history?trader_id=xxx # Equity history (chart data)
|
||||
GET /api/decisions/latest?trader_id=xxx # Latest 5 decisions
|
||||
GET /api/statistics?trader_id=xxx # Statistics
|
||||
GET /api/performance?trader_id=xxx # AI performance analysis
|
||||
```
|
||||
|
||||
### System Endpoints
|
||||
|
||||
```bash
|
||||
GET /health # Health check
|
||||
GET /api/config # System configuration
|
||||
GET /api/health # Health check
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1187,7 +1204,7 @@ sudo apt-get install libta-lib0-dev
|
||||
**Solution**:
|
||||
- Coin pool API is optional
|
||||
- If API fails, system uses default mainstream coins (BTC, ETH, etc.)
|
||||
- Check API URL and auth parameter in config.json
|
||||
- ~~Check API URL and auth parameter in config.json~~ *Check configuration in web interface*
|
||||
|
||||
---
|
||||
|
||||
@@ -1203,90 +1220,19 @@ sudo apt-get install libta-lib0-dev
|
||||
|
||||
## 🔄 Changelog
|
||||
|
||||
### v2.0.2 (2025-10-29)
|
||||
📖 **For detailed version history and updates, see:**
|
||||
|
||||
**Critical Bug Fixes - Trade History & Performance Analysis:**
|
||||
- **English:** [CHANGELOG.md](CHANGELOG.md)
|
||||
- **中文:** [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md)
|
||||
|
||||
This version fixes **critical calculation errors** in the historical trade record and performance analysis system that significantly affected profitability statistics.
|
||||
**Latest Release:** v3.0.0 (2025-10-30) - Major Architecture Transformation
|
||||
|
||||
**1. PnL Calculation - Major Error Fixed** (logger/decision_logger.go)
|
||||
- **Problem**: Previously calculated PnL as percentage only, completely ignoring position size and leverage
|
||||
- Example: 100 USDT position earning 5% and 1000 USDT position earning 5% both showed `5.0` as profit
|
||||
- This made performance analysis completely inaccurate
|
||||
- **Solution**: Now calculates actual USDT profit amount
|
||||
```
|
||||
PnL (USDT) = Position Value × Price Change % × Leverage
|
||||
Example: 1000 USDT × 5% × 20x = 1000 USDT actual profit
|
||||
```
|
||||
- **Impact**: Win rate, profit factor, and Sharpe ratio now based on accurate USDT amounts
|
||||
|
||||
**2. Position Tracking - Missing Critical Data**
|
||||
- **Problem**: Open position records only stored price and time, missing quantity and leverage
|
||||
- **Solution**: Now stores complete trade data:
|
||||
- `quantity`: Position size (in coins)
|
||||
- `leverage`: Leverage multiplier (e.g., 20x)
|
||||
- These are essential for accurate PnL calculations
|
||||
|
||||
**3. Position Key Logic - Long/Short Conflict**
|
||||
- **Problem**: Used `symbol` as position key, causing data conflicts when holding both long and short
|
||||
- Example: BTCUSDT long and BTCUSDT short would overwrite each other
|
||||
- **Solution**: Changed to `symbol_side` format (e.g., `BTCUSDT_long`, `BTCUSDT_short`)
|
||||
- Now properly distinguishes between long and short positions
|
||||
|
||||
**4. Sharpe Ratio Calculation - Code Optimization**
|
||||
- **Problem**: Used custom Newton's method for square root calculation
|
||||
- **Solution**: Replaced with standard library `math.Sqrt`
|
||||
- More reliable, maintainable, and efficient
|
||||
|
||||
**Why This Update Matters:**
|
||||
- ✅ Historical trade statistics now show **real USDT profit/loss** instead of meaningless percentages
|
||||
- ✅ Performance comparison between different leverage trades is now accurate
|
||||
- ✅ AI self-learning mechanism receives correct historical feedback
|
||||
- ✅ Profit factor and Sharpe ratio calculations are now meaningful
|
||||
- ✅ Multi-position tracking (long + short simultaneously) works correctly
|
||||
|
||||
**Recommendation**: If you were running the system before this update, your historical statistics were inaccurate. After updating to v2.0.2, new trades will be calculated correctly.
|
||||
|
||||
### v2.0.2 (2025-10-29)
|
||||
|
||||
**Bug Fixes:**
|
||||
- ✅ Fixed Aster exchange precision error (code -1111: "Precision is over the maximum defined for this asset")
|
||||
- ✅ Improved price and quantity formatting to match exchange precision requirements
|
||||
- ✅ Added detailed precision processing logs for debugging
|
||||
- ✅ Enhanced all order functions (OpenLong, OpenShort, CloseLong, CloseShort, SetStopLoss, SetTakeProfit) with proper precision handling
|
||||
|
||||
**Technical Details:**
|
||||
- Added `formatFloatWithPrecision` function to convert float64 to strings with correct precision
|
||||
- Price and quantity parameters are now formatted according to exchange's `pricePrecision` and `quantityPrecision` specifications
|
||||
- Trailing zeros are removed from formatted values to optimize API requests
|
||||
|
||||
### v2.0.1 (2025-10-29)
|
||||
|
||||
**Bug Fixes:**
|
||||
- ✅ Fixed ComparisonChart data processing logic - switched from cycle_number to timestamp grouping
|
||||
- ✅ Resolved chart freezing issue when backend restarts and cycle_number resets
|
||||
- ✅ Improved chart data display - now shows all historical data points chronologically
|
||||
- ✅ Enhanced debugging logs for better troubleshooting
|
||||
|
||||
### v2.0.0 (2025-10-28)
|
||||
|
||||
**Major Updates:**
|
||||
- ✅ AI self-learning mechanism (historical feedback, performance analysis)
|
||||
- ✅ Multi-trader competition mode (Qwen vs DeepSeek)
|
||||
- ✅ Binance-style UI (complete Binance interface imitation)
|
||||
- ✅ Performance comparison charts (real-time ROI comparison)
|
||||
- ✅ Risk control optimization (per-coin position limit adjustment)
|
||||
|
||||
**Bug Fixes:**
|
||||
- Fixed hardcoded initial balance issue
|
||||
- Fixed multi-trader data sync issue
|
||||
- Optimized chart data alignment (using cycle_number)
|
||||
|
||||
### v1.0.0 (2025-10-27)
|
||||
- Initial release
|
||||
- Basic AI trading functionality
|
||||
- Decision logging system
|
||||
- Simple Web interface
|
||||
**Recent Highlights:**
|
||||
- 🚀 Complete system redesign with web-based configuration
|
||||
- 🗄️ Database-driven architecture (SQLite)
|
||||
- 🎨 No more JSON editing - all configuration through web interface
|
||||
- 🔧 Mix & match AI models with any exchange
|
||||
- 📊 Enhanced API layer with comprehensive endpoints
|
||||
|
||||
---
|
||||
|
||||
@@ -1298,10 +1244,14 @@ MIT License - See [LICENSE](LICENSE) file for details
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Issues and Pull Requests are welcome!
|
||||
We welcome contributions from the community! See our comprehensive guides:
|
||||
|
||||
### Development Guide
|
||||
- **📖 [Contributing Guide](CONTRIBUTING.md)** - Complete development workflow, code standards, and PR process
|
||||
- **🤝 [Code of Conduct](CODE_OF_CONDUCT.md)** - Community guidelines and standards
|
||||
- **💰 [Bounty Program](docs/community/bounty-guide.md)** - Earn rewards for contributions
|
||||
- **🔒 [Security Policy](SECURITY.md)** - Report vulnerabilities responsibly
|
||||
|
||||
**Quick Start:**
|
||||
1. Fork the project
|
||||
2. Create feature branch (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit changes (`git commit -m 'Add some AmazingFeature'`)
|
||||
@@ -1323,13 +1273,13 @@ Issues and Pull Requests are welcome!
|
||||
|
||||
- [Binance API](https://binance-docs.github.io/apidocs/futures/en/) - Binance Futures API
|
||||
- [DeepSeek](https://platform.deepseek.com/) - DeepSeek AI API
|
||||
- [Qwen](https://dashscope.aliyuncs.com/) - Alibaba Cloud Qwen
|
||||
- [Qwen](https://dashscope.console.aliyun.com/) - Alibaba Cloud Qwen
|
||||
- [TA-Lib](https://ta-lib.org/) - Technical indicator library
|
||||
- [Recharts](https://recharts.org/) - React chart library
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-10-29 (v2.0.3)
|
||||
**Last Updated**: 2025-10-30 (v3.0.0)
|
||||
|
||||
**⚡ Explore the possibilities of quantitative trading with the power of AI!**
|
||||
|
||||
|
||||
469
SECURITY.md
Normal file
@@ -0,0 +1,469 @@
|
||||
# Security Policy / 安全政策
|
||||
|
||||
**Languages:** [English](#english) | [中文](#中文)
|
||||
|
||||
---
|
||||
|
||||
# English
|
||||
|
||||
## 🛡️ Security Overview
|
||||
|
||||
NOFX is an AI-powered trading system that handles real funds and API credentials. We take security seriously and appreciate the security community's efforts to responsibly disclose vulnerabilities.
|
||||
|
||||
**Critical Areas:**
|
||||
- 🔑 API key storage and handling
|
||||
- 💰 Trading execution and fund management
|
||||
- 🔐 Authentication and authorization
|
||||
- 🗄️ Database security (SQLite)
|
||||
- 🌐 Web interface and API endpoints
|
||||
|
||||
---
|
||||
|
||||
## 📋 Supported Versions
|
||||
|
||||
We provide security updates for the following versions:
|
||||
|
||||
| Version | Supported | Notes |
|
||||
| ------- | ------------------ | -------------------- |
|
||||
| 3.x | ✅ Fully supported | Current stable release |
|
||||
| 2.x | ⚠️ Limited support | Security fixes only |
|
||||
| < 2.0 | ❌ Not supported | Please upgrade |
|
||||
|
||||
**Recommendation:** Always use the latest stable release (v3.x) for best security.
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Reporting a Vulnerability
|
||||
|
||||
### ⚠️ Please DO NOT Publicly Disclose
|
||||
|
||||
If you discover a security vulnerability in NOFX, please **DO NOT**:
|
||||
- ❌ Open a public GitHub Issue
|
||||
- ❌ Discuss it on social media (Twitter, Reddit, etc.)
|
||||
- ❌ Share it in Telegram/Discord groups
|
||||
- ❌ Post it on security forums before we've had time to fix it
|
||||
|
||||
Public disclosure before a fix is available puts all users at risk.
|
||||
|
||||
### ✅ Responsible Disclosure Process
|
||||
|
||||
**Step 1: Report Privately**
|
||||
|
||||
Contact core team directly:
|
||||
- **Tinkle:** [@Web3Tinkle on Twitter](https://x.com/Web3Tinkle) (DM)
|
||||
|
||||
**Alternative:** Encrypted communication via [Keybase](https://keybase.io/) (if available)
|
||||
|
||||
**Step 2: Include These Details**
|
||||
|
||||
```markdown
|
||||
Subject: [SECURITY] Brief description of vulnerability
|
||||
|
||||
## Vulnerability Description
|
||||
Clear explanation of the security issue
|
||||
|
||||
## Affected Components
|
||||
- Which parts of the system are affected?
|
||||
- Which versions are vulnerable?
|
||||
|
||||
## Reproduction Steps
|
||||
1. Step-by-step instructions
|
||||
2. Sample code or commands (if applicable)
|
||||
3. Expected vs actual behavior
|
||||
|
||||
## Potential Impact
|
||||
- Can funds be stolen?
|
||||
- Can API keys be leaked?
|
||||
- Can accounts be compromised?
|
||||
- Rate the severity: Critical / High / Medium / Low
|
||||
|
||||
## Suggested Fix (Optional)
|
||||
If you have ideas for fixing it, please share!
|
||||
|
||||
## Your Information
|
||||
- Name (or pseudonym)
|
||||
- Contact info for follow-up
|
||||
- If you want public credit (yes/no)
|
||||
```
|
||||
|
||||
**Step 3: Wait for Our Response**
|
||||
|
||||
We will:
|
||||
- ✅ Acknowledge receipt within **24 hours**
|
||||
- ✅ Provide initial assessment within **72 hours**
|
||||
- ✅ Keep you updated on fix progress
|
||||
- ✅ Notify you before public disclosure
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ Response Timeline
|
||||
|
||||
| Stage | Timeline | Action |
|
||||
|-------|----------|--------|
|
||||
| **Acknowledgment** | 24 hours | Confirm we received your report |
|
||||
| **Initial Assessment** | 72 hours | Verify vulnerability, rate severity |
|
||||
| **Fix Development** | 7-30 days | Depends on complexity and severity |
|
||||
| **Testing** | 3-7 days | Verify fix doesn't break functionality |
|
||||
| **Public Disclosure** | After fix deployed | Publish security advisory |
|
||||
|
||||
**Critical vulnerabilities** (fund theft, credential leaks) are prioritized and may be fixed within 48 hours.
|
||||
|
||||
---
|
||||
|
||||
## 💰 Security Bounty Program (Optional)
|
||||
|
||||
We offer rewards for valid security vulnerabilities:
|
||||
|
||||
| Severity | Criteria | Reward |
|
||||
|----------|----------|--------|
|
||||
| **🔴 Critical** | Fund theft, API key extraction, RCE | **$500-1000 USD** |
|
||||
| **🟠 High** | Authentication bypass, unauthorized trading | **$200-500 USD** |
|
||||
| **🟡 Medium** | Information disclosure, XSS, CSRF | **$100-200 USD** |
|
||||
| **🟢 Low** | Security improvements, minor issues | **$50-100 USD or Recognition** |
|
||||
|
||||
**Note:** Bounty amounts are at maintainers' discretion based on:
|
||||
- Severity and impact
|
||||
- Quality of report
|
||||
- Ease of exploitation
|
||||
- Number of affected users
|
||||
|
||||
**Out of Scope (No Bounty):**
|
||||
- Issues in third-party libraries (report to them directly)
|
||||
- Social engineering attacks
|
||||
- DoS/DDoS attacks
|
||||
- Issues requiring physical access
|
||||
- Previously known/reported vulnerabilities
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Best Practices (For Users)
|
||||
|
||||
To keep your NOFX deployment secure:
|
||||
|
||||
### 1. API Key Management
|
||||
```bash
|
||||
# ✅ DO: Use environment variables
|
||||
export BINANCE_API_KEY="your_key"
|
||||
export BINANCE_SECRET_KEY="your_secret"
|
||||
|
||||
# ❌ DON'T: Hardcode in source files
|
||||
api_key = "abc123..." # NEVER DO THIS
|
||||
```
|
||||
|
||||
### 2. Database Security
|
||||
```bash
|
||||
# ✅ Set proper permissions
|
||||
chmod 600 nofx.db
|
||||
chmod 600 config.json
|
||||
|
||||
# ❌ DON'T: Leave files world-readable
|
||||
chmod 777 nofx.db # NEVER DO THIS
|
||||
```
|
||||
|
||||
### 3. Network Security
|
||||
```bash
|
||||
# ✅ Use firewall to restrict API access
|
||||
# Only allow localhost to access API server
|
||||
iptables -A INPUT -p tcp --dport 8080 -s 127.0.0.1 -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 8080 -j DROP
|
||||
|
||||
# ❌ DON'T: Expose API to public internet without authentication
|
||||
```
|
||||
|
||||
### 4. Use Subaccounts
|
||||
- Create dedicated Binance subaccount for trading
|
||||
- Limit maximum balance
|
||||
- Restrict withdrawal permissions
|
||||
- Use IP whitelist
|
||||
|
||||
### 5. Test on Testnet First
|
||||
- Hyperliquid: Use testnet mode
|
||||
- Binance: Use testnet API (https://testnet.binancefuture.com)
|
||||
- Never test with real funds initially
|
||||
|
||||
### 6. Regular Updates
|
||||
```bash
|
||||
# Check for updates regularly
|
||||
git pull origin main
|
||||
go build -o nofx
|
||||
|
||||
# Subscribe to security advisories
|
||||
# Watch GitHub releases: https://github.com/tinkle-community/nofx/releases
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Security Advisories
|
||||
|
||||
Past security advisories will be published here:
|
||||
|
||||
### 2025-XX-XX: [Title]
|
||||
- **Severity:** [Critical/High/Medium/Low]
|
||||
- **Affected Versions:** [x.x.x - x.x.x]
|
||||
- **Fixed in:** [x.x.x]
|
||||
- **Description:** [Brief description]
|
||||
- **Mitigation:** [How to protect yourself]
|
||||
|
||||
*No security advisories have been published yet.*
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Security Researchers Hall of Fame
|
||||
|
||||
We thank the following security researchers for responsibly disclosing vulnerabilities:
|
||||
|
||||
*No reports have been submitted yet. Be the first!*
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
**Security Documentation:**
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [CWE Top 25](https://cwe.mitre.org/top25/)
|
||||
- [Binance API Security Best Practices](https://www.binance.com/en/support/faq/360002502072)
|
||||
|
||||
**Audit Reports:**
|
||||
- No third-party audits completed yet
|
||||
- Self-audit checklist: [TODO: Add link]
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact
|
||||
|
||||
**For security issues ONLY:**
|
||||
- 🐦 **Twitter DM:** [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
|
||||
**For general questions:**
|
||||
- See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
- Join [Telegram Community](https://t.me/nofx_dev_community)
|
||||
|
||||
---
|
||||
|
||||
**Thank you for helping keep NOFX secure!** 🔒
|
||||
|
||||
---
|
||||
|
||||
# 中文
|
||||
|
||||
## 🛡️ 安全概述
|
||||
|
||||
NOFX 是一个处理真实资金和 API 凭证的 AI 交易系统。我们非常重视安全,并感谢安全社区负责任地披露漏洞的努力。
|
||||
|
||||
**关键领域:**
|
||||
- 🔑 API 密钥存储和处理
|
||||
- 💰 交易执行和资金管理
|
||||
- 🔐 身份验证和授权
|
||||
- 🗄️ 数据库安全(SQLite)
|
||||
- 🌐 Web 界面和 API 端点
|
||||
|
||||
---
|
||||
|
||||
## 📋 支持的版本
|
||||
|
||||
我们为以下版本提供安全更新:
|
||||
|
||||
| 版本 | 支持状态 | 说明 |
|
||||
| ------- | ------------------ | -------------------- |
|
||||
| 3.x | ✅ 完全支持 | 当前稳定版本 |
|
||||
| 2.x | ⚠️ 有限支持 | 仅安全修复 |
|
||||
| < 2.0 | ❌ 不支持 | 请升级 |
|
||||
|
||||
**建议:** 始终使用最新的稳定版本(v3.x)以获得最佳安全性。
|
||||
|
||||
---
|
||||
|
||||
## 🔒 报告漏洞
|
||||
|
||||
### ⚠️ 请勿公开披露
|
||||
|
||||
如果您在 NOFX 中发现安全漏洞,请**不要**:
|
||||
- ❌ 公开创建 GitHub Issue
|
||||
- ❌ 在社交媒体上讨论(Twitter、Reddit 等)
|
||||
- ❌ 在 Telegram/Discord 群组中分享
|
||||
- ❌ 在我们有时间修复之前发布到安全论坛
|
||||
|
||||
在修复可用之前公开披露会使所有用户面临风险。
|
||||
|
||||
### ✅ 负责任的披露流程
|
||||
|
||||
**步骤 1:私下报告**
|
||||
|
||||
直接联系核心团队:
|
||||
- **Tinkle:** [@Web3Tinkle on Twitter](https://x.com/Web3Tinkle)(私信)
|
||||
|
||||
**替代方案:** 通过 [Keybase](https://keybase.io/) 加密通信(如果可用)
|
||||
|
||||
**步骤 2:包含这些详细信息**
|
||||
|
||||
```markdown
|
||||
主题:[SECURITY] 漏洞简要描述
|
||||
|
||||
## 漏洞描述
|
||||
清楚解释安全问题
|
||||
|
||||
## 受影响的组件
|
||||
- 系统的哪些部分受到影响?
|
||||
- 哪些版本存在漏洞?
|
||||
|
||||
## 复现步骤
|
||||
1. 逐步说明
|
||||
2. 示例代码或命令(如果适用)
|
||||
3. 预期行为 vs 实际行为
|
||||
|
||||
## 潜在影响
|
||||
- 资金是否可能被盗?
|
||||
- API 密钥是否可能泄露?
|
||||
- 账户是否可能被入侵?
|
||||
- 严重程度评级:严重 / 高 / 中 / 低
|
||||
|
||||
## 建议修复(可选)
|
||||
如果您有修复的想法,请分享!
|
||||
|
||||
## 您的信息
|
||||
- 姓名(或化名)
|
||||
- 后续联系信息
|
||||
- 是否希望公开致谢(是/否)
|
||||
```
|
||||
|
||||
**步骤 3:等待我们的回复**
|
||||
|
||||
我们将:
|
||||
- ✅ 在 **24 小时**内确认收到
|
||||
- ✅ 在 **72 小时**内提供初步评估
|
||||
- ✅ 告知您修复进展
|
||||
- ✅ 在公开披露前通知您
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ 响应时间表
|
||||
|
||||
| 阶段 | 时间线 | 行动 |
|
||||
|-------|----------|--------|
|
||||
| **确认** | 24 小时 | 确认我们收到了您的报告 |
|
||||
| **初步评估** | 72 小时 | 验证漏洞,评估严重程度 |
|
||||
| **修复开发** | 7-30 天 | 取决于复杂性和严重程度 |
|
||||
| **测试** | 3-7 天 | 验证修复不会破坏功能 |
|
||||
| **公开披露** | 修复部署后 | 发布安全公告 |
|
||||
|
||||
**严重漏洞**(资金盗窃、凭证泄露)会优先处理,可能在 48 小时内修复。
|
||||
|
||||
---
|
||||
|
||||
## 💰 安全奖励计划(可选)
|
||||
|
||||
我们为有效的安全漏洞提供奖励:
|
||||
|
||||
| 严重程度 | 标准 | 奖励 |
|
||||
|----------|----------|--------|
|
||||
| **🔴 严重** | 资金盗窃、API 密钥提取、RCE | **$500-1000 USD** |
|
||||
| **🟠 高** | 认证绕过、未授权交易 | **$200-500 USD** |
|
||||
| **🟡 中** | 信息泄露、XSS、CSRF | **$100-200 USD** |
|
||||
| **🟢 低** | 安全改进、小问题 | **$50-100 USD 或致谢** |
|
||||
|
||||
**注意:** 奖励金额由维护者根据以下因素酌情决定:
|
||||
- 严重性和影响
|
||||
- 报告质量
|
||||
- 利用难易度
|
||||
- 受影响用户数量
|
||||
|
||||
**不在范围内(无奖励):**
|
||||
- 第三方库的问题(直接向他们报告)
|
||||
- 社会工程攻击
|
||||
- DoS/DDoS 攻击
|
||||
- 需要物理访问的问题
|
||||
- 已知/已报告的漏洞
|
||||
|
||||
---
|
||||
|
||||
## 🔐 安全最佳实践(用户指南)
|
||||
|
||||
保护您的 NOFX 部署安全:
|
||||
|
||||
### 1. API 密钥管理
|
||||
```bash
|
||||
# ✅ 正确:使用环境变量
|
||||
export BINANCE_API_KEY="your_key"
|
||||
export BINANCE_SECRET_KEY="your_secret"
|
||||
|
||||
# ❌ 错误:在源文件中硬编码
|
||||
api_key = "abc123..." # 永远不要这样做
|
||||
```
|
||||
|
||||
### 2. 数据库安全
|
||||
```bash
|
||||
# ✅ 设置适当的权限
|
||||
chmod 600 nofx.db
|
||||
chmod 600 config.json
|
||||
|
||||
# ❌ 不要:让文件全局可读
|
||||
chmod 777 nofx.db # 永远不要这样做
|
||||
```
|
||||
|
||||
### 3. 网络安全
|
||||
```bash
|
||||
# ✅ 使用防火墙限制 API 访问
|
||||
# 仅允许本地访问 API 服务器
|
||||
iptables -A INPUT -p tcp --dport 8080 -s 127.0.0.1 -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 8080 -j DROP
|
||||
|
||||
# ❌ 不要:在没有身份验证的情况下将 API 暴露到公共互联网
|
||||
```
|
||||
|
||||
### 4. 使用子账户
|
||||
- 为交易创建专用的 Binance 子账户
|
||||
- 限制最大余额
|
||||
- 限制提现权限
|
||||
- 使用 IP 白名单
|
||||
|
||||
### 5. 先在测试网上测试
|
||||
- Hyperliquid:使用测试网模式
|
||||
- Binance:使用测试网 API (https://testnet.binancefuture.com)
|
||||
- 最初永远不要用真实资金测试
|
||||
|
||||
### 6. 定期更新
|
||||
```bash
|
||||
# 定期检查更新
|
||||
git pull origin main
|
||||
go build -o nofx
|
||||
|
||||
# 订阅安全公告
|
||||
# 关注 GitHub 发布:https://github.com/tinkle-community/nofx/releases
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 安全公告
|
||||
|
||||
过去的安全公告将在此发布:
|
||||
|
||||
### 2025-XX-XX: [标题]
|
||||
- **严重程度:** [严重/高/中/低]
|
||||
- **受影响版本:** [x.x.x - x.x.x]
|
||||
- **已修复版本:** [x.x.x]
|
||||
- **描述:** [简要描述]
|
||||
- **缓解措施:** [如何保护自己]
|
||||
|
||||
*尚未发布任何安全公告。*
|
||||
|
||||
---
|
||||
|
||||
## 🙏 安全研究员名人堂
|
||||
|
||||
我们感谢以下安全研究员负责任地披露漏洞:
|
||||
|
||||
*尚未收到任何报告。成为第一个!*
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
**仅限安全问题:**
|
||||
- 🐦 **Twitter 私信:** [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
|
||||
**一般问题:**
|
||||
- 加入 [Telegram 社区](https://t.me/nofx_dev_community)
|
||||
|
||||
---
|
||||
|
||||
**感谢您帮助保持 NOFX 的安全!** 🔒
|
||||
1102
api/server.go
121
auth/auth.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// JWTSecret JWT密钥,将从配置中动态设置
|
||||
var JWTSecret []byte
|
||||
|
||||
// AdminMode 管理员模式标志
|
||||
var AdminMode bool = false
|
||||
|
||||
// OTPIssuer OTP发行者名称
|
||||
const OTPIssuer = "nofxAI"
|
||||
|
||||
// SetJWTSecret 设置JWT密钥
|
||||
func SetJWTSecret(secret string) {
|
||||
JWTSecret = []byte(secret)
|
||||
}
|
||||
|
||||
// SetAdminMode 设置管理员模式
|
||||
func SetAdminMode(enabled bool) {
|
||||
AdminMode = enabled
|
||||
}
|
||||
|
||||
// IsAdminMode 检查是否为管理员模式
|
||||
func IsAdminMode() bool {
|
||||
return AdminMode
|
||||
}
|
||||
|
||||
// Claims JWT声明
|
||||
type Claims struct {
|
||||
UserID string `json:"user_id"`
|
||||
Email string `json:"email"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// HashPassword 哈希密码
|
||||
func HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
// CheckPassword 验证密码
|
||||
func CheckPassword(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// GenerateOTPSecret 生成OTP密钥
|
||||
func GenerateOTPSecret() (string, error) {
|
||||
secret := make([]byte, 20)
|
||||
_, err := rand.Read(secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: OTPIssuer,
|
||||
AccountName: uuid.New().String(),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return key.Secret(), nil
|
||||
}
|
||||
|
||||
// VerifyOTP 验证OTP码
|
||||
func VerifyOTP(secret, code string) bool {
|
||||
return totp.Validate(code, secret)
|
||||
}
|
||||
|
||||
// GenerateJWT 生成JWT token
|
||||
func GenerateJWT(userID, email string) (string, error) {
|
||||
claims := Claims{
|
||||
UserID: userID,
|
||||
Email: email,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 24小时过期
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: "nofxAI",
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString(JWTSecret)
|
||||
}
|
||||
|
||||
// ValidateJWT 验证JWT token
|
||||
func ValidateJWT(tokenString string) (*Claims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("意外的签名方法: %v", token.Header["alg"])
|
||||
}
|
||||
return JWTSecret, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("无效的token")
|
||||
}
|
||||
|
||||
// GetOTPQRCodeURL 获取OTP二维码URL
|
||||
func GetOTPQRCodeURL(secret, email string) string {
|
||||
return fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s", OTPIssuer, email, secret, OTPIssuer)
|
||||
}
|
||||
@@ -1,65 +1,5 @@
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "hyperliquid_deepseek",
|
||||
"name": "Hyperliquid DeepSeek Trader",
|
||||
"enabled": true,
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "hyperliquid",
|
||||
"hyperliquid_private_key": "your_ethereum_private_key_without_0x_prefix",
|
||||
"hyperliquid_wallet_addr": "your_ethereum_address",
|
||||
"hyperliquid_testnet": false,
|
||||
"deepseek_key": "your_deepseek_api_key",
|
||||
"initial_balance": 1000,
|
||||
"scan_interval_minutes": 3
|
||||
},
|
||||
{
|
||||
"id": "binance_qwen",
|
||||
"name": "Binance Qwen Trader",
|
||||
"enabled": true,
|
||||
"ai_model": "qwen",
|
||||
"exchange": "binance",
|
||||
"binance_api_key": "your_binance_api_key",
|
||||
"binance_secret_key": "your_binance_secret_key",
|
||||
"qwen_key": "your_qwen_api_key",
|
||||
"initial_balance": 1000,
|
||||
"scan_interval_minutes": 3
|
||||
},
|
||||
{
|
||||
"id": "binance_custom",
|
||||
"name": "Binance Custom API Trader",
|
||||
"enabled": false,
|
||||
"ai_model": "custom",
|
||||
"exchange": "binance",
|
||||
"binance_api_key": "your_binance_api_key",
|
||||
"binance_secret_key": "your_binance_secret_key",
|
||||
"custom_api_url": "https://api.openai.com/v1",
|
||||
"custom_api_key": "sk-your-api-key",
|
||||
"custom_model_name": "gpt-4o",
|
||||
"initial_balance": 1000,
|
||||
"scan_interval_minutes": 3
|
||||
},
|
||||
{
|
||||
"id": "aster_deepseek",
|
||||
"name": "Aster DeepSeek Trader",
|
||||
"enabled": true,
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "aster",
|
||||
|
||||
// 注意请仔细阅读这三个提示 请进入https://www.asterdex.com/en/api-wallet网站 -> 选择专业api -> 创建新api获取以下信息
|
||||
// user: 主钱包地址 (登录地址/连接到aster的钱包地址)
|
||||
// signer: API钱包地址 (点击生成地址后生成的地址)
|
||||
// privateKey: API钱包私钥 (生成地址对应的私钥)
|
||||
|
||||
"aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e",
|
||||
"aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0",
|
||||
"aster_private_key": "your_aster_api_wallet_private_key_without_0x_prefix",
|
||||
|
||||
"deepseek_key": "your_deepseek_api_key",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"admin_mode": true,
|
||||
"leverage": {
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 5
|
||||
@@ -75,10 +15,9 @@
|
||||
"ADAUSDT",
|
||||
"HYPEUSDT"
|
||||
],
|
||||
"coin_pool_api_url": "",
|
||||
"oi_top_api_url": "",
|
||||
"api_server_port": 8080,
|
||||
"max_daily_loss": 10.0,
|
||||
"max_drawdown": 20.0,
|
||||
"stop_trading_minutes": 60
|
||||
}
|
||||
"stop_trading_minutes": 60,
|
||||
"jwt_secret": "Qk0kAa+d0iIEzXVHXbNbm+UaN3RNabmWtH8rDWZ5OPf+4GX8pBflAHodfpbipVMyrw1fsDanHsNBjhgbDeK9Jg=="
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
type TraderConfig struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"` // 是否启用该trader
|
||||
Enabled bool `json:"enabled"` // 是否启用该trader
|
||||
AIModel string `json:"ai_model"` // "qwen" or "deepseek"
|
||||
|
||||
// 交易平台选择(二选一)
|
||||
@@ -55,8 +55,6 @@ type Config struct {
|
||||
Traders []TraderConfig `json:"traders"`
|
||||
UseDefaultCoins bool `json:"use_default_coins"` // 是否使用默认主流币种列表
|
||||
DefaultCoins []string `json:"default_coins"` // 默认主流币种池
|
||||
CoinPoolAPIURL string `json:"coin_pool_api_url"`
|
||||
OITopAPIURL string `json:"oi_top_api_url"`
|
||||
APIServerPort int `json:"api_server_port"`
|
||||
MaxDailyLoss float64 `json:"max_daily_loss"`
|
||||
MaxDrawdown float64 `json:"max_drawdown"`
|
||||
@@ -76,8 +74,8 @@ func LoadConfig(filename string) (*Config, error) {
|
||||
return nil, fmt.Errorf("解析配置文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置默认值:如果use_default_coins未设置(为false)且没有配置coin_pool_api_url,则默认使用默认币种列表
|
||||
if !config.UseDefaultCoins && config.CoinPoolAPIURL == "" {
|
||||
// 设置默认值:确保使用默认币种列表
|
||||
if !config.UseDefaultCoins {
|
||||
config.UseDefaultCoins = true
|
||||
}
|
||||
|
||||
|
||||
973
config/database.go
Normal file
@@ -0,0 +1,973 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"encoding/base32"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"nofx/market"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// Database 配置数据库
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewDatabase 创建配置数据库
|
||||
func NewDatabase(dbPath string) (*Database, error) {
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("打开数据库失败: %w", err)
|
||||
}
|
||||
|
||||
database := &Database{db: db}
|
||||
if err := database.createTables(); err != nil {
|
||||
return nil, fmt.Errorf("创建表失败: %w", err)
|
||||
}
|
||||
|
||||
if err := database.initDefaultData(); err != nil {
|
||||
return nil, fmt.Errorf("初始化默认数据失败: %w", err)
|
||||
}
|
||||
|
||||
return database, nil
|
||||
}
|
||||
|
||||
// createTables 创建数据库表
|
||||
func (d *Database) createTables() error {
|
||||
queries := []string{
|
||||
// AI模型配置表
|
||||
`CREATE TABLE IF NOT EXISTS ai_models (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL DEFAULT 'default',
|
||||
name TEXT NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT 0,
|
||||
api_key TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// 交易所配置表
|
||||
`CREATE TABLE IF NOT EXISTS exchanges (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL DEFAULT 'default',
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL, -- 'cex' or 'dex'
|
||||
enabled BOOLEAN DEFAULT 0,
|
||||
api_key TEXT DEFAULT '',
|
||||
secret_key TEXT DEFAULT '',
|
||||
testnet BOOLEAN DEFAULT 0,
|
||||
-- Hyperliquid 特定字段
|
||||
hyperliquid_wallet_addr TEXT DEFAULT '',
|
||||
-- Aster 特定字段
|
||||
aster_user TEXT DEFAULT '',
|
||||
aster_signer TEXT DEFAULT '',
|
||||
aster_private_key TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// 用户信号源配置表
|
||||
`CREATE TABLE IF NOT EXISTS user_signal_sources (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
coin_pool_url TEXT DEFAULT '',
|
||||
oi_top_url TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE(user_id)
|
||||
)`,
|
||||
|
||||
// 交易员配置表
|
||||
`CREATE TABLE IF NOT EXISTS traders (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL DEFAULT 'default',
|
||||
name TEXT NOT NULL,
|
||||
ai_model_id TEXT NOT NULL,
|
||||
exchange_id TEXT NOT NULL,
|
||||
initial_balance REAL NOT NULL,
|
||||
scan_interval_minutes INTEGER DEFAULT 3,
|
||||
is_running BOOLEAN DEFAULT 0,
|
||||
btc_eth_leverage INTEGER DEFAULT 5,
|
||||
altcoin_leverage INTEGER DEFAULT 5,
|
||||
trading_symbols TEXT DEFAULT '',
|
||||
use_coin_pool BOOLEAN DEFAULT 0,
|
||||
use_oi_top BOOLEAN DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (ai_model_id) REFERENCES ai_models(id),
|
||||
FOREIGN KEY (exchange_id) REFERENCES exchanges(id)
|
||||
)`,
|
||||
|
||||
// 用户表
|
||||
`CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
otp_secret TEXT,
|
||||
otp_verified BOOLEAN DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
|
||||
// 系统配置表
|
||||
`CREATE TABLE IF NOT EXISTS system_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
|
||||
// 触发器:自动更新 updated_at
|
||||
`CREATE TRIGGER IF NOT EXISTS update_users_updated_at
|
||||
AFTER UPDATE ON users
|
||||
BEGIN
|
||||
UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END`,
|
||||
|
||||
`CREATE TRIGGER IF NOT EXISTS update_ai_models_updated_at
|
||||
AFTER UPDATE ON ai_models
|
||||
BEGIN
|
||||
UPDATE ai_models SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END`,
|
||||
|
||||
`CREATE TRIGGER IF NOT EXISTS update_exchanges_updated_at
|
||||
AFTER UPDATE ON exchanges
|
||||
BEGIN
|
||||
UPDATE exchanges SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END`,
|
||||
|
||||
`CREATE TRIGGER IF NOT EXISTS update_traders_updated_at
|
||||
AFTER UPDATE ON traders
|
||||
BEGIN
|
||||
UPDATE traders SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END`,
|
||||
|
||||
`CREATE TRIGGER IF NOT EXISTS update_user_signal_sources_updated_at
|
||||
AFTER UPDATE ON user_signal_sources
|
||||
BEGIN
|
||||
UPDATE user_signal_sources SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END`,
|
||||
|
||||
`CREATE TRIGGER IF NOT EXISTS update_system_config_updated_at
|
||||
AFTER UPDATE ON system_config
|
||||
BEGIN
|
||||
UPDATE system_config SET updated_at = CURRENT_TIMESTAMP WHERE key = NEW.key;
|
||||
END`,
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
if _, err := d.db.Exec(query); err != nil {
|
||||
return fmt.Errorf("执行SQL失败 [%s]: %w", query, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 为现有数据库添加新字段(向后兼容)
|
||||
alterQueries := []string{
|
||||
`ALTER TABLE exchanges ADD COLUMN hyperliquid_wallet_addr TEXT DEFAULT ''`,
|
||||
`ALTER TABLE exchanges ADD COLUMN aster_user TEXT DEFAULT ''`,
|
||||
`ALTER TABLE exchanges ADD COLUMN aster_signer TEXT DEFAULT ''`,
|
||||
`ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`,
|
||||
`ALTER TABLE traders ADD COLUMN custom_prompt TEXT DEFAULT ''`,
|
||||
`ALTER TABLE traders ADD COLUMN override_base_prompt BOOLEAN DEFAULT 0`,
|
||||
`ALTER TABLE traders ADD COLUMN is_cross_margin BOOLEAN DEFAULT 1`, // 默认为全仓模式
|
||||
`ALTER TABLE traders ADD COLUMN use_default_coins BOOLEAN DEFAULT 1`, // 默认使用默认币种
|
||||
`ALTER TABLE traders ADD COLUMN custom_coins TEXT DEFAULT ''`, // 自定义币种列表(JSON格式)
|
||||
`ALTER TABLE traders ADD COLUMN btc_eth_leverage INTEGER DEFAULT 5`, // BTC/ETH杠杆倍数
|
||||
`ALTER TABLE traders ADD COLUMN altcoin_leverage INTEGER DEFAULT 5`, // 山寨币杠杆倍数
|
||||
`ALTER TABLE traders ADD COLUMN trading_symbols TEXT DEFAULT ''`, // 交易币种,逗号分隔
|
||||
`ALTER TABLE traders ADD COLUMN use_coin_pool BOOLEAN DEFAULT 0`, // 是否使用COIN POOL信号源
|
||||
`ALTER TABLE traders ADD COLUMN use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
|
||||
`ALTER TABLE traders ADD COLUMN use_inside_coins BOOLEAN DEFAULT 0`, // 是否使用内置AI评分信号源
|
||||
`ALTER TABLE traders ADD COLUMN system_prompt_template TEXT DEFAULT 'default'`, // 系统提示词模板名称
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_model_name TEXT DEFAULT ''`, // 自定义模型名称
|
||||
}
|
||||
|
||||
for _, query := range alterQueries {
|
||||
// 忽略已存在字段的错误
|
||||
d.db.Exec(query)
|
||||
}
|
||||
|
||||
// 检查是否需要迁移exchanges表的主键结构
|
||||
err := d.migrateExchangesTable()
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 迁移exchanges表失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initDefaultData 初始化默认数据
|
||||
func (d *Database) initDefaultData() error {
|
||||
// 初始化AI模型(使用default用户)
|
||||
aiModels := []struct {
|
||||
id, name, provider string
|
||||
}{
|
||||
{"deepseek", "DeepSeek", "deepseek"},
|
||||
{"qwen", "Qwen", "qwen"},
|
||||
}
|
||||
|
||||
for _, model := range aiModels {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO ai_models (id, user_id, name, provider, enabled)
|
||||
VALUES (?, 'default', ?, ?, 0)
|
||||
`, model.id, model.name, model.provider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化AI模型失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化交易所(使用default用户)
|
||||
exchanges := []struct {
|
||||
id, name, typ string
|
||||
}{
|
||||
{"binance", "Binance Futures", "binance"},
|
||||
{"hyperliquid", "Hyperliquid", "hyperliquid"},
|
||||
{"aster", "Aster DEX", "aster"},
|
||||
}
|
||||
|
||||
for _, exchange := range exchanges {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO exchanges (id, user_id, name, type, enabled)
|
||||
VALUES (?, 'default', ?, ?, 0)
|
||||
`, exchange.id, exchange.name, exchange.typ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化交易所失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化系统配置 - 创建所有字段,设置默认值,后续由config.json同步更新
|
||||
systemConfigs := map[string]string{
|
||||
"admin_mode": "true", // 默认开启管理员模式,便于首次使用
|
||||
"api_server_port": "8080", // 默认API端口
|
||||
"use_default_coins": "true", // 默认使用内置币种列表
|
||||
"default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式)
|
||||
"max_daily_loss": "10.0", // 最大日损失百分比
|
||||
"max_drawdown": "20.0", // 最大回撤百分比
|
||||
"stop_trading_minutes": "60", // 停止交易时间(分钟)
|
||||
"btc_eth_leverage": "5", // BTC/ETH杠杆倍数
|
||||
"altcoin_leverage": "5", // 山寨币杠杆倍数
|
||||
"jwt_secret": "", // JWT密钥,默认为空,由config.json或系统生成
|
||||
}
|
||||
|
||||
for key, value := range systemConfigs {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO system_config (key, value)
|
||||
VALUES (?, ?)
|
||||
`, key, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化系统配置失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateExchangesTable 迁移exchanges表支持多用户
|
||||
func (d *Database) migrateExchangesTable() error {
|
||||
// 检查是否已经迁移过
|
||||
var count int
|
||||
err := d.db.QueryRow(`
|
||||
SELECT COUNT(*) FROM sqlite_master
|
||||
WHERE type='table' AND name='exchanges_new'
|
||||
`).Scan(&count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果已经迁移过,直接返回
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("🔄 开始迁移exchanges表...")
|
||||
|
||||
// 创建新的exchanges表,使用复合主键
|
||||
_, err = d.db.Exec(`
|
||||
CREATE TABLE exchanges_new (
|
||||
id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL DEFAULT 'default',
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT 0,
|
||||
api_key TEXT DEFAULT '',
|
||||
secret_key TEXT DEFAULT '',
|
||||
testnet BOOLEAN DEFAULT 0,
|
||||
hyperliquid_wallet_addr TEXT DEFAULT '',
|
||||
aster_user TEXT DEFAULT '',
|
||||
aster_signer TEXT DEFAULT '',
|
||||
aster_private_key TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id, user_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建新exchanges表失败: %w", err)
|
||||
}
|
||||
|
||||
// 复制数据到新表
|
||||
_, err = d.db.Exec(`
|
||||
INSERT INTO exchanges_new
|
||||
SELECT * FROM exchanges
|
||||
`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("复制数据失败: %w", err)
|
||||
}
|
||||
|
||||
// 删除旧表
|
||||
_, err = d.db.Exec(`DROP TABLE exchanges`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除旧表失败: %w", err)
|
||||
}
|
||||
|
||||
// 重命名新表
|
||||
_, err = d.db.Exec(`ALTER TABLE exchanges_new RENAME TO exchanges`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("重命名表失败: %w", err)
|
||||
}
|
||||
|
||||
// 重新创建触发器
|
||||
_, err = d.db.Exec(`
|
||||
CREATE TRIGGER IF NOT EXISTS update_exchanges_updated_at
|
||||
AFTER UPDATE ON exchanges
|
||||
BEGIN
|
||||
UPDATE exchanges SET updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = NEW.id AND user_id = NEW.user_id;
|
||||
END
|
||||
`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建触发器失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("✅ exchanges表迁移完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// User 用户配置
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
PasswordHash string `json:"-"` // 不返回到前端
|
||||
OTPSecret string `json:"-"` // 不返回到前端
|
||||
OTPVerified bool `json:"otp_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AIModelConfig AI模型配置
|
||||
type AIModelConfig struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"apiKey"`
|
||||
CustomAPIURL string `json:"customApiUrl"`
|
||||
CustomModelName string `json:"customModelName"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ExchangeConfig 交易所配置
|
||||
type ExchangeConfig struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"apiKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Testnet bool `json:"testnet"`
|
||||
// Hyperliquid 特定字段
|
||||
HyperliquidWalletAddr string `json:"hyperliquidWalletAddr"`
|
||||
// Aster 特定字段
|
||||
AsterUser string `json:"asterUser"`
|
||||
AsterSigner string `json:"asterSigner"`
|
||||
AsterPrivateKey string `json:"asterPrivateKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TraderRecord 交易员配置(数据库实体)
|
||||
type TraderRecord struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
AIModelID string `json:"ai_model_id"`
|
||||
ExchangeID string `json:"exchange_id"`
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||
IsRunning bool `json:"is_running"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC/ETH杠杆倍数
|
||||
AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币杠杆倍数
|
||||
TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
|
||||
UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
|
||||
UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
|
||||
UseInsideCoins bool `json:"use_inside_coins"` // 是否使用内置评分信号源
|
||||
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
|
||||
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
|
||||
SystemPromptTemplate string `json:"system_prompt_template"` // 系统提示词模板名称
|
||||
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式(true=全仓,false=逐仓)
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// UserSignalSource 用户信号源配置
|
||||
type UserSignalSource struct {
|
||||
ID int `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
CoinPoolURL string `json:"coin_pool_url"`
|
||||
OITopURL string `json:"oi_top_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// GenerateOTPSecret 生成OTP密钥
|
||||
func GenerateOTPSecret() (string, error) {
|
||||
secret := make([]byte, 20)
|
||||
_, err := rand.Read(secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base32.StdEncoding.EncodeToString(secret), nil
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
func (d *Database) CreateUser(user *User) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT INTO users (id, email, password_hash, otp_secret, otp_verified)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, user.ID, user.Email, user.PasswordHash, user.OTPSecret, user.OTPVerified)
|
||||
return err
|
||||
}
|
||||
|
||||
// EnsureAdminUser 确保admin用户存在(用于管理员模式)
|
||||
func (d *Database) EnsureAdminUser() error {
|
||||
// 检查admin用户是否已存在
|
||||
var count int
|
||||
err := d.db.QueryRow(`SELECT COUNT(*) FROM users WHERE id = 'admin'`).Scan(&count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果已存在,直接返回
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建admin用户(密码为空,因为管理员模式下不需要密码)
|
||||
adminUser := &User{
|
||||
ID: "admin",
|
||||
Email: "admin@localhost",
|
||||
PasswordHash: "", // 管理员模式下不使用密码
|
||||
OTPSecret: "",
|
||||
OTPVerified: true,
|
||||
}
|
||||
|
||||
return d.CreateUser(adminUser)
|
||||
}
|
||||
|
||||
// GetUserByEmail 通过邮箱获取用户
|
||||
func (d *Database) GetUserByEmail(email string) (*User, error) {
|
||||
var user User
|
||||
err := d.db.QueryRow(`
|
||||
SELECT id, email, password_hash, otp_secret, otp_verified, created_at, updated_at
|
||||
FROM users WHERE email = ?
|
||||
`, email).Scan(
|
||||
&user.ID, &user.Email, &user.PasswordHash, &user.OTPSecret,
|
||||
&user.OTPVerified, &user.CreatedAt, &user.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserByID 通过ID获取用户
|
||||
func (d *Database) GetUserByID(userID string) (*User, error) {
|
||||
var user User
|
||||
err := d.db.QueryRow(`
|
||||
SELECT id, email, password_hash, otp_secret, otp_verified, created_at, updated_at
|
||||
FROM users WHERE id = ?
|
||||
`, userID).Scan(
|
||||
&user.ID, &user.Email, &user.PasswordHash, &user.OTPSecret,
|
||||
&user.OTPVerified, &user.CreatedAt, &user.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetAllUsers 获取所有用户ID列表
|
||||
func (d *Database) GetAllUsers() ([]string, error) {
|
||||
rows, err := d.db.Query(`SELECT id FROM users ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var userIDs []string
|
||||
for rows.Next() {
|
||||
var userID string
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
// UpdateUserOTPVerified 更新用户OTP验证状态
|
||||
func (d *Database) UpdateUserOTPVerified(userID string, verified bool) error {
|
||||
_, err := d.db.Exec(`UPDATE users SET otp_verified = ? WHERE id = ?`, verified, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAIModels 获取用户的AI模型配置
|
||||
func (d *Database) GetAIModels(userID string) ([]*AIModelConfig, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, user_id, name, provider, enabled, api_key,
|
||||
COALESCE(custom_api_url, '') as custom_api_url,
|
||||
COALESCE(custom_model_name, '') as custom_model_name,
|
||||
created_at, updated_at
|
||||
FROM ai_models WHERE user_id = ? ORDER BY id
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// 初始化为空切片而不是nil,确保JSON序列化为[]而不是null
|
||||
models := make([]*AIModelConfig, 0)
|
||||
for rows.Next() {
|
||||
var model AIModelConfig
|
||||
err := rows.Scan(
|
||||
&model.ID, &model.UserID, &model.Name, &model.Provider,
|
||||
&model.Enabled, &model.APIKey, &model.CustomAPIURL, &model.CustomModelName,
|
||||
&model.CreatedAt, &model.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
models = append(models, &model)
|
||||
}
|
||||
|
||||
return models, nil
|
||||
}
|
||||
|
||||
// UpdateAIModel 更新AI模型配置,如果不存在则创建用户特定配置
|
||||
func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error {
|
||||
// 先尝试精确匹配 ID(新版逻辑,支持多个相同 provider 的模型)
|
||||
var existingID string
|
||||
err := d.db.QueryRow(`
|
||||
SELECT id FROM ai_models WHERE user_id = ? AND id = ? LIMIT 1
|
||||
`, userID, id).Scan(&existingID)
|
||||
|
||||
if err == nil {
|
||||
// 找到了现有配置(精确匹配 ID),更新它
|
||||
_, err = d.db.Exec(`
|
||||
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// ID 不存在,尝试兼容旧逻辑:将 id 作为 provider 查找
|
||||
provider := id
|
||||
err = d.db.QueryRow(`
|
||||
SELECT id FROM ai_models WHERE user_id = ? AND provider = ? LIMIT 1
|
||||
`, userID, provider).Scan(&existingID)
|
||||
|
||||
if err == nil {
|
||||
// 找到了现有配置(通过 provider 匹配,兼容旧版),更新它
|
||||
log.Printf("⚠️ 使用旧版 provider 匹配更新模型: %s -> %s", provider, existingID)
|
||||
_, err = d.db.Exec(`
|
||||
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// 没有找到任何现有配置,创建新的
|
||||
// 推断 provider(从 id 中提取,或者直接使用 id)
|
||||
if provider == id && (provider == "deepseek" || provider == "qwen") {
|
||||
// id 本身就是 provider
|
||||
provider = id
|
||||
} else {
|
||||
// 从 id 中提取 provider(假设格式是 userID_provider 或 timestamp_userID_provider)
|
||||
parts := strings.Split(id, "_")
|
||||
if len(parts) >= 2 {
|
||||
provider = parts[len(parts)-1] // 取最后一部分作为 provider
|
||||
} else {
|
||||
provider = id
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模型的基本信息
|
||||
var name string
|
||||
err = d.db.QueryRow(`
|
||||
SELECT name FROM ai_models WHERE provider = ? LIMIT 1
|
||||
`, provider).Scan(&name)
|
||||
if err != nil {
|
||||
// 如果找不到基本信息,使用默认值
|
||||
if provider == "deepseek" {
|
||||
name = "DeepSeek AI"
|
||||
} else if provider == "qwen" {
|
||||
name = "Qwen AI"
|
||||
} else {
|
||||
name = provider + " AI"
|
||||
}
|
||||
}
|
||||
|
||||
// 如果传入的 ID 已经是完整格式(如 "admin_deepseek_custom1"),直接使用
|
||||
// 否则生成新的 ID
|
||||
newModelID := id
|
||||
if id == provider {
|
||||
// id 就是 provider,生成新的用户特定 ID
|
||||
newModelID = fmt.Sprintf("%s_%s", userID, provider)
|
||||
}
|
||||
|
||||
log.Printf("✓ 创建新的 AI 模型配置: ID=%s, Provider=%s, Name=%s", newModelID, provider, name)
|
||||
_, err = d.db.Exec(`
|
||||
INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
`, newModelID, userID, name, provider, enabled, apiKey, customAPIURL, customModelName)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetExchanges 获取用户的交易所配置
|
||||
func (d *Database) GetExchanges(userID string) ([]*ExchangeConfig, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, user_id, name, type, enabled, api_key, secret_key, testnet,
|
||||
COALESCE(hyperliquid_wallet_addr, '') as hyperliquid_wallet_addr,
|
||||
COALESCE(aster_user, '') as aster_user,
|
||||
COALESCE(aster_signer, '') as aster_signer,
|
||||
COALESCE(aster_private_key, '') as aster_private_key,
|
||||
created_at, updated_at
|
||||
FROM exchanges WHERE user_id = ? ORDER BY id
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// 初始化为空切片而不是nil,确保JSON序列化为[]而不是null
|
||||
exchanges := make([]*ExchangeConfig, 0)
|
||||
for rows.Next() {
|
||||
var exchange ExchangeConfig
|
||||
err := rows.Scan(
|
||||
&exchange.ID, &exchange.UserID, &exchange.Name, &exchange.Type,
|
||||
&exchange.Enabled, &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
|
||||
&exchange.HyperliquidWalletAddr, &exchange.AsterUser,
|
||||
&exchange.AsterSigner, &exchange.AsterPrivateKey,
|
||||
&exchange.CreatedAt, &exchange.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exchanges = append(exchanges, &exchange)
|
||||
}
|
||||
|
||||
return exchanges, nil
|
||||
}
|
||||
|
||||
// UpdateExchange 更新交易所配置,如果不存在则创建用户特定配置
|
||||
func (d *Database) UpdateExchange(userID, id string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error {
|
||||
log.Printf("🔧 UpdateExchange: userID=%s, id=%s, enabled=%v", userID, id, enabled)
|
||||
|
||||
// 首先尝试更新现有的用户配置
|
||||
result, err := d.db.Exec(`
|
||||
UPDATE exchanges SET enabled = ?, api_key = ?, secret_key = ?, testnet = ?,
|
||||
hyperliquid_wallet_addr = ?, aster_user = ?, aster_signer = ?, aster_private_key = ?, updated_at = datetime('now')
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, id, userID)
|
||||
if err != nil {
|
||||
log.Printf("❌ UpdateExchange: 更新失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查是否有行被更新
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
log.Printf("❌ UpdateExchange: 获取影响行数失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("📊 UpdateExchange: 影响行数 = %d", rowsAffected)
|
||||
|
||||
// 如果没有行被更新,说明用户没有这个交易所的配置,需要创建
|
||||
if rowsAffected == 0 {
|
||||
log.Printf("💡 UpdateExchange: 没有现有记录,创建新记录")
|
||||
|
||||
// 根据交易所ID确定基本信息
|
||||
var name, typ string
|
||||
if id == "binance" {
|
||||
name = "Binance Futures"
|
||||
typ = "cex"
|
||||
} else if id == "hyperliquid" {
|
||||
name = "Hyperliquid"
|
||||
typ = "dex"
|
||||
} else if id == "aster" {
|
||||
name = "Aster DEX"
|
||||
typ = "dex"
|
||||
} else {
|
||||
name = id + " Exchange"
|
||||
typ = "cex"
|
||||
}
|
||||
|
||||
log.Printf("🆕 UpdateExchange: 创建新记录 ID=%s, name=%s, type=%s", id, name, typ)
|
||||
|
||||
// 创建用户特定的配置,使用原始的交易所ID
|
||||
_, err = d.db.Exec(`
|
||||
INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet,
|
||||
hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
`, id, userID, name, typ, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("❌ UpdateExchange: 创建记录失败: %v", err)
|
||||
} else {
|
||||
log.Printf("✅ UpdateExchange: 创建记录成功")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("✅ UpdateExchange: 更新现有记录成功")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAIModel 创建AI模型配置
|
||||
func (d *Database) CreateAIModel(userID, id, name, provider string, enabled bool, apiKey, customAPIURL string) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`, id, userID, name, provider, enabled, apiKey, customAPIURL)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateExchange 创建交易所配置
|
||||
func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, id, userID, name, typ, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateTrader 创建交易员
|
||||
func (d *Database) CreateTrader(trader *TraderRecord) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, use_inside_coins, custom_prompt, override_base_prompt, system_prompt_template, is_cross_margin)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?)
|
||||
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.BTCETHLeverage, trader.AltcoinLeverage, trader.TradingSymbols, trader.UseCoinPool, trader.UseOITop, trader.UseInsideCoins, trader.CustomPrompt, trader.OverrideBasePrompt, trader.SystemPromptTemplate, trader.IsCrossMargin)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTraders 获取用户的交易员
|
||||
func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running,
|
||||
COALESCE(btc_eth_leverage, 5) as btc_eth_leverage, COALESCE(altcoin_leverage, 5) as altcoin_leverage,
|
||||
COALESCE(trading_symbols, '') as trading_symbols,
|
||||
COALESCE(use_coin_pool, 0) as use_coin_pool, COALESCE(use_oi_top, 0) as use_oi_top,COALESCE(use_inside_coins, 0) as use_inside_coins,
|
||||
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt,
|
||||
COALESCE(system_prompt_template, 'default') as system_prompt_template,
|
||||
COALESCE(is_cross_margin, 1) as is_cross_margin, created_at, updated_at
|
||||
FROM traders WHERE user_id = ? ORDER BY created_at DESC
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var traders []*TraderRecord
|
||||
for rows.Next() {
|
||||
var trader TraderRecord
|
||||
err := rows.Scan(
|
||||
&trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
|
||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||
&trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols,
|
||||
&trader.UseCoinPool, &trader.UseOITop, &trader.UseInsideCoins,
|
||||
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.SystemPromptTemplate,
|
||||
&trader.IsCrossMargin,
|
||||
&trader.CreatedAt, &trader.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
traders = append(traders, &trader)
|
||||
}
|
||||
|
||||
return traders, nil
|
||||
}
|
||||
|
||||
// UpdateTraderStatus 更新交易员状态
|
||||
func (d *Database) UpdateTraderStatus(userID, id string, isRunning bool) error {
|
||||
_, err := d.db.Exec(`UPDATE traders SET is_running = ? WHERE id = ? AND user_id = ?`, isRunning, id, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateTrader 更新交易员配置
|
||||
func (d *Database) UpdateTrader(trader *TraderRecord) error {
|
||||
_, err := d.db.Exec(`
|
||||
UPDATE traders SET
|
||||
name = ?, ai_model_id = ?, exchange_id = ?, initial_balance = ?,
|
||||
scan_interval_minutes = ?, btc_eth_leverage = ?, altcoin_leverage = ?,
|
||||
trading_symbols = ?, custom_prompt = ?, override_base_prompt = ?,
|
||||
system_prompt_template = ?, is_cross_margin = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance,
|
||||
trader.ScanIntervalMinutes, trader.BTCETHLeverage, trader.AltcoinLeverage,
|
||||
trader.TradingSymbols, trader.CustomPrompt, trader.OverrideBasePrompt,
|
||||
trader.SystemPromptTemplate, trader.IsCrossMargin, trader.ID, trader.UserID)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateTraderCustomPrompt 更新交易员自定义Prompt
|
||||
func (d *Database) UpdateTraderCustomPrompt(userID, id string, customPrompt string, overrideBase bool) error {
|
||||
_, err := d.db.Exec(`UPDATE traders SET custom_prompt = ?, override_base_prompt = ? WHERE id = ? AND user_id = ?`, customPrompt, overrideBase, id, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteTrader 删除交易员
|
||||
func (d *Database) DeleteTrader(userID, id string) error {
|
||||
_, err := d.db.Exec(`DELETE FROM traders WHERE id = ? AND user_id = ?`, id, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTraderConfig 获取交易员完整配置(包含AI模型和交易所信息)
|
||||
func (d *Database) GetTraderConfig(userID, traderID string) (*TraderRecord, *AIModelConfig, *ExchangeConfig, error) {
|
||||
var trader TraderRecord
|
||||
var aiModel AIModelConfig
|
||||
var exchange ExchangeConfig
|
||||
|
||||
err := d.db.QueryRow(`
|
||||
SELECT
|
||||
t.id, t.user_id, t.name, t.ai_model_id, t.exchange_id, t.initial_balance, t.scan_interval_minutes, t.is_running, t.created_at, t.updated_at,
|
||||
a.id, a.user_id, a.name, a.provider, a.enabled, a.api_key, a.created_at, a.updated_at,
|
||||
e.id, e.user_id, e.name, e.type, e.enabled, e.api_key, e.secret_key, e.testnet,
|
||||
COALESCE(e.hyperliquid_wallet_addr, '') as hyperliquid_wallet_addr,
|
||||
COALESCE(e.aster_user, '') as aster_user,
|
||||
COALESCE(e.aster_signer, '') as aster_signer,
|
||||
COALESCE(e.aster_private_key, '') as aster_private_key,
|
||||
e.created_at, e.updated_at
|
||||
FROM traders t
|
||||
JOIN ai_models a ON t.ai_model_id = a.id AND t.user_id = a.user_id
|
||||
JOIN exchanges e ON t.exchange_id = e.id AND t.user_id = e.user_id
|
||||
WHERE t.id = ? AND t.user_id = ?
|
||||
`, traderID, userID).Scan(
|
||||
&trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
|
||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||
&trader.CreatedAt, &trader.UpdatedAt,
|
||||
&aiModel.ID, &aiModel.UserID, &aiModel.Name, &aiModel.Provider, &aiModel.Enabled, &aiModel.APIKey,
|
||||
&aiModel.CreatedAt, &aiModel.UpdatedAt,
|
||||
&exchange.ID, &exchange.UserID, &exchange.Name, &exchange.Type, &exchange.Enabled,
|
||||
&exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
|
||||
&exchange.HyperliquidWalletAddr, &exchange.AsterUser, &exchange.AsterSigner, &exchange.AsterPrivateKey,
|
||||
&exchange.CreatedAt, &exchange.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return &trader, &aiModel, &exchange, nil
|
||||
}
|
||||
|
||||
// GetSystemConfig 获取系统配置
|
||||
func (d *Database) GetSystemConfig(key string) (string, error) {
|
||||
var value string
|
||||
err := d.db.QueryRow(`SELECT value FROM system_config WHERE key = ?`, key).Scan(&value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
// SetSystemConfig 设置系统配置
|
||||
func (d *Database) SetSystemConfig(key, value string) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR REPLACE INTO system_config (key, value) VALUES (?, ?)
|
||||
`, key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateUserSignalSource 创建用户信号源配置
|
||||
func (d *Database) CreateUserSignalSource(userID, coinPoolURL, oiTopURL string) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR REPLACE INTO user_signal_sources (user_id, coin_pool_url, oi_top_url, updated_at)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||
`, userID, coinPoolURL, oiTopURL)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetUserSignalSource 获取用户信号源配置
|
||||
func (d *Database) GetUserSignalSource(userID string) (*UserSignalSource, error) {
|
||||
var source UserSignalSource
|
||||
err := d.db.QueryRow(`
|
||||
SELECT id, user_id, coin_pool_url, oi_top_url, created_at, updated_at
|
||||
FROM user_signal_sources WHERE user_id = ?
|
||||
`, userID).Scan(
|
||||
&source.ID, &source.UserID, &source.CoinPoolURL, &source.OITopURL,
|
||||
&source.CreatedAt, &source.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &source, nil
|
||||
}
|
||||
|
||||
// UpdateUserSignalSource 更新用户信号源配置
|
||||
func (d *Database) UpdateUserSignalSource(userID, coinPoolURL, oiTopURL string) error {
|
||||
_, err := d.db.Exec(`
|
||||
UPDATE user_signal_sources SET coin_pool_url = ?, oi_top_url = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE user_id = ?
|
||||
`, coinPoolURL, oiTopURL, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetCustomCoins 获取所有交易员自定义币种 / Get all trader-customized currencies
|
||||
func (d *Database) GetCustomCoins() []string {
|
||||
var symbol string
|
||||
var symbols []string
|
||||
_ = d.db.QueryRow(`
|
||||
SELECT GROUP_CONCAT(custom_coins , ',') as symbol
|
||||
FROM main.traders where custom_coins != ''
|
||||
`).Scan(&symbol)
|
||||
// 检测用户是否未配置币种 - 兼容性
|
||||
if symbol == "" {
|
||||
symbolJSON, _ := d.GetSystemConfig("default_coins")
|
||||
if err := json.Unmarshal([]byte(symbolJSON), &symbols); err != nil {
|
||||
log.Printf("⚠️ 解析default_coins配置失败: %v,使用硬编码默认值", err)
|
||||
symbols = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT"}
|
||||
}
|
||||
}
|
||||
// filter Symbol
|
||||
for _, s := range strings.Split(symbol, ",") {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
coin := market.Normalize(s)
|
||||
if !slices.Contains(symbols, coin) {
|
||||
symbols = append(symbols, coin)
|
||||
}
|
||||
}
|
||||
return symbols
|
||||
}
|
||||
|
||||
// Close 关闭数据库连接
|
||||
func (d *Database) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
||||
@@ -83,21 +83,27 @@ type Decision struct {
|
||||
|
||||
// FullDecision AI的完整决策(包含思维链)
|
||||
type FullDecision struct {
|
||||
UserPrompt string `json:"user_prompt"` // 发送给AI的输入prompt
|
||||
CoTTrace string `json:"cot_trace"` // 思维链分析(AI输出)
|
||||
Decisions []Decision `json:"decisions"` // 具体决策列表
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
SystemPrompt string `json:"system_prompt"` // 系统提示词(发送给AI的系统prompt)
|
||||
UserPrompt string `json:"user_prompt"` // 发送给AI的输入prompt
|
||||
CoTTrace string `json:"cot_trace"` // 思维链分析(AI输出)
|
||||
Decisions []Decision `json:"decisions"` // 具体决策列表
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// GetFullDecision 获取AI的完整交易决策(批量分析所有币种和持仓)
|
||||
func GetFullDecision(ctx *Context, mcpClient *mcp.Client) (*FullDecision, error) {
|
||||
return GetFullDecisionWithCustomPrompt(ctx, mcpClient, "", false, "")
|
||||
}
|
||||
|
||||
// GetFullDecisionWithCustomPrompt 获取AI的完整交易决策(支持自定义prompt和模板选择)
|
||||
func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, customPrompt string, overrideBase bool, templateName string) (*FullDecision, error) {
|
||||
// 1. 为所有币种获取市场数据
|
||||
if err := fetchMarketDataForContext(ctx); err != nil {
|
||||
return nil, fmt.Errorf("获取市场数据失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 构建 System Prompt(固定规则)和 User Prompt(动态数据)
|
||||
systemPrompt := buildSystemPrompt(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage)
|
||||
systemPrompt := buildSystemPromptWithCustom(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage, customPrompt, overrideBase, templateName)
|
||||
userPrompt := buildUserPrompt(ctx)
|
||||
|
||||
// 3. 调用AI API(使用 system + user prompt)
|
||||
@@ -109,11 +115,12 @@ func GetFullDecision(ctx *Context, mcpClient *mcp.Client) (*FullDecision, error)
|
||||
// 4. 解析AI响应
|
||||
decision, err := parseFullDecisionResponse(aiResponse, ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析AI响应失败: %w", err)
|
||||
return decision, fmt.Errorf("解析AI响应失败: %w", err)
|
||||
}
|
||||
|
||||
decision.Timestamp = time.Now()
|
||||
decision.UserPrompt = userPrompt // 保存输入prompt
|
||||
decision.SystemPrompt = systemPrompt // 保存系统prompt
|
||||
decision.UserPrompt = userPrompt // 保存输入prompt
|
||||
return decision, nil
|
||||
}
|
||||
|
||||
@@ -199,119 +206,82 @@ func calculateMaxCandidates(ctx *Context) int {
|
||||
return len(ctx.CandidateCoins)
|
||||
}
|
||||
|
||||
// buildSystemPrompt 构建 System Prompt(固定规则,可缓存)
|
||||
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int) string {
|
||||
// buildSystemPromptWithCustom 构建包含自定义内容的 System Prompt
|
||||
func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinLeverage int, customPrompt string, overrideBase bool, templateName string) string {
|
||||
// 如果覆盖基础prompt且有自定义prompt,只使用自定义prompt
|
||||
if overrideBase && customPrompt != "" {
|
||||
return customPrompt
|
||||
}
|
||||
|
||||
// 获取基础prompt(使用指定的模板)
|
||||
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage, templateName)
|
||||
|
||||
// 如果没有自定义prompt,直接返回基础prompt
|
||||
if customPrompt == "" {
|
||||
return basePrompt
|
||||
}
|
||||
|
||||
// 添加自定义prompt部分到基础prompt
|
||||
var sb strings.Builder
|
||||
sb.WriteString(basePrompt)
|
||||
sb.WriteString("\n\n")
|
||||
sb.WriteString("# 📌 个性化交易策略\n\n")
|
||||
sb.WriteString(customPrompt)
|
||||
sb.WriteString("\n\n")
|
||||
sb.WriteString("注意: 以上个性化策略是对基础规则的补充,不能违背基础风险控制原则。\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// buildSystemPrompt 构建 System Prompt(使用模板+动态部分)
|
||||
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int, templateName string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// === 核心使命 ===
|
||||
sb.WriteString("你是专业的加密货币交易AI,在币安合约市场进行自主交易。\n\n")
|
||||
sb.WriteString("# 🎯 核心目标\n\n")
|
||||
sb.WriteString("**最大化夏普比率(Sharpe Ratio)**\n\n")
|
||||
sb.WriteString("夏普比率 = 平均收益 / 收益波动率\n\n")
|
||||
sb.WriteString("**这意味着**:\n")
|
||||
sb.WriteString("- ✅ 高质量交易(高胜率、大盈亏比)→ 提升夏普\n")
|
||||
sb.WriteString("- ✅ 稳定收益、控制回撤 → 提升夏普\n")
|
||||
sb.WriteString("- ✅ 耐心持仓、让利润奔跑 → 提升夏普\n")
|
||||
sb.WriteString("- ❌ 频繁交易、小盈小亏 → 增加波动,严重降低夏普\n")
|
||||
sb.WriteString("- ❌ 过度交易、手续费损耗 → 直接亏损\n")
|
||||
sb.WriteString("- ❌ 过早平仓、频繁进出 → 错失大行情\n\n")
|
||||
sb.WriteString("**关键认知**: 系统每3分钟扫描一次,但不意味着每次都要交易!\n")
|
||||
sb.WriteString("大多数时候应该是 `wait` 或 `hold`,只在极佳机会时才开仓。\n\n")
|
||||
// 1. 加载提示词模板(核心交易策略部分)
|
||||
if templateName == "" {
|
||||
templateName = "default" // 默认使用 default 模板
|
||||
}
|
||||
|
||||
// === 硬约束(风险控制)===
|
||||
sb.WriteString("# ⚖️ 硬约束(风险控制)\n\n")
|
||||
sb.WriteString("1. **风险回报比**: 必须 ≥ 1:3(冒1%风险,赚3%+收益)\n")
|
||||
sb.WriteString("2. **最多持仓**: 3个币种(质量>数量)\n")
|
||||
sb.WriteString(fmt.Sprintf("3. **单币仓位**: 山寨%.0f-%.0f U(%dx杠杆) | BTC/ETH %.0f-%.0f U(%dx杠杆)\n",
|
||||
template, err := GetPromptTemplate(templateName)
|
||||
if err != nil {
|
||||
// 如果模板不存在,记录错误并使用 default
|
||||
log.Printf("⚠️ 提示词模板 '%s' 不存在,使用 default: %v", templateName, err)
|
||||
template, err = GetPromptTemplate("default")
|
||||
if err != nil {
|
||||
// 如果连 default 都不存在,使用内置的简化版本
|
||||
log.Printf("❌ 无法加载任何提示词模板,使用内置简化版本")
|
||||
sb.WriteString("你是专业的加密货币交易AI。请根据市场数据做出交易决策。\n\n")
|
||||
} else {
|
||||
sb.WriteString(template.Content)
|
||||
sb.WriteString("\n\n")
|
||||
}
|
||||
} else {
|
||||
sb.WriteString(template.Content)
|
||||
sb.WriteString("\n\n")
|
||||
}
|
||||
|
||||
// 2. 硬约束(风险控制)- 动态生成
|
||||
sb.WriteString("# 硬约束(风险控制)\n\n")
|
||||
sb.WriteString("1. 风险回报比: 必须 ≥ 1:3(冒1%风险,赚3%+收益)\n")
|
||||
sb.WriteString("2. 最多持仓: 3个币种(质量>数量)\n")
|
||||
sb.WriteString(fmt.Sprintf("3. 单币仓位: 山寨%.0f-%.0f U(%dx杠杆) | BTC/ETH %.0f-%.0f U(%dx杠杆)\n",
|
||||
accountEquity*0.8, accountEquity*1.5, altcoinLeverage, accountEquity*5, accountEquity*10, btcEthLeverage))
|
||||
sb.WriteString("4. **保证金**: 总使用率 ≤ 90%\n\n")
|
||||
sb.WriteString("4. 保证金: 总使用率 ≤ 90%\n\n")
|
||||
|
||||
// === 做空激励 ===
|
||||
sb.WriteString("# 📉 做多做空平衡\n\n")
|
||||
sb.WriteString("**重要**: 下跌趋势做空的利润 = 上涨趋势做多的利润\n\n")
|
||||
sb.WriteString("- 上涨趋势 → 做多\n")
|
||||
sb.WriteString("- 下跌趋势 → 做空\n")
|
||||
sb.WriteString("- 震荡市场 → 观望\n\n")
|
||||
sb.WriteString("**不要有做多偏见!做空是你的核心工具之一**\n\n")
|
||||
|
||||
// === 交易频率认知 ===
|
||||
sb.WriteString("# ⏱️ 交易频率认知\n\n")
|
||||
sb.WriteString("**量化标准**:\n")
|
||||
sb.WriteString("- 优秀交易员:每天2-4笔 = 每小时0.1-0.2笔\n")
|
||||
sb.WriteString("- 过度交易:每小时>2笔 = 严重问题\n")
|
||||
sb.WriteString("- 最佳节奏:开仓后持有至少30-60分钟\n\n")
|
||||
sb.WriteString("**自查**:\n")
|
||||
sb.WriteString("如果你发现自己每个周期都在交易 → 说明标准太低\n")
|
||||
sb.WriteString("如果你发现持仓<30分钟就平仓 → 说明太急躁\n\n")
|
||||
|
||||
// === 开仓信号强度 ===
|
||||
sb.WriteString("# 🎯 开仓标准(严格)\n\n")
|
||||
sb.WriteString("只在**强信号**时开仓,不确定就观望。\n\n")
|
||||
sb.WriteString("**你拥有的完整数据**:\n")
|
||||
sb.WriteString("- 📊 **原始序列**:3分钟价格序列(MidPrices数组) + 4小时K线序列\n")
|
||||
sb.WriteString("- 📈 **技术序列**:EMA20序列、MACD序列、RSI7序列、RSI14序列\n")
|
||||
sb.WriteString("- 💰 **资金序列**:成交量序列、持仓量(OI)序列、资金费率\n")
|
||||
sb.WriteString("- 🎯 **筛选标记**:AI500评分 / OI_Top排名(如果有标注)\n\n")
|
||||
sb.WriteString("**分析方法**(完全由你自主决定):\n")
|
||||
sb.WriteString("- 自由运用序列数据,你可以做但不限于趋势分析、形态识别、支撑阻力、技术阻力位、斐波那契、波动带计算\n")
|
||||
sb.WriteString("- 多维度交叉验证(价格+量+OI+指标+序列形态)\n")
|
||||
sb.WriteString("- 用你认为最有效的方法发现高确定性机会\n")
|
||||
sb.WriteString("- 综合信心度 ≥ 75 才开仓\n\n")
|
||||
sb.WriteString("**避免低质量信号**:\n")
|
||||
sb.WriteString("- 单一维度(只看一个指标)\n")
|
||||
sb.WriteString("- 相互矛盾(涨但量萎缩)\n")
|
||||
sb.WriteString("- 横盘震荡\n")
|
||||
sb.WriteString("- 刚平仓不久(<15分钟)\n\n")
|
||||
|
||||
// === 夏普比率自我进化 ===
|
||||
sb.WriteString("# 🧬 夏普比率自我进化\n\n")
|
||||
sb.WriteString("每次你会收到**夏普比率**作为绩效反馈(周期级别):\n\n")
|
||||
sb.WriteString("**夏普比率 < -0.5** (持续亏损):\n")
|
||||
sb.WriteString(" → 🛑 停止交易,连续观望至少6个周期(18分钟)\n")
|
||||
sb.WriteString(" → 🔍 深度反思:\n")
|
||||
sb.WriteString(" • 交易频率过高?(每小时>2次就是过度)\n")
|
||||
sb.WriteString(" • 持仓时间过短?(<30分钟就是过早平仓)\n")
|
||||
sb.WriteString(" • 信号强度不足?(信心度<75)\n")
|
||||
sb.WriteString(" • 是否在做空?(单边做多是错误的)\n\n")
|
||||
sb.WriteString("**夏普比率 -0.5 ~ 0** (轻微亏损):\n")
|
||||
sb.WriteString(" → ⚠️ 严格控制:只做信心度>80的交易\n")
|
||||
sb.WriteString(" → 减少交易频率:每小时最多1笔新开仓\n")
|
||||
sb.WriteString(" → 耐心持仓:至少持有30分钟以上\n\n")
|
||||
sb.WriteString("**夏普比率 0 ~ 0.7** (正收益):\n")
|
||||
sb.WriteString(" → ✅ 维持当前策略\n\n")
|
||||
sb.WriteString("**夏普比率 > 0.7** (优异表现):\n")
|
||||
sb.WriteString(" → 🚀 可适度扩大仓位\n\n")
|
||||
sb.WriteString("**关键**: 夏普比率是唯一指标,它会自然惩罚频繁交易和过度进出。\n\n")
|
||||
|
||||
// === 决策流程 ===
|
||||
sb.WriteString("# 📋 决策流程\n\n")
|
||||
sb.WriteString("1. **分析夏普比率**: 当前策略是否有效?需要调整吗?\n")
|
||||
sb.WriteString("2. **评估持仓**: 趋势是否改变?是否该止盈/止损?\n")
|
||||
sb.WriteString("3. **寻找新机会**: 有强信号吗?多空机会?\n")
|
||||
sb.WriteString("4. **输出决策**: 思维链分析 + JSON\n\n")
|
||||
|
||||
// === 输出格式 ===
|
||||
sb.WriteString("# 📤 输出格式\n\n")
|
||||
sb.WriteString("**第一步: 思维链(纯文本)**\n")
|
||||
// 3. 输出格式 - 动态生成
|
||||
sb.WriteString("#输出格式\n\n")
|
||||
sb.WriteString("第一步: 思维链(纯文本)\n")
|
||||
sb.WriteString("简洁分析你的思考过程\n\n")
|
||||
sb.WriteString("**第二步: JSON决策数组**\n\n")
|
||||
sb.WriteString("第二步: JSON决策数组\n\n")
|
||||
sb.WriteString("```json\n[\n")
|
||||
sb.WriteString(fmt.Sprintf(" {\"symbol\": \"BTCUSDT\", \"action\": \"open_short\", \"leverage\": %d, \"position_size_usd\": %.0f, \"stop_loss\": 97000, \"take_profit\": 91000, \"confidence\": 85, \"risk_usd\": 300, \"reasoning\": \"下跌趋势+MACD死叉\"},\n", btcEthLeverage, accountEquity*5))
|
||||
sb.WriteString(" {\"symbol\": \"ETHUSDT\", \"action\": \"close_long\", \"reasoning\": \"止盈离场\"}\n")
|
||||
sb.WriteString("]\n```\n\n")
|
||||
sb.WriteString("**字段说明**:\n")
|
||||
sb.WriteString("字段说明:\n")
|
||||
sb.WriteString("- `action`: open_long | open_short | close_long | close_short | hold | wait\n")
|
||||
sb.WriteString("- `confidence`: 0-100(开仓建议≥75)\n")
|
||||
sb.WriteString("- 开仓时必填: leverage, position_size_usd, stop_loss, take_profit, confidence, risk_usd, reasoning\n\n")
|
||||
|
||||
// === 关键提醒 ===
|
||||
sb.WriteString("---\n\n")
|
||||
sb.WriteString("**记住**: \n")
|
||||
sb.WriteString("- 目标是夏普比率,不是交易频率\n")
|
||||
sb.WriteString("- 做空 = 做多,都是赚钱工具\n")
|
||||
sb.WriteString("- 宁可错过,不做低质量交易\n")
|
||||
sb.WriteString("- 风险回报比1:3是底线\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -320,18 +290,18 @@ func buildUserPrompt(ctx *Context) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 系统状态
|
||||
sb.WriteString(fmt.Sprintf("**时间**: %s | **周期**: #%d | **运行**: %d分钟\n\n",
|
||||
sb.WriteString(fmt.Sprintf("时间: %s | 周期: #%d | 运行: %d分钟\n\n",
|
||||
ctx.CurrentTime, ctx.CallCount, ctx.RuntimeMinutes))
|
||||
|
||||
// BTC 市场
|
||||
if btcData, hasBTC := ctx.MarketDataMap["BTCUSDT"]; hasBTC {
|
||||
sb.WriteString(fmt.Sprintf("**BTC**: %.2f (1h: %+.2f%%, 4h: %+.2f%%) | MACD: %.4f | RSI: %.2f\n\n",
|
||||
sb.WriteString(fmt.Sprintf("BTC: %.2f (1h: %+.2f%%, 4h: %+.2f%%) | MACD: %.4f | RSI: %.2f\n\n",
|
||||
btcData.CurrentPrice, btcData.PriceChange1h, btcData.PriceChange4h,
|
||||
btcData.CurrentMACD, btcData.CurrentRSI7))
|
||||
}
|
||||
|
||||
// 账户
|
||||
sb.WriteString(fmt.Sprintf("**账户**: 净值%.2f | 余额%.2f (%.1f%%) | 盈亏%+.2f%% | 保证金%.1f%% | 持仓%d个\n\n",
|
||||
sb.WriteString(fmt.Sprintf("账户: 净值%.2f | 余额%.2f (%.1f%%) | 盈亏%+.2f%% | 保证金%.1f%% | 持仓%d个\n\n",
|
||||
ctx.Account.TotalEquity,
|
||||
ctx.Account.AvailableBalance,
|
||||
(ctx.Account.AvailableBalance/ctx.Account.TotalEquity)*100,
|
||||
@@ -369,7 +339,7 @@ func buildUserPrompt(ctx *Context) string {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sb.WriteString("**当前持仓**: 无\n\n")
|
||||
sb.WriteString("当前持仓: 无\n\n")
|
||||
}
|
||||
|
||||
// 候选币种(完整市场数据)
|
||||
@@ -427,7 +397,7 @@ func parseFullDecisionResponse(aiResponse string, accountEquity float64, btcEthL
|
||||
return &FullDecision{
|
||||
CoTTrace: cotTrace,
|
||||
Decisions: []Decision{},
|
||||
}, fmt.Errorf("提取决策失败: %w\n\n=== AI思维链分析 ===\n%s", err, cotTrace)
|
||||
}, fmt.Errorf("提取决策失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 验证决策
|
||||
@@ -435,7 +405,7 @@ func parseFullDecisionResponse(aiResponse string, accountEquity float64, btcEthL
|
||||
return &FullDecision{
|
||||
CoTTrace: cotTrace,
|
||||
Decisions: decisions,
|
||||
}, fmt.Errorf("决策验证失败: %w\n\n=== AI思维链分析 ===\n%s", err, cotTrace)
|
||||
}, fmt.Errorf("决策验证失败: %w", err)
|
||||
}
|
||||
|
||||
return &FullDecision{
|
||||
|
||||
162
decision/prompt_manager.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package decision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// PromptTemplate 系统提示词模板
|
||||
type PromptTemplate struct {
|
||||
Name string // 模板名称(文件名,不含扩展名)
|
||||
Content string // 模板内容
|
||||
}
|
||||
|
||||
// PromptManager 提示词管理器
|
||||
type PromptManager struct {
|
||||
templates map[string]*PromptTemplate
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var (
|
||||
// globalPromptManager 全局提示词管理器
|
||||
globalPromptManager *PromptManager
|
||||
// promptsDir 提示词文件夹路径
|
||||
promptsDir = "prompts"
|
||||
)
|
||||
|
||||
// init 包初始化时加载所有提示词模板
|
||||
func init() {
|
||||
globalPromptManager = NewPromptManager()
|
||||
if err := globalPromptManager.LoadTemplates(promptsDir); err != nil {
|
||||
log.Printf("⚠️ 加载提示词模板失败: %v", err)
|
||||
} else {
|
||||
log.Printf("✓ 已加载 %d 个系统提示词模板", len(globalPromptManager.templates))
|
||||
}
|
||||
}
|
||||
|
||||
// NewPromptManager 创建提示词管理器
|
||||
func NewPromptManager() *PromptManager {
|
||||
return &PromptManager{
|
||||
templates: make(map[string]*PromptTemplate),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadTemplates 从指定目录加载所有提示词模板
|
||||
func (pm *PromptManager) LoadTemplates(dir string) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
// 检查目录是否存在
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("提示词目录不存在: %s", dir)
|
||||
}
|
||||
|
||||
// 扫描目录中的所有 .txt 文件
|
||||
files, err := filepath.Glob(filepath.Join(dir, "*.txt"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("扫描提示词目录失败: %w", err)
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
log.Printf("⚠️ 提示词目录 %s 中没有找到 .txt 文件", dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 加载每个模板文件
|
||||
for _, file := range files {
|
||||
// 读取文件内容
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 读取提示词文件失败 %s: %v", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 提取文件名(不含扩展名)作为模板名称
|
||||
fileName := filepath.Base(file)
|
||||
templateName := strings.TrimSuffix(fileName, filepath.Ext(fileName))
|
||||
|
||||
// 存储模板
|
||||
pm.templates[templateName] = &PromptTemplate{
|
||||
Name: templateName,
|
||||
Content: string(content),
|
||||
}
|
||||
|
||||
log.Printf(" 📄 加载提示词模板: %s (%s)", templateName, fileName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTemplate 获取指定名称的提示词模板
|
||||
func (pm *PromptManager) GetTemplate(name string) (*PromptTemplate, error) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
|
||||
template, exists := pm.templates[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("提示词模板不存在: %s", name)
|
||||
}
|
||||
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// GetAllTemplateNames 获取所有模板名称列表
|
||||
func (pm *PromptManager) GetAllTemplateNames() []string {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
|
||||
names := make([]string, 0, len(pm.templates))
|
||||
for name := range pm.templates {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// GetAllTemplates 获取所有模板
|
||||
func (pm *PromptManager) GetAllTemplates() []*PromptTemplate {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
|
||||
templates := make([]*PromptTemplate, 0, len(pm.templates))
|
||||
for _, template := range pm.templates {
|
||||
templates = append(templates, template)
|
||||
}
|
||||
|
||||
return templates
|
||||
}
|
||||
|
||||
// ReloadTemplates 重新加载所有模板
|
||||
func (pm *PromptManager) ReloadTemplates(dir string) error {
|
||||
pm.mu.Lock()
|
||||
pm.templates = make(map[string]*PromptTemplate)
|
||||
pm.mu.Unlock()
|
||||
|
||||
return pm.LoadTemplates(dir)
|
||||
}
|
||||
|
||||
// === 全局函数(供外部调用)===
|
||||
|
||||
// GetPromptTemplate 获取指定名称的提示词模板(全局函数)
|
||||
func GetPromptTemplate(name string) (*PromptTemplate, error) {
|
||||
return globalPromptManager.GetTemplate(name)
|
||||
}
|
||||
|
||||
// GetAllPromptTemplateNames 获取所有模板名称(全局函数)
|
||||
func GetAllPromptTemplateNames() []string {
|
||||
return globalPromptManager.GetAllTemplateNames()
|
||||
}
|
||||
|
||||
// GetAllPromptTemplates 获取所有模板(全局函数)
|
||||
func GetAllPromptTemplates() []*PromptTemplate {
|
||||
return globalPromptManager.GetAllTemplates()
|
||||
}
|
||||
|
||||
// ReloadPromptTemplates 重新加载所有模板(全局函数)
|
||||
func ReloadPromptTemplates() error {
|
||||
return globalPromptManager.ReloadTemplates(promptsDir)
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Backend service (API and core logic)
|
||||
nofx:
|
||||
@@ -12,14 +10,16 @@ services:
|
||||
- "${NOFX_BACKEND_PORT:-8080}:8080"
|
||||
volumes:
|
||||
- ./config.json:/app/config.json:ro
|
||||
- ./config.db:/app/config.db
|
||||
- ./decision_logs:/app/decision_logs
|
||||
- ./prompts:/app/prompts
|
||||
- /etc/localtime:/etc/localtime:ro # Sync host time
|
||||
environment:
|
||||
- TZ=${NOFX_TIMEZONE:-Asia/Shanghai} # Set timezone
|
||||
networks:
|
||||
- nofx-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -63,6 +63,6 @@ COPY --from=backend-builder /app/nofx .
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1
|
||||
|
||||
CMD ["./nofx"]
|
||||
|
||||
242
docs/MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# 📦 Documentation Migration Guide
|
||||
|
||||
## What Changed?
|
||||
|
||||
NOFX documentation has been reorganized into a structured `docs/` directory for better organization and navigation.
|
||||
|
||||
## 🗺️ File Locations (Old → New)
|
||||
|
||||
### Deployment Guides
|
||||
- `DOCKER_DEPLOY.en.md` → `docs/getting-started/docker-deploy.en.md`
|
||||
- `DOCKER_DEPLOY.md` → `docs/getting-started/docker-deploy.zh-CN.md`
|
||||
- `PM2_DEPLOYMENT.md` → `docs/getting-started/pm2-deploy.md`
|
||||
- `CUSTOM_API.md` → `docs/getting-started/custom-api.md`
|
||||
|
||||
### Community Docs
|
||||
- `HOW_TO_POST_BOUNTY.md` → `docs/community/bounty-guide.md`
|
||||
- `INTEGRATION_BOUNTY_HYPERLIQUID.md` → `docs/community/bounty-hyperliquid.md`
|
||||
- `INTEGRATION_BOUNTY_ASTER.md` → `docs/community/bounty-aster.md`
|
||||
|
||||
### Internationalization
|
||||
- `README.zh-CN.md` → `docs/i18n/zh-CN/README.md`
|
||||
- `README.ru.md` → `docs/i18n/ru/README.md`
|
||||
- `README.uk.md` → `docs/i18n/uk/README.md`
|
||||
- `常见问题.md` → `docs/guides/faq.zh-CN.md`
|
||||
|
||||
### Root Directory (Unchanged)
|
||||
These stay in the root for GitHub recognition:
|
||||
- `README.md` ✅ (stays in root)
|
||||
- `LICENSE` ✅ (stays in root)
|
||||
- `CONTRIBUTING.md` ✅ (stays in root)
|
||||
- `CODE_OF_CONDUCT.md` ✅ (stays in root)
|
||||
- `SECURITY.md` ✅ (stays in root)
|
||||
|
||||
## 🎯 Why This Change?
|
||||
|
||||
### Before (❌ Problems)
|
||||
```
|
||||
nofx/
|
||||
├── README.md
|
||||
├── README.zh-CN.md
|
||||
├── README.ru.md
|
||||
├── README.uk.md
|
||||
├── DOCKER_DEPLOY.md
|
||||
├── DOCKER_DEPLOY.en.md
|
||||
├── PM2_DEPLOYMENT.md
|
||||
├── CUSTOM_API.md
|
||||
├── HOW_TO_POST_BOUNTY.md
|
||||
├── INTEGRATION_BOUNTY_HYPERLIQUID.md
|
||||
├── INTEGRATION_BOUNTY_ASTER.md
|
||||
├── 常见问题.md
|
||||
└── ... (15+ markdown files in root!)
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
- 😵 Too cluttered (15+ files in root)
|
||||
- 🔍 Hard to find specific docs
|
||||
- 🌍 Mixed languages
|
||||
- 📚 No clear organization
|
||||
|
||||
### After (✅ Benefits)
|
||||
```
|
||||
nofx/
|
||||
├── README.md # Project homepage
|
||||
├── LICENSE # Legal (GitHub needs it here)
|
||||
├── CONTRIBUTING.md # GitHub auto-links
|
||||
├── CODE_OF_CONDUCT.md # GitHub auto-links
|
||||
├── SECURITY.md # GitHub auto-links
|
||||
│
|
||||
└── docs/ # 📚 Documentation hub
|
||||
├── README.md # Documentation home
|
||||
├── getting-started/ # 🚀 Setup guides
|
||||
├── guides/ # 📘 User guides
|
||||
├── community/ # 👥 Contribution docs
|
||||
├── i18n/ # 🌍 Translations
|
||||
└── architecture/ # 🏗️ Technical docs
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Clean root directory
|
||||
- ✅ Logical categorization
|
||||
- ✅ Easy navigation
|
||||
- ✅ Scalable structure
|
||||
- ✅ Professional appearance
|
||||
|
||||
## 📚 New Documentation Structure
|
||||
|
||||
### Root Level
|
||||
Files GitHub needs to see:
|
||||
- `README.md` - Main project page
|
||||
- `LICENSE` - Open source license
|
||||
- `CONTRIBUTING.md` - Contributor guide
|
||||
- `CODE_OF_CONDUCT.md` - Community standards
|
||||
- `SECURITY.md` - Security policy
|
||||
|
||||
### docs/ Level
|
||||
|
||||
**Navigation:**
|
||||
- `docs/README.md` - **Start here!** Main documentation hub
|
||||
|
||||
**Categories:**
|
||||
|
||||
1. **`getting-started/`** - Deployment and setup
|
||||
- Docker deployment (EN/中文)
|
||||
- PM2 deployment
|
||||
- Custom API configuration
|
||||
|
||||
2. **`guides/`** - Usage guides and tutorials
|
||||
- FAQ (中文)
|
||||
- Troubleshooting (planned)
|
||||
- Configuration examples (planned)
|
||||
|
||||
3. **`community/`** - Contribution and bounties
|
||||
- Bounty guide
|
||||
- Active bounty tasks
|
||||
- Contributor recognition
|
||||
|
||||
4. **`i18n/`** - International translations
|
||||
- `zh-CN/` - Simplified Chinese
|
||||
- `ru/` - Russian
|
||||
- `uk/` - Ukrainian
|
||||
|
||||
5. **`architecture/`** - Technical documentation
|
||||
- System design (planned)
|
||||
- API reference (planned)
|
||||
- Database schema (planned)
|
||||
|
||||
## 🔗 Updating Your Links
|
||||
|
||||
### If you bookmarked old links:
|
||||
|
||||
| Old Link | New Link |
|
||||
|----------|----------|
|
||||
| `DOCKER_DEPLOY.en.md` | `docs/getting-started/docker-deploy.en.md` |
|
||||
| `README.zh-CN.md` | `docs/i18n/zh-CN/README.md` |
|
||||
| `HOW_TO_POST_BOUNTY.md` | `docs/community/bounty-guide.md` |
|
||||
|
||||
### If you linked in your own docs:
|
||||
|
||||
**Update relative links:**
|
||||
```markdown
|
||||
<!-- Old -->
|
||||
[Docker Deployment](DOCKER_DEPLOY.en.md)
|
||||
|
||||
<!-- New -->
|
||||
[Docker Deployment](docs/getting-started/docker-deploy.en.md)
|
||||
```
|
||||
|
||||
**GitHub URLs automatically redirect!**
|
||||
- Old: `github.com/tinkle-community/nofx/blob/main/DOCKER_DEPLOY.en.md`
|
||||
- Will redirect to: `github.com/.../docs/getting-started/docker-deploy.en.md`
|
||||
|
||||
## 🛠️ For Contributors
|
||||
|
||||
### Cloning/Pulling Latest
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
git pull origin dev
|
||||
|
||||
# Your old bookmarks still work!
|
||||
# Git tracked the file moves (git mv)
|
||||
```
|
||||
|
||||
### Finding Documentation
|
||||
|
||||
**Use the navigation hub:**
|
||||
1. Start at [docs/README.md](README.md)
|
||||
2. Browse by category
|
||||
3. Use the quick navigation section
|
||||
|
||||
**Or search:**
|
||||
```bash
|
||||
# Find all markdown docs
|
||||
find docs -name "*.md"
|
||||
|
||||
# Search content
|
||||
grep -r "keyword" docs/
|
||||
```
|
||||
|
||||
### Adding New Documentation
|
||||
|
||||
**Follow the structure:**
|
||||
|
||||
```bash
|
||||
# Getting started guides
|
||||
docs/getting-started/your-guide.md
|
||||
|
||||
# User guides
|
||||
docs/guides/your-tutorial.md
|
||||
|
||||
# Community docs
|
||||
docs/community/your-doc.md
|
||||
|
||||
# Translations
|
||||
docs/i18n/ja/README.md # Japanese example
|
||||
```
|
||||
|
||||
**Update navigation:**
|
||||
- Add link in relevant category README
|
||||
- Add to `docs/README.md` main hub
|
||||
|
||||
## 📝 Commit Messages
|
||||
|
||||
This reorganization was committed as:
|
||||
|
||||
```
|
||||
docs: reorganize documentation into structured docs/ directory
|
||||
|
||||
- Move deployment guides to docs/getting-started/
|
||||
- Move community docs to docs/community/
|
||||
- Move translations to docs/i18n/
|
||||
- Create navigation hub at docs/README.md
|
||||
- Update all internal links in README.md
|
||||
- Add GitHub issue/PR templates
|
||||
|
||||
BREAKING CHANGE: Direct links to moved files will need updating
|
||||
(though GitHub redirects should work)
|
||||
|
||||
Closes #XXX
|
||||
```
|
||||
|
||||
## 🆘 Need Help?
|
||||
|
||||
**Can't find a document?**
|
||||
1. Check [docs/README.md](README.md) navigation hub
|
||||
2. Search GitHub repo
|
||||
3. Ask in [Telegram](https://t.me/nofx_dev_community)
|
||||
|
||||
**Link broken?**
|
||||
- Report in [GitHub Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
- We'll fix it ASAP!
|
||||
|
||||
**Want to contribute docs?**
|
||||
- See [Contributing Guide](../CONTRIBUTING.md)
|
||||
- Check [docs/community/](community/README.md)
|
||||
|
||||
---
|
||||
|
||||
**Migration Date:** 2025-11-01
|
||||
**Maintainers:** Tinkle Community
|
||||
|
||||
[← Back to Documentation Home](README.md)
|
||||
192
docs/README.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# 📚 NOFX Documentation Center / 文档中心
|
||||
|
||||
Welcome to the NOFX documentation! This page helps you find the right documentation quickly.
|
||||
|
||||
欢迎来到 NOFX 文档中心!本页面帮助您快速找到所需文档。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Getting Started / 快速开始
|
||||
|
||||
**New to NOFX? Start here!**
|
||||
|
||||
| Document | Description | 描述 |
|
||||
|----------|-------------|------|
|
||||
| [Main README](../README.md) | Project overview, features, quick start | 项目概述、功能、快速入门 |
|
||||
| [Getting Started Index (EN)](getting-started/README.md) | All deployment options | 所有部署选项 |
|
||||
| [Getting Started Index (中文)](getting-started/README.zh-CN.md) | 所有部署选项 | All deployment options |
|
||||
| [Docker Deployment (EN)](getting-started/docker-deploy.en.md) | Deploy with Docker (recommended) | Docker 部署(推荐) |
|
||||
| [Docker Deployment (中文)](getting-started/docker-deploy.zh-CN.md) | Docker 部署指南(中文) | Docker deployment guide |
|
||||
| [PM2 Deployment (EN)](getting-started/pm2-deploy.en.md) | Deploy with PM2 process manager | PM2 进程管理器部署 |
|
||||
| [PM2 Deployment (中文)](getting-started/pm2-deploy.md) | PM2 部署指南(中文) | PM2 deployment guide |
|
||||
| [Custom API (EN)](getting-started/custom-api.en.md) | Connect custom AI API providers | 连接自定义 AI API |
|
||||
| [Custom API (中文)](getting-started/custom-api.md) | 连接自定义 AI API 提供商 | Custom AI provider guide |
|
||||
|
||||
**Quick Links:**
|
||||
- 📖 See all options → [Getting Started](getting-started/README.md) / [快速开始](getting-started/README.zh-CN.md)
|
||||
- 🐳 Want easiest setup? → [Docker (EN)](getting-started/docker-deploy.en.md) / [Docker (中文)](getting-started/docker-deploy.zh-CN.md)
|
||||
- 🔧 Advanced user? → [PM2 (EN)](getting-started/pm2-deploy.en.md) / [PM2 (中文)](getting-started/pm2-deploy.md)
|
||||
- 🤖 Custom AI model? → [Custom API (EN)](getting-started/custom-api.en.md) / [自定义 API](getting-started/custom-api.md)
|
||||
|
||||
---
|
||||
|
||||
## 📘 User Guides / 使用指南
|
||||
|
||||
**Learn how to use NOFX effectively**
|
||||
|
||||
| Document | Description | 描述 |
|
||||
|----------|-------------|------|
|
||||
| [User Guides Index (EN)](guides/README.md) | All usage guides and tips | 所有使用指南和技巧 |
|
||||
| [User Guides Index (中文)](guides/README.zh-CN.md) | 所有使用指南和技巧 | All usage guides and tips |
|
||||
| [FAQ (English)](guides/faq.en.md) | Frequently asked questions | 常见问题解答 |
|
||||
| [FAQ (中文)](guides/faq.zh-CN.md) | 常见问题解答 | Frequently asked questions |
|
||||
| Troubleshooting *(coming soon)* | Common issues and solutions | 故障排查 |
|
||||
| Configuration Guide *(coming soon)* | Advanced configuration options | 高级配置选项 |
|
||||
| Trading Strategies *(coming soon)* | AI trading strategy examples | AI 交易策略示例 |
|
||||
|
||||
---
|
||||
|
||||
## 👥 Community & Contributing / 社区与贡献
|
||||
|
||||
**Join the community and contribute!**
|
||||
|
||||
| Document | Description | 描述 |
|
||||
|----------|-------------|------|
|
||||
| [Code of Conduct](../CODE_OF_CONDUCT.md) | Community guidelines | 社区行为准则 |
|
||||
| [Security Policy](../SECURITY.md) | Report security vulnerabilities | 报告安全漏洞 |
|
||||
| [Bounty Guide](community/bounty-guide.md) | How to post bounty tasks | 如何发布悬赏任务 |
|
||||
| [Hyperliquid Bounty](community/bounty-hyperliquid.md) | Hyperliquid integration bounty | Hyperliquid 集成悬赏 |
|
||||
| [Aster Bounty](community/bounty-aster.md) | Aster DEX integration bounty | Aster DEX 集成悬赏 |
|
||||
|
||||
**Get Involved:**
|
||||
- 💬 [Telegram Community](https://t.me/nofx_dev_community)
|
||||
- 🐦 [Twitter @nofx_ai](https://x.com/nofx_ai)
|
||||
- 🐛 [Report Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
|
||||
---
|
||||
|
||||
## 🌍 International / 国际化文档
|
||||
|
||||
**Documentation in other languages**
|
||||
|
||||
| Language | Main README | Status |
|
||||
|----------|-------------|--------|
|
||||
| 🇨🇳 Chinese (中文) | [README.md](i18n/zh-CN/README.md) | ✅ Complete |
|
||||
| 🇷🇺 Russian (Русский) | [README.md](i18n/ru/README.md) | ✅ Complete |
|
||||
| 🇺🇦 Ukrainian (Українська) | [README.md](i18n/uk/README.md) | ✅ Complete |
|
||||
| 🇬🇧 English | [README.md](../README.md) | ✅ Complete |
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture & Development / 架构与开发
|
||||
|
||||
**For developers who want to understand the internals**
|
||||
|
||||
| Document | Description | 描述 |
|
||||
|----------|-------------|------|
|
||||
| [Architecture Overview (EN)](architecture/README.md) | System architecture, modules, and design | 系统架构、模块和设计 |
|
||||
| [Architecture Overview (中文)](architecture/README.zh-CN.md) | 系统架构、模块和设计 | System architecture overview |
|
||||
| API Reference *(coming soon)* | HTTP API documentation | HTTP API 文档 |
|
||||
| Database Schema *(coming soon)* | SQLite database structure | SQLite 数据库结构 |
|
||||
| Testing Guide *(coming soon)* | How to write tests | 如何编写测试 |
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Roadmap / 路线图
|
||||
|
||||
**NOFX's strategic development plan and market expansion**
|
||||
|
||||
| Document | Description | 描述 |
|
||||
|----------|-------------|------|
|
||||
| [Roadmap (EN)](roadmap/README.md) | Short-term and long-term roadmap, feature timeline | 短期和长期路线图、功能时间表 |
|
||||
| [Roadmap (中文)](roadmap/README.zh-CN.md) | 短期和长期路线图、功能时间表 | Strategic development plan |
|
||||
|
||||
**Roadmap Highlights:**
|
||||
- 📈 **Short-term (Q2-Q3 2025)**: Advanced risk management, multi-AI ensemble, new exchange integrations
|
||||
- 🚀 **Long-term (2026)**: Universal market expansion (stocks, futures, options, forex), reinforcement learning, enterprise features
|
||||
|
||||
---
|
||||
|
||||
## 📄 Legal & Policies / 法律与政策
|
||||
|
||||
| Document | Description | 描述 |
|
||||
|----------|-------------|------|
|
||||
| [License (MIT)](../LICENSE) | Open source license | 开源许可证 |
|
||||
| [Changelog (EN)](../CHANGELOG.md) | Version history and updates | 版本历史和更新 |
|
||||
| [Changelog (中文)](../CHANGELOG.zh-CN.md) | 版本历史和更新 | Version history and updates |
|
||||
| [Security Policy](../SECURITY.md) | Vulnerability disclosure | 漏洞披露政策 |
|
||||
| [Code of Conduct](../CODE_OF_CONDUCT.md) | Community standards | 社区标准 |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Quick Navigation / 快速导航
|
||||
|
||||
**Find what you need fast:**
|
||||
|
||||
### I want to...
|
||||
- 🚀 **Get started quickly** → [Getting Started](getting-started/README.md) / [快速开始](getting-started/README.zh-CN.md)
|
||||
- 🐛 **Report a bug** → [GitHub Issues](https://github.com/tinkle-community/nofx/issues/new)
|
||||
- 💡 **Suggest a feature** → [Feature Request](https://github.com/tinkle-community/nofx/issues/new?template=feature_request.md)
|
||||
- 🔒 **Report security issue** → [Security Policy](../SECURITY.md)
|
||||
- 💰 **Claim a bounty** → [Bounty Guide](community/bounty-guide.md)
|
||||
- 🤝 **Contribute code** → [Contributing Guide](../CONTRIBUTING.md)
|
||||
- 💬 **Ask questions** → [Telegram Community](https://t.me/nofx_dev_community)
|
||||
|
||||
### I'm looking for...
|
||||
- 🏗️ **System architecture** → [Architecture (EN)](architecture/README.md) / [架构文档](architecture/README.zh-CN.md)
|
||||
- 🗺️ **Product roadmap** → [Roadmap (EN)](roadmap/README.md) / [路线图](roadmap/README.zh-CN.md)
|
||||
- 📊 **API documentation** → Coming soon
|
||||
- 🧪 **Testing guide** → Coming soon
|
||||
- 🔧 **Configuration examples** → [Custom API (EN)](getting-started/custom-api.en.md) / [自定义 API](getting-started/custom-api.md)
|
||||
- 🌐 **Multi-language docs** → [International section](#-international--国际化文档)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Status
|
||||
|
||||
| Category | Status | Last Updated |
|
||||
|----------|--------|--------------|
|
||||
| Getting Started | ✅ Complete | 2025-11-01 |
|
||||
| User Guides | ✅ Complete | 2025-11-01 |
|
||||
| Community | ✅ Complete | 2025-11-01 |
|
||||
| Architecture | ✅ Complete | 2025-11-01 |
|
||||
| Roadmap | ✅ Complete | 2025-11-01 |
|
||||
| API Reference | 📋 Planned | - |
|
||||
|
||||
**Legend:**
|
||||
- ✅ Complete - Documentation is ready
|
||||
- 🚧 In Progress - Being written
|
||||
- 📋 Planned - On the roadmap
|
||||
- ⚠️ Outdated - Needs update
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Need Help?
|
||||
|
||||
**Can't find what you're looking for?**
|
||||
|
||||
1. **Search GitHub Issues** - Someone might have asked already
|
||||
2. **Join Telegram** - [NOFX Developer Community](https://t.me/nofx_dev_community)
|
||||
3. **Ask on Twitter** - Mention [@nofx_ai](https://x.com/nofx_ai)
|
||||
4. **Create an Issue** - [New Issue](https://github.com/tinkle-community/nofx/issues/new)
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing to Documentation
|
||||
|
||||
Found an error or want to improve the docs?
|
||||
|
||||
1. **Small fixes** - Click "Edit" on GitHub and submit PR
|
||||
2. **New documentation** - Create an issue first to discuss
|
||||
3. **Translations** - See [Contributing Guide](../CONTRIBUTING.md)
|
||||
|
||||
**Documentation Contributors:**
|
||||
- All documentation follows [Markdown Guide](https://www.markdownguide.org/)
|
||||
- Use clear, concise language
|
||||
- Include code examples where helpful
|
||||
- Add screenshots for UI-related docs
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-11-01
|
||||
**Maintained by:** [Tinkle Community](https://github.com/tinkle-community)
|
||||
575
docs/architecture/README.md
Normal file
@@ -0,0 +1,575 @@
|
||||
# 🏗️ NOFX Architecture Documentation
|
||||
|
||||
**Language:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
Technical documentation for developers who want to understand NOFX internals.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
NOFX is a full-stack AI trading platform with:
|
||||
- **Backend:** Go (Gin framework, SQLite)
|
||||
- **Frontend:** React/TypeScript (Vite, TailwindCSS)
|
||||
- **Architecture:** Microservice-inspired modular design
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
nofx/
|
||||
├── main.go # Program entry (multi-trader manager)
|
||||
├── config.json # ~~Multi-trader config~~ (Now via web interface)
|
||||
├── trading.db # SQLite database (traders, models, exchanges)
|
||||
│
|
||||
├── api/ # HTTP API service
|
||||
│ └── server.go # Gin framework, RESTful API
|
||||
│
|
||||
├── trader/ # Trading core
|
||||
│ ├── auto_trader.go # Auto trading main controller
|
||||
│ ├── interface.go # Unified trader interface
|
||||
│ ├── binance_futures.go # Binance API wrapper
|
||||
│ ├── hyperliquid_trader.go # Hyperliquid DEX wrapper
|
||||
│ └── aster_trader.go # Aster DEX wrapper
|
||||
│
|
||||
├── manager/ # Multi-trader management
|
||||
│ └── trader_manager.go # Manages multiple trader instances
|
||||
│
|
||||
├── config/ # Configuration & database
|
||||
│ └── database.go # SQLite operations and schema
|
||||
│
|
||||
├── auth/ # Authentication
|
||||
│ └── jwt.go # JWT token management & 2FA
|
||||
│
|
||||
├── mcp/ # Model Context Protocol - AI communication
|
||||
│ └── client.go # AI API client (DeepSeek/Qwen/Custom)
|
||||
│
|
||||
├── decision/ # AI decision engine
|
||||
│ ├── engine.go # Decision logic with historical feedback
|
||||
│ └── prompt_manager.go # Prompt template system
|
||||
│
|
||||
├── market/ # Market data fetching
|
||||
│ └── data.go # Market data & technical indicators (TA-Lib)
|
||||
│ └── api_client.go # Market data acquisition API
|
||||
│ └── websocket_client.go # Market data acquisition WebSocket interface
|
||||
│ └── combined_streams.go # Market data acquisition: Combined streaming (single link to subscribe to multiple cryptocurrencies)
|
||||
│ └── monitor.go # Market data cache
|
||||
│ └── types.go # market structure
|
||||
|
||||
├── pool/ # Coin pool management
|
||||
│ └── coin_pool.go # AI500 + OI Top merged pool
|
||||
│
|
||||
├── logger/ # Logging system
|
||||
│ └── decision_logger.go # Decision recording + performance analysis
|
||||
│
|
||||
├── decision_logs/ # Decision log storage (JSON files)
|
||||
│ ├── {trader_id}/ # Per-trader logs
|
||||
│ └── {timestamp}.json # Individual decisions
|
||||
│
|
||||
└── web/ # React frontend
|
||||
├── src/
|
||||
│ ├── components/ # React components
|
||||
│ │ ├── EquityChart.tsx # Equity curve chart
|
||||
│ │ ├── ComparisonChart.tsx # Multi-AI comparison chart
|
||||
│ │ └── CompetitionPage.tsx # Competition leaderboard
|
||||
│ ├── lib/api.ts # API call wrapper
|
||||
│ ├── types/index.ts # TypeScript types
|
||||
│ ├── stores/ # Zustand state management
|
||||
│ ├── index.css # Binance-style CSS
|
||||
│ └── App.tsx # Main app
|
||||
├── package.json # Frontend dependencies
|
||||
└── vite.config.ts # Vite configuration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Core Dependencies
|
||||
|
||||
### Backend (Go)
|
||||
|
||||
| Package | Purpose | Version |
|
||||
|---------|---------|---------|
|
||||
| `github.com/gin-gonic/gin` | HTTP API framework | v1.9+ |
|
||||
| `github.com/adshao/go-binance/v2` | Binance API client | v2.4+ |
|
||||
| `github.com/markcheno/go-talib` | Technical indicators (TA-Lib) | Latest |
|
||||
| `github.com/mattn/go-sqlite3` | SQLite database driver | v1.14+ |
|
||||
| `github.com/golang-jwt/jwt/v5` | JWT authentication | v5.0+ |
|
||||
| `github.com/pquerna/otp` | 2FA/TOTP support | v1.4+ |
|
||||
| `golang.org/x/crypto` | Password hashing (bcrypt) | Latest |
|
||||
|
||||
### Frontend (React + TypeScript)
|
||||
|
||||
| Package | Purpose | Version |
|
||||
|---------|---------|---------|
|
||||
| `react` + `react-dom` | UI framework | 18.3+ |
|
||||
| `typescript` | Type safety | 5.8+ |
|
||||
| `vite` | Build tool | 6.0+ |
|
||||
| `recharts` | Charts (equity, comparison) | 2.15+ |
|
||||
| `swr` | Data fetching & caching | 2.2+ |
|
||||
| `zustand` | State management | 5.0+ |
|
||||
| `tailwindcss` | CSS framework | 3.4+ |
|
||||
| `lucide-react` | Icon library | Latest |
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ System Architecture
|
||||
|
||||
### High-Level Overview
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER │
|
||||
│ React SPA (Vite + TypeScript + TailwindCSS) │
|
||||
│ - Competition dashboard, trader management UI │
|
||||
│ - Real-time charts (Recharts), authentication pages │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
↓ HTTP/JSON API
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ API LAYER (Gin Router) │
|
||||
│ /api/traders, /api/status, /api/positions, /api/decisions │
|
||||
│ Authentication middleware (JWT), CORS handling │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ BUSINESS LOGIC LAYER │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐ │
|
||||
│ │ TraderManager │ │ DecisionEngine │ │ MarketData │ │
|
||||
│ │ - Multi-trader │ │ - AI reasoning │ │ - K-lines │ │
|
||||
│ │ orchestration │ │ - Risk control │ │ - Indicators │ │
|
||||
│ └──────────────────┘ └──────────────────┘ └────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ DATA ACCESS LAYER │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
||||
│ │ SQLite DB │ │ File Logger │ │ External APIs │ │
|
||||
│ │ - Traders │ │ - Decisions │ │ - Binance │ │
|
||||
│ │ - Models │ │ - Performance│ │ - Hyperliquid │ │
|
||||
│ │ - Exchanges │ │ analysis │ │ - Aster │ │
|
||||
│ └──────────────┘ └──────────────┘ └────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Component Diagram
|
||||
|
||||
*(Coming soon: detailed component interaction diagram)*
|
||||
|
||||
---
|
||||
|
||||
## 📚 Core Modules
|
||||
|
||||
### 1. Trader System (`trader/`)
|
||||
|
||||
**Purpose:** Trading execution layer with multi-exchange support
|
||||
|
||||
**Key Files:**
|
||||
- `auto_trader.go` - Main trading orchestrator (100+ lines)
|
||||
- `interface.go` - Unified trader interface
|
||||
- `binance_futures.go` - Binance API wrapper
|
||||
- `hyperliquid_trader.go` - Hyperliquid DEX wrapper
|
||||
- `aster_trader.go` - Aster DEX wrapper
|
||||
|
||||
**Design Pattern:** Strategy pattern with interface-based abstraction
|
||||
|
||||
**Example:**
|
||||
```go
|
||||
type ExchangeClient interface {
|
||||
GetAccount() (*AccountInfo, error)
|
||||
GetPositions() ([]*Position, error)
|
||||
CreateOrder(*OrderParams) (*Order, error)
|
||||
// ... more methods
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Decision Engine (`decision/`)
|
||||
|
||||
**Purpose:** AI-powered trading decision making
|
||||
|
||||
**Key Files:**
|
||||
- `engine.go` - Decision logic with historical feedback
|
||||
- `prompt_manager.go` - Template system for AI prompts
|
||||
|
||||
**Features:**
|
||||
- Chain-of-Thought reasoning
|
||||
- Historical performance analysis
|
||||
- Risk-aware decision making
|
||||
- Multi-model support (DeepSeek, Qwen, custom)
|
||||
|
||||
**Flow:**
|
||||
```
|
||||
Historical Data → Prompt Generation → AI API Call →
|
||||
Decision Parsing → Risk Validation → Execution
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Market Data System (`market/`)
|
||||
|
||||
**Purpose:** Fetch and analyze market data
|
||||
|
||||
**Key Files:**
|
||||
- `data.go` - Market data fetching and technical indicators
|
||||
|
||||
**Features:**
|
||||
- Multi-timeframe K-line data (3min, 4hour)
|
||||
- Technical indicators via TA-Lib:
|
||||
- EMA (20, 50)
|
||||
- MACD
|
||||
- RSI (7, 14)
|
||||
- ATR (volatility)
|
||||
- Open Interest tracking
|
||||
|
||||
---
|
||||
|
||||
### 4. Manager (`manager/`)
|
||||
|
||||
**Purpose:** Multi-trader orchestration
|
||||
|
||||
**Key Files:**
|
||||
- `trader_manager.go` - Manages multiple trader instances
|
||||
|
||||
**Responsibilities:**
|
||||
- Trader lifecycle (start, stop, restart)
|
||||
- Resource allocation
|
||||
- Concurrent execution coordination
|
||||
|
||||
---
|
||||
|
||||
### 5. API Server (`api/`)
|
||||
|
||||
**Purpose:** HTTP API for frontend communication
|
||||
|
||||
**Key Files:**
|
||||
- `server.go` - Gin framework RESTful API
|
||||
|
||||
**Endpoints:**
|
||||
```
|
||||
GET /api/traders # List all traders
|
||||
POST /api/traders # Create trader
|
||||
POST /api/traders/:id/start # Start trader
|
||||
GET /api/status # System status
|
||||
GET /api/positions # Current positions
|
||||
GET /api/decisions/latest # Recent decisions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Database Layer (`config/`)
|
||||
|
||||
**Purpose:** SQLite data persistence
|
||||
|
||||
**Key Files:**
|
||||
- `database.go` - Database operations and schema
|
||||
|
||||
**Tables:**
|
||||
- `users` - User accounts (with 2FA support)
|
||||
- `ai_models` - AI model configurations
|
||||
- `exchanges` - Exchange credentials
|
||||
- `traders` - Trader instances
|
||||
- `equity_history` - Performance tracking
|
||||
- `system_config` - Application settings
|
||||
|
||||
---
|
||||
|
||||
### 7. Authentication (`auth/`)
|
||||
|
||||
**Purpose:** User authentication and authorization
|
||||
|
||||
**Features:**
|
||||
- JWT token-based auth
|
||||
- 2FA with TOTP (Google Authenticator)
|
||||
- Bcrypt password hashing
|
||||
- Admin mode (simplified single-user)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Request Flow Examples
|
||||
|
||||
### Example 1: Create New Trader
|
||||
|
||||
```
|
||||
User Action (Frontend)
|
||||
↓
|
||||
POST /api/traders
|
||||
↓
|
||||
API Server (auth middleware)
|
||||
↓
|
||||
Database.CreateTrader()
|
||||
↓
|
||||
TraderManager.StartTrader()
|
||||
↓
|
||||
AutoTrader.Run() → goroutine
|
||||
↓
|
||||
Response: {trader_id, status}
|
||||
```
|
||||
|
||||
### Example 2: Trading Decision Cycle
|
||||
|
||||
```
|
||||
AutoTrader (every 3-5 min)
|
||||
↓
|
||||
1. FetchAccountStatus()
|
||||
↓
|
||||
2. GetOpenPositions()
|
||||
↓
|
||||
3. FetchMarketData() → TA-Lib indicators
|
||||
↓
|
||||
4. AnalyzeHistory() → last 20 trades
|
||||
↓
|
||||
5. GeneratePrompt() → full context
|
||||
↓
|
||||
6. CallAI() → DeepSeek/Qwen
|
||||
↓
|
||||
7. ParseDecision() → structured output
|
||||
↓
|
||||
8. ValidateRisk() → position limits, margin
|
||||
↓
|
||||
9. ExecuteOrders() → exchange API
|
||||
↓
|
||||
10. LogDecision() → JSON file + database
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Data Flow
|
||||
|
||||
### Market Data Flow
|
||||
|
||||
```
|
||||
Exchange API
|
||||
↓
|
||||
market.FetchKlines()
|
||||
↓
|
||||
TA-Lib.Calculate(EMA, MACD, RSI)
|
||||
↓
|
||||
DecisionEngine (as context)
|
||||
↓
|
||||
AI Model (reasoning)
|
||||
```
|
||||
|
||||
### Decision Logging Flow
|
||||
|
||||
```
|
||||
AI Response
|
||||
↓
|
||||
decision_logger.go
|
||||
↓
|
||||
JSON file: decision_logs/{trader_id}/{timestamp}.json
|
||||
↓
|
||||
Database: performance tracking
|
||||
↓
|
||||
Frontend: /api/decisions/latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Database Schema
|
||||
|
||||
### Core Tables
|
||||
|
||||
**users**
|
||||
```sql
|
||||
- id (INTEGER PRIMARY KEY)
|
||||
- username (TEXT UNIQUE)
|
||||
- password_hash (TEXT)
|
||||
- totp_secret (TEXT)
|
||||
- is_admin (BOOLEAN)
|
||||
- created_at (DATETIME)
|
||||
```
|
||||
|
||||
**ai_models**
|
||||
```sql
|
||||
- id (INTEGER PRIMARY KEY)
|
||||
- name (TEXT)
|
||||
- model_type (TEXT) -- deepseek, qwen, custom
|
||||
- api_key (TEXT)
|
||||
- api_url (TEXT)
|
||||
- enabled (BOOLEAN)
|
||||
```
|
||||
|
||||
**traders**
|
||||
```sql
|
||||
- id (TEXT PRIMARY KEY)
|
||||
- name (TEXT)
|
||||
- ai_model_id (INTEGER FK)
|
||||
- exchange_id (INTEGER FK)
|
||||
- initial_balance (REAL)
|
||||
- current_equity (REAL)
|
||||
- status (TEXT) -- running, stopped
|
||||
- created_at (DATETIME)
|
||||
```
|
||||
|
||||
*(More details: database-schema.md - coming soon)*
|
||||
|
||||
---
|
||||
|
||||
## 🔌 API Reference
|
||||
|
||||
### Authentication
|
||||
|
||||
**POST /api/auth/login**
|
||||
```json
|
||||
Request: {
|
||||
"username": "string",
|
||||
"password": "string",
|
||||
"totp_code": "string" // optional
|
||||
}
|
||||
|
||||
Response: {
|
||||
"token": "jwt_token",
|
||||
"user": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### Trader Management
|
||||
|
||||
**GET /api/traders**
|
||||
```json
|
||||
Response: {
|
||||
"traders": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"status": "running|stopped",
|
||||
"balance": 1000.0,
|
||||
"roi": 5.2
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
*(Full API reference: api-reference.md - coming soon)*
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Architecture
|
||||
|
||||
### Current State
|
||||
- ⚠️ No unit tests yet
|
||||
- ⚠️ Manual testing only
|
||||
- ⚠️ Testnet verification
|
||||
|
||||
### Planned Testing Strategy
|
||||
|
||||
**Unit Tests (Priority 1)**
|
||||
```
|
||||
trader/binance_futures_test.go
|
||||
- Mock API responses
|
||||
- Test precision handling
|
||||
- Validate order construction
|
||||
```
|
||||
|
||||
**Integration Tests (Priority 2)**
|
||||
```
|
||||
- End-to-end trading flow (testnet)
|
||||
- Multi-trader scenarios
|
||||
- Database operations
|
||||
```
|
||||
|
||||
**Frontend Tests (Priority 3)**
|
||||
```
|
||||
- Component tests (Vitest + React Testing Library)
|
||||
- API integration tests
|
||||
- E2E tests (Playwright)
|
||||
```
|
||||
|
||||
*(Testing guide: testing-guide.md - coming soon)*
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Development Tools
|
||||
|
||||
### Build & Run
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
go build -o nofx
|
||||
./nofx
|
||||
|
||||
# Frontend
|
||||
cd web
|
||||
npm run dev
|
||||
|
||||
# Docker
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
# Format Go code
|
||||
go fmt ./...
|
||||
|
||||
# Lint (if configured)
|
||||
golangci-lint run
|
||||
|
||||
# Type check TypeScript
|
||||
cd web && npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Performance Considerations
|
||||
|
||||
### Backend
|
||||
- **Concurrency:** Each trader runs in separate goroutine
|
||||
- **Database:** SQLite (good for <100 traders)
|
||||
- **API Rate Limits:** Handled per exchange
|
||||
- **Memory:** ~50-100MB per trader
|
||||
|
||||
### Frontend
|
||||
- **Data Fetching:** SWR with 5-10s polling
|
||||
- **State:** Zustand (lightweight)
|
||||
- **Bundle Size:** ~500KB (gzipped)
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Future Architecture Plans
|
||||
|
||||
### Planned Improvements
|
||||
|
||||
1. **Microservices Split** (if scaling needed)
|
||||
- Separate decision engine service
|
||||
- Market data service
|
||||
- Execution service
|
||||
|
||||
2. **Database Migration**
|
||||
- Mysql for production (>100 traders)
|
||||
- Redis for caching
|
||||
|
||||
3. **Event-Driven Architecture**
|
||||
- WebSocket for real-time updates
|
||||
- Message queue (RabbitMQ/NATS)
|
||||
|
||||
4. **Kubernetes Deployment**
|
||||
- Helm charts
|
||||
- Auto-scaling
|
||||
- High availability
|
||||
|
||||
---
|
||||
|
||||
## 🆘 For Developers
|
||||
|
||||
**Want to contribute?**
|
||||
- Read [Contributing Guide](../../CONTRIBUTING.md)
|
||||
- Check [Open Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
- Join [Telegram Community](https://t.me/nofx_dev_community)
|
||||
|
||||
**Need clarification?**
|
||||
- Open a [GitHub Discussion](https://github.com/tinkle-community/nofx/discussions)
|
||||
- Ask in Telegram
|
||||
|
||||
---
|
||||
|
||||
## 📚 Related Documentation
|
||||
|
||||
- [Getting Started](../getting-started/README.md) - Setup and deployment
|
||||
- [Contributing](../../CONTRIBUTING.md) - How to contribute
|
||||
- [Community](../community/README.md) - Bounties and recognition
|
||||
|
||||
---
|
||||
|
||||
[← Back to Documentation Home](../README.md)
|
||||
575
docs/architecture/README.zh-CN.md
Normal file
@@ -0,0 +1,575 @@
|
||||
# 🏗️ NOFX 架构文档
|
||||
|
||||
**语言:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
为希望了解 NOFX 内部实现的开发者提供的技术文档。
|
||||
|
||||
---
|
||||
|
||||
## 📋 概述
|
||||
|
||||
NOFX 是一个全栈 AI 交易平台:
|
||||
- **后端:** Go (Gin 框架, SQLite)
|
||||
- **前端:** React/TypeScript (Vite, TailwindCSS)
|
||||
- **架构:** 微服务启发的模块化设计
|
||||
|
||||
---
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
nofx/
|
||||
├── main.go # 程序入口(多交易员管理器)
|
||||
├── config.json # ~~多交易员配置~~ (现通过Web界面)
|
||||
├── trading.db # SQLite 数据库(交易员、模型、交易所)
|
||||
│
|
||||
├── api/ # HTTP API 服务
|
||||
│ └── server.go # Gin 框架,RESTful API
|
||||
│
|
||||
├── trader/ # 交易核心
|
||||
│ ├── auto_trader.go # 自动交易主控制器
|
||||
│ ├── interface.go # 统一交易员接口
|
||||
│ ├── binance_futures.go # Binance API 包装器
|
||||
│ ├── hyperliquid_trader.go # Hyperliquid DEX 包装器
|
||||
│ └── aster_trader.go # Aster DEX 包装器
|
||||
│
|
||||
├── manager/ # 多交易员管理
|
||||
│ └── trader_manager.go # 管理多个交易员实例
|
||||
│
|
||||
├── config/ # 配置与数据库
|
||||
│ └── database.go # SQLite 操作和模式
|
||||
│
|
||||
├── auth/ # 认证
|
||||
│ └── jwt.go # JWT token 管理 & 2FA
|
||||
│
|
||||
├── mcp/ # Model Context Protocol - AI 通信
|
||||
│ └── client.go # AI API 客户端(DeepSeek/Qwen/自定义)
|
||||
│
|
||||
├── decision/ # AI 决策引擎
|
||||
│ ├── engine.go # 带历史反馈的决策逻辑
|
||||
│ └── prompt_manager.go # 提示词模板系统
|
||||
│
|
||||
├── market/ # 市场数据获取
|
||||
│ └── data.go # 市场数据与技术指标(TA-Lib)
|
||||
│ └── api_client.go # 行情获取 Api接口
|
||||
│ └── websocket_client.go # 行情获取 Websocket接口
|
||||
│ └── combined_streams.go # 行情获取 组合流式(单链接订阅多个币种)
|
||||
│ └── monitor.go # 行情数据缓存
|
||||
│ └── types.go # market结构体
|
||||
│
|
||||
├── pool/ # 币种池管理
|
||||
│ └── coin_pool.go # AI500 + OI Top 合并池
|
||||
│
|
||||
├── logger/ # 日志系统
|
||||
│ └── decision_logger.go # 决策记录 + 性能分析
|
||||
│
|
||||
├── decision_logs/ # 决策日志存储(JSON 文件)
|
||||
│ ├── {trader_id}/ # 每个交易员的日志
|
||||
│ └── {timestamp}.json # 单个决策
|
||||
│
|
||||
└── web/ # React 前端
|
||||
├── src/
|
||||
│ ├── components/ # React 组件
|
||||
│ │ ├── EquityChart.tsx # 权益曲线图表
|
||||
│ │ ├── ComparisonChart.tsx # 多 AI 对比图表
|
||||
│ │ └── CompetitionPage.tsx # 竞赛排行榜
|
||||
│ ├── lib/api.ts # API 调用包装器
|
||||
│ ├── types/index.ts # TypeScript 类型
|
||||
│ ├── stores/ # Zustand 状态管理
|
||||
│ ├── index.css # Binance 风格样式
|
||||
│ └── App.tsx # 主应用
|
||||
├── package.json # 前端依赖
|
||||
└── vite.config.ts # Vite 配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 核心依赖
|
||||
|
||||
### 后端 (Go)
|
||||
|
||||
| 包 | 用途 | 版本 |
|
||||
|---------|---------|---------|
|
||||
| `github.com/gin-gonic/gin` | HTTP API 框架 | v1.9+ |
|
||||
| `github.com/adshao/go-binance/v2` | Binance API 客户端 | v2.4+ |
|
||||
| `github.com/markcheno/go-talib` | 技术指标(TA-Lib) | 最新 |
|
||||
| `github.com/mattn/go-sqlite3` | SQLite 数据库驱动 | v1.14+ |
|
||||
| `github.com/golang-jwt/jwt/v5` | JWT 认证 | v5.0+ |
|
||||
| `github.com/pquerna/otp` | 2FA/TOTP 支持 | v1.4+ |
|
||||
| `golang.org/x/crypto` | 密码哈希(bcrypt) | 最新 |
|
||||
|
||||
### 前端 (React + TypeScript)
|
||||
|
||||
| 包 | 用途 | 版本 |
|
||||
|---------|---------|---------|
|
||||
| `react` + `react-dom` | UI 框架 | 18.3+ |
|
||||
| `typescript` | 类型安全 | 5.8+ |
|
||||
| `vite` | 构建工具 | 6.0+ |
|
||||
| `recharts` | 图表(权益、对比) | 2.15+ |
|
||||
| `swr` | 数据获取与缓存 | 2.2+ |
|
||||
| `zustand` | 状态管理 | 5.0+ |
|
||||
| `tailwindcss` | CSS 框架 | 3.4+ |
|
||||
| `lucide-react` | 图标库 | 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ 系统架构
|
||||
|
||||
### 高层架构概览
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 表现层 │
|
||||
│ React SPA (Vite + TypeScript + TailwindCSS) │
|
||||
│ - 竞赛仪表板、交易员管理 UI │
|
||||
│ - 实时图表 (Recharts)、认证页面 │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
↓ HTTP/JSON API
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ API 层 (Gin Router) │
|
||||
│ /api/traders, /api/status, /api/positions, /api/decisions │
|
||||
│ 认证中间件 (JWT)、CORS 处理 │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 业务逻辑层 │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐ │
|
||||
│ │ TraderManager │ │ DecisionEngine │ │ MarketData │ │
|
||||
│ │ - 多交易员 │ │ - AI 推理 │ │ - K线数据 │ │
|
||||
│ │ 编排 │ │ - 风险控制 │ │ - 技术指标 │ │
|
||||
│ └──────────────────┘ └──────────────────┘ └────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 数据访问层 │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
||||
│ │ SQLite DB │ │ 文件日志 │ │ 外部 APIs │ │
|
||||
│ │ - Traders │ │ - Decisions │ │ - Binance │ │
|
||||
│ │ - Models │ │ - Performance│ │ - Hyperliquid │ │
|
||||
│ │ - Exchanges │ │ analysis │ │ - Aster │ │
|
||||
│ └──────────────┘ └──────────────┘ └────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 组件图
|
||||
|
||||
*(即将推出:详细的组件交互图)*
|
||||
|
||||
---
|
||||
|
||||
## 📚 核心模块
|
||||
|
||||
### 1. 交易系统 (`trader/`)
|
||||
|
||||
**用途:** 支持多交易所的交易执行层
|
||||
|
||||
**关键文件:**
|
||||
- `auto_trader.go` - 主交易编排器(100+ 行)
|
||||
- `interface.go` - 统一的交易员接口
|
||||
- `binance_futures.go` - Binance API 包装器
|
||||
- `hyperliquid_trader.go` - Hyperliquid DEX 包装器
|
||||
- `aster_trader.go` - Aster DEX 包装器
|
||||
|
||||
**设计模式:** 基于接口抽象的策略模式
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
type ExchangeClient interface {
|
||||
GetAccount() (*AccountInfo, error)
|
||||
GetPositions() ([]*Position, error)
|
||||
CreateOrder(*OrderParams) (*Order, error)
|
||||
// ... 更多方法
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 决策引擎 (`decision/`)
|
||||
|
||||
**用途:** AI 驱动的交易决策制定
|
||||
|
||||
**关键文件:**
|
||||
- `engine.go` - 带历史反馈的决策逻辑
|
||||
- `prompt_manager.go` - AI 提示词模板系统
|
||||
|
||||
**特性:**
|
||||
- 思维链推理
|
||||
- 历史表现分析
|
||||
- 风险感知决策
|
||||
- 多模型支持(DeepSeek、Qwen、自定义)
|
||||
|
||||
**流程:**
|
||||
```
|
||||
历史数据 → 提示词生成 → AI API 调用 →
|
||||
决策解析 → 风险验证 → 执行
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 市场数据系统 (`market/`)
|
||||
|
||||
**用途:** 获取和分析市场数据
|
||||
|
||||
**关键文件:**
|
||||
- `data.go` - 市场数据获取和技术指标
|
||||
|
||||
**特性:**
|
||||
- 多时间周期 K线数据(3分钟、4小时)
|
||||
- 通过 TA-Lib 计算技术指标:
|
||||
- EMA (20, 50)
|
||||
- MACD
|
||||
- RSI (7, 14)
|
||||
- ATR(波动率)
|
||||
- 持仓量跟踪
|
||||
|
||||
---
|
||||
|
||||
### 4. 管理器 (`manager/`)
|
||||
|
||||
**用途:** 多交易员编排
|
||||
|
||||
**关键文件:**
|
||||
- `trader_manager.go` - 管理多个交易员实例
|
||||
|
||||
**职责:**
|
||||
- 交易员生命周期(启动、停止、重启)
|
||||
- 资源分配
|
||||
- 并发执行协调
|
||||
|
||||
---
|
||||
|
||||
### 5. API 服务器 (`api/`)
|
||||
|
||||
**用途:** 前端通信的 HTTP API
|
||||
|
||||
**关键文件:**
|
||||
- `server.go` - Gin 框架 RESTful API
|
||||
|
||||
**端点:**
|
||||
```
|
||||
GET /api/traders # 列出所有交易员
|
||||
POST /api/traders # 创建交易员
|
||||
POST /api/traders/:id/start # 启动交易员
|
||||
GET /api/status # 系统状态
|
||||
GET /api/positions # 当前持仓
|
||||
GET /api/decisions/latest # 最近决策
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. 数据库层 (`config/`)
|
||||
|
||||
**用途:** SQLite 数据持久化
|
||||
|
||||
**关键文件:**
|
||||
- `database.go` - 数据库操作和模式
|
||||
|
||||
**表:**
|
||||
- `users` - 用户账户(支持 2FA)
|
||||
- `ai_models` - AI 模型配置
|
||||
- `exchanges` - 交易所凭证
|
||||
- `traders` - 交易员实例
|
||||
- `equity_history` - 绩效跟踪
|
||||
- `system_config` - 应用程序设置
|
||||
|
||||
---
|
||||
|
||||
### 7. 认证 (`auth/`)
|
||||
|
||||
**用途:** 用户认证和授权
|
||||
|
||||
**特性:**
|
||||
- 基于 JWT token 的认证
|
||||
- 使用 TOTP 的 2FA(Google Authenticator)
|
||||
- Bcrypt 密码哈希
|
||||
- 管理员模式(简化的单用户模式)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 请求流程示例
|
||||
|
||||
### 示例 1:创建新交易员
|
||||
|
||||
```
|
||||
用户操作(前端)
|
||||
↓
|
||||
POST /api/traders
|
||||
↓
|
||||
API 服务器(认证中间件)
|
||||
↓
|
||||
Database.CreateTrader()
|
||||
↓
|
||||
TraderManager.StartTrader()
|
||||
↓
|
||||
AutoTrader.Run() → goroutine
|
||||
↓
|
||||
响应: {trader_id, status}
|
||||
```
|
||||
|
||||
### 示例 2:交易决策周期
|
||||
|
||||
```
|
||||
AutoTrader(每 3-5 分钟)
|
||||
↓
|
||||
1. FetchAccountStatus()
|
||||
↓
|
||||
2. GetOpenPositions()
|
||||
↓
|
||||
3. FetchMarketData() → TA-Lib 指标
|
||||
↓
|
||||
4. AnalyzeHistory() → 最近 20 笔交易
|
||||
↓
|
||||
5. GeneratePrompt() → 完整上下文
|
||||
↓
|
||||
6. CallAI() → DeepSeek/Qwen
|
||||
↓
|
||||
7. ParseDecision() → 结构化输出
|
||||
↓
|
||||
8. ValidateRisk() → 仓位限制、保证金
|
||||
↓
|
||||
9. ExecuteOrders() → 交易所 API
|
||||
↓
|
||||
10. LogDecision() → JSON 文件 + 数据库
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据流
|
||||
|
||||
### 市场数据流
|
||||
|
||||
```
|
||||
交易所 API
|
||||
↓
|
||||
market.FetchKlines()
|
||||
↓
|
||||
TA-Lib.Calculate(EMA, MACD, RSI)
|
||||
↓
|
||||
DecisionEngine(作为上下文)
|
||||
↓
|
||||
AI 模型(推理)
|
||||
```
|
||||
|
||||
### 决策日志流
|
||||
|
||||
```
|
||||
AI 响应
|
||||
↓
|
||||
decision_logger.go
|
||||
↓
|
||||
JSON 文件: decision_logs/{trader_id}/{timestamp}.json
|
||||
↓
|
||||
数据库: 绩效跟踪
|
||||
↓
|
||||
前端: /api/decisions/latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ 数据库架构
|
||||
|
||||
### 核心表
|
||||
|
||||
**users**
|
||||
```sql
|
||||
- id (INTEGER PRIMARY KEY)
|
||||
- username (TEXT UNIQUE)
|
||||
- password_hash (TEXT)
|
||||
- totp_secret (TEXT)
|
||||
- is_admin (BOOLEAN)
|
||||
- created_at (DATETIME)
|
||||
```
|
||||
|
||||
**ai_models**
|
||||
```sql
|
||||
- id (INTEGER PRIMARY KEY)
|
||||
- name (TEXT)
|
||||
- model_type (TEXT) -- deepseek, qwen, custom
|
||||
- api_key (TEXT)
|
||||
- api_url (TEXT)
|
||||
- enabled (BOOLEAN)
|
||||
```
|
||||
|
||||
**traders**
|
||||
```sql
|
||||
- id (TEXT PRIMARY KEY)
|
||||
- name (TEXT)
|
||||
- ai_model_id (INTEGER FK)
|
||||
- exchange_id (INTEGER FK)
|
||||
- initial_balance (REAL)
|
||||
- current_equity (REAL)
|
||||
- status (TEXT) -- running, stopped
|
||||
- created_at (DATETIME)
|
||||
```
|
||||
|
||||
*(更多详情:database-schema.md - 即将推出)*
|
||||
|
||||
---
|
||||
|
||||
## 🔌 API 参考
|
||||
|
||||
### 认证
|
||||
|
||||
**POST /api/auth/login**
|
||||
```json
|
||||
请求: {
|
||||
"username": "string",
|
||||
"password": "string",
|
||||
"totp_code": "string" // 可选
|
||||
}
|
||||
|
||||
响应: {
|
||||
"token": "jwt_token",
|
||||
"user": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### 交易员管理
|
||||
|
||||
**GET /api/traders**
|
||||
```json
|
||||
响应: {
|
||||
"traders": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"status": "running|stopped",
|
||||
"balance": 1000.0,
|
||||
"roi": 5.2
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
*(完整 API 参考:api-reference.md - 即将推出)*
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试架构
|
||||
|
||||
### 当前状态
|
||||
- ⚠️ 尚无单元测试
|
||||
- ⚠️ 仅手动测试
|
||||
- ⚠️ 测试网验证
|
||||
|
||||
### 计划的测试策略
|
||||
|
||||
**单元测试(优先级 1)**
|
||||
```
|
||||
trader/binance_futures_test.go
|
||||
- 模拟 API 响应
|
||||
- 测试精度处理
|
||||
- 验证订单构造
|
||||
```
|
||||
|
||||
**集成测试(优先级 2)**
|
||||
```
|
||||
- 端到端交易流程(测试网)
|
||||
- 多交易员场景
|
||||
- 数据库操作
|
||||
```
|
||||
|
||||
**前端测试(优先级 3)**
|
||||
```
|
||||
- 组件测试(Vitest + React Testing Library)
|
||||
- API 集成测试
|
||||
- E2E 测试(Playwright)
|
||||
```
|
||||
|
||||
*(测试指南:testing-guide.md - 即将推出)*
|
||||
|
||||
---
|
||||
|
||||
## 🔧 开发工具
|
||||
|
||||
### 构建与运行
|
||||
|
||||
```bash
|
||||
# 后端
|
||||
go build -o nofx
|
||||
./nofx
|
||||
|
||||
# 前端
|
||||
cd web
|
||||
npm run dev
|
||||
|
||||
# Docker
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
### 代码质量
|
||||
|
||||
```bash
|
||||
# 格式化 Go 代码
|
||||
go fmt ./...
|
||||
|
||||
# Lint(如果配置)
|
||||
golangci-lint run
|
||||
|
||||
# TypeScript 类型检查
|
||||
cd web && npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 性能考虑
|
||||
|
||||
### 后端
|
||||
- **并发:** 每个交易员在独立的 goroutine 中运行
|
||||
- **数据库:** SQLite(适用于 <100 个交易员)
|
||||
- **API 速率限制:** 按交易所处理
|
||||
- **内存:** 每个交易员 ~50-100MB
|
||||
|
||||
### 前端
|
||||
- **数据获取:** SWR,5-10 秒轮询
|
||||
- **状态:** Zustand(轻量级)
|
||||
- **包大小:** ~500KB(gzipped)
|
||||
|
||||
---
|
||||
|
||||
## 🔮 未来架构计划
|
||||
|
||||
### 计划改进
|
||||
|
||||
1. **微服务拆分**(如需扩展)
|
||||
- 独立的决策引擎服务
|
||||
- 市场数据服务
|
||||
- 执行服务
|
||||
|
||||
2. **数据库迁移**
|
||||
- 生产环境使用 Mysql (>100 个交易员)
|
||||
- Redis 缓存
|
||||
|
||||
3. **事件驱动架构**
|
||||
- WebSocket 实时更新
|
||||
- 消息队列(RabbitMQ/NATS)
|
||||
|
||||
4. **Kubernetes 部署**
|
||||
- Helm charts
|
||||
- 自动扩展
|
||||
- 高可用性
|
||||
|
||||
---
|
||||
|
||||
## 🆘 开发者资源
|
||||
|
||||
**想要贡献?**
|
||||
- 阅读[贡献指南](../../CONTRIBUTING.md)
|
||||
- 查看[开放问题](https://github.com/tinkle-community/nofx/issues)
|
||||
- 加入 [Telegram 社区](https://t.me/nofx_dev_community)
|
||||
|
||||
**需要澄清?**
|
||||
- 开启 [GitHub 讨论](https://github.com/tinkle-community/nofx/discussions)
|
||||
- 在 Telegram 提问
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [快速开始](../getting-started/README.zh-CN.md) - 设置和部署
|
||||
- [贡献指南](../../CONTRIBUTING.md) - 如何贡献
|
||||
- [社区](../community/README.md) - 悬赏和认可
|
||||
|
||||
---
|
||||
|
||||
[← 返回文档首页](../README.md)
|
||||
272
docs/community/HOW_TO_MIGRATE_YOUR_PR.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# 🔄 How to Migrate Your PR to the New Format
|
||||
|
||||
**Language:** [English](HOW_TO_MIGRATE_YOUR_PR.md) | [中文](HOW_TO_MIGRATE_YOUR_PR.zh-CN.md)
|
||||
|
||||
This guide helps you migrate your existing PR to meet the new PR management system requirements.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Why Migrate?
|
||||
|
||||
While your existing PR **will still be reviewed and merged** under current standards, migrating it to the new format gives you:
|
||||
|
||||
✅ **Faster reviews** - Automated checks catch issues early
|
||||
✅ **Better feedback** - Clear, actionable feedback from CI
|
||||
✅ **Higher quality** - Consistent code standards
|
||||
✅ **Learning** - Understand our new contribution workflow
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Quick Check (Recommended)
|
||||
|
||||
### Step 1: Analyze Your PR
|
||||
|
||||
```bash
|
||||
# Run the PR health check (reads only, doesn't modify anything)
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
This will analyze your PR and tell you:
|
||||
- ✅ What's good
|
||||
- ⚠️ What needs attention
|
||||
- 💡 How to fix issues
|
||||
- 📊 Overall health score
|
||||
|
||||
### Step 2: Fix Issues
|
||||
|
||||
Based on the suggestions, fix the issues manually. Common fixes:
|
||||
|
||||
```bash
|
||||
# Rebase on latest dev
|
||||
git fetch upstream && git rebase upstream/dev
|
||||
|
||||
# Format Go code
|
||||
go fmt ./...
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
|
||||
# Format frontend code
|
||||
cd web && npm run lint -- --fix
|
||||
```
|
||||
|
||||
### Step 3: Run Check Again
|
||||
|
||||
```bash
|
||||
# Verify all issues are fixed
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
### Step 4: Push Changes
|
||||
|
||||
```bash
|
||||
git push -f origin <your-pr-branch>
|
||||
```
|
||||
|
||||
### What the Script Does
|
||||
|
||||
1. ✅ Syncs with latest `upstream/dev`
|
||||
2. ✅ Rebases your changes
|
||||
3. ✅ Formats Go code (`go fmt`)
|
||||
4. ✅ Runs Go linting (`go vet`)
|
||||
5. ✅ Runs tests
|
||||
6. ✅ Formats frontend code (if applicable)
|
||||
7. ✅ Pushes changes to your PR
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Manual Migration (Step by Step)
|
||||
|
||||
If you prefer to do it manually:
|
||||
|
||||
### Step 1: Sync with Upstream
|
||||
|
||||
```bash
|
||||
# Add upstream if not already added
|
||||
git remote add upstream https://github.com/tinkle-community/nofx.git
|
||||
|
||||
# Fetch latest changes
|
||||
git fetch upstream
|
||||
|
||||
# Rebase your branch
|
||||
git checkout <your-pr-branch>
|
||||
git rebase upstream/dev
|
||||
```
|
||||
|
||||
### Step 2: Backend Checks (Go)
|
||||
|
||||
```bash
|
||||
# Format Go code
|
||||
go fmt ./...
|
||||
|
||||
# Run linting
|
||||
go vet ./...
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
|
||||
# If you made changes, commit them
|
||||
git add .
|
||||
git commit -m "chore: format and fix backend issues"
|
||||
```
|
||||
|
||||
### Step 3: Frontend Checks (if applicable)
|
||||
|
||||
```bash
|
||||
cd web
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Fix linting issues
|
||||
npm run lint -- --fix
|
||||
|
||||
# Check types
|
||||
npm run type-check
|
||||
|
||||
# Test build
|
||||
npm run build
|
||||
|
||||
cd ..
|
||||
|
||||
# Commit any fixes
|
||||
git add .
|
||||
git commit -m "chore: fix frontend issues"
|
||||
```
|
||||
|
||||
### Step 4: Update PR Title (if needed)
|
||||
|
||||
Ensure your PR title follows [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
Examples:
|
||||
feat(exchange): add OKX integration
|
||||
fix(trader): resolve position tracking bug
|
||||
docs(readme): update installation guide
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat` - New feature
|
||||
- `fix` - Bug fix
|
||||
- `docs` - Documentation
|
||||
- `refactor` - Code refactoring
|
||||
- `perf` - Performance improvement
|
||||
- `test` - Test updates
|
||||
- `chore` - Build/config changes
|
||||
- `security` - Security improvements
|
||||
|
||||
### Step 5: Push Changes
|
||||
|
||||
```bash
|
||||
git push -f origin <your-pr-branch>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Checklist
|
||||
|
||||
After migrating, verify:
|
||||
|
||||
- [ ] PR is rebased on latest `dev`
|
||||
- [ ] No merge conflicts
|
||||
- [ ] Backend tests pass locally
|
||||
- [ ] Frontend builds successfully
|
||||
- [ ] PR title follows Conventional Commits format
|
||||
- [ ] All commits are meaningful
|
||||
- [ ] Changes pushed to GitHub
|
||||
|
||||
---
|
||||
|
||||
## 🤖 What Happens After Migration?
|
||||
|
||||
After you push your changes:
|
||||
|
||||
1. **Automated checks will run** (they won't block merging, just provide feedback)
|
||||
2. **You'll get a comment** with check results and suggestions
|
||||
3. **Maintainers will review** your PR with the new context
|
||||
4. **Faster review** thanks to pre-checks
|
||||
|
||||
---
|
||||
|
||||
## ❓ Troubleshooting
|
||||
|
||||
### "Rebase conflicts"
|
||||
|
||||
If you get conflicts during rebase:
|
||||
|
||||
```bash
|
||||
# Fix conflicts in your editor
|
||||
# Then:
|
||||
git add <fixed-files>
|
||||
git rebase --continue
|
||||
|
||||
# Or abort and ask for help:
|
||||
git rebase --abort
|
||||
```
|
||||
|
||||
**Need help?** Just comment on your PR and we'll assist!
|
||||
|
||||
### "Tests failing"
|
||||
|
||||
If tests fail:
|
||||
|
||||
```bash
|
||||
# Run tests to see the error
|
||||
go test ./...
|
||||
|
||||
# Fix the issue
|
||||
# Then commit and push
|
||||
git add .
|
||||
git commit -m "fix: resolve test failures"
|
||||
git push -f origin <your-pr-branch>
|
||||
```
|
||||
|
||||
### "Script not working"
|
||||
|
||||
If the migration script doesn't work:
|
||||
|
||||
1. Check you have Go and Node.js installed
|
||||
2. Try manual migration (steps above)
|
||||
3. Ask for help in your PR comments
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
**Don't want to migrate?**
|
||||
- That's okay! Your PR will still be reviewed and merged
|
||||
- Migration is optional but recommended
|
||||
|
||||
**First time using Git rebase?**
|
||||
- Check our [Git guide](https://git-scm.com/book/en/v2/Git-Branching-Rebasing)
|
||||
- Ask questions in your PR - we're here to help!
|
||||
|
||||
**Want to learn more?**
|
||||
- [Contributing Guidelines](../../CONTRIBUTING.md)
|
||||
- [Migration Announcement](MIGRATION_ANNOUNCEMENT.md)
|
||||
- [PR Review Guide](../maintainers/PR_REVIEW_GUIDE.md)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Need Help?
|
||||
|
||||
**Stuck on migration?**
|
||||
- Comment on your PR
|
||||
- Ask in [Telegram](https://t.me/nofx_dev_community)
|
||||
- Open a [Discussion](https://github.com/tinkle-community/nofx/discussions)
|
||||
|
||||
**We're here to help you succeed!** 🚀
|
||||
|
||||
---
|
||||
|
||||
## 🎉 After Migration
|
||||
|
||||
Once migrated:
|
||||
1. ✅ Wait for automated checks to run
|
||||
2. ✅ Address any feedback in comments
|
||||
3. ✅ Wait for maintainer review
|
||||
4. ✅ Celebrate when merged! 🎉
|
||||
|
||||
**Thank you for contributing to NOFX!**
|
||||
272
docs/community/HOW_TO_MIGRATE_YOUR_PR.zh-CN.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# 🔄 如何将你的 PR 迁移到新格式
|
||||
|
||||
**语言:** [English](HOW_TO_MIGRATE_YOUR_PR.md) | [中文](HOW_TO_MIGRATE_YOUR_PR.zh-CN.md)
|
||||
|
||||
本指南帮助你将现有 PR 迁移以满足新的 PR 管理系统要求。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 为什么要迁移?
|
||||
|
||||
虽然你的现有 PR **仍将按照当前标准审核和合并**,但将其迁移到新格式可以获得:
|
||||
|
||||
✅ **更快的审核** - 自动化检查尽早捕获问题
|
||||
✅ **更好的反馈** - CI 提供清晰、可操作的反馈
|
||||
✅ **更高质量** - 一致的代码标准
|
||||
✅ **学习机会** - 了解我们新的贡献工作流程
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 快速检查(推荐)
|
||||
|
||||
### 步骤 1:分析你的 PR
|
||||
|
||||
```bash
|
||||
# 运行 PR 健康检查(只读,不修改任何内容)
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
这将分析你的 PR 并告诉你:
|
||||
- ✅ 什么是好的
|
||||
- ⚠️ 什么需要注意
|
||||
- 💡 如何修复问题
|
||||
- 📊 整体健康评分
|
||||
|
||||
### 步骤 2:修复问题
|
||||
|
||||
根据建议,手动修复问题。常见修复:
|
||||
|
||||
```bash
|
||||
# Rebase 到最新 dev
|
||||
git fetch upstream && git rebase upstream/dev
|
||||
|
||||
# 格式化 Go 代码
|
||||
go fmt ./...
|
||||
|
||||
# 运行测试
|
||||
go test ./...
|
||||
|
||||
# 格式化前端代码
|
||||
cd web && npm run lint -- --fix
|
||||
```
|
||||
|
||||
### 步骤 3:再次运行检查
|
||||
|
||||
```bash
|
||||
# 验证所有问题都已修复
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
### 步骤 4:推送更改
|
||||
|
||||
```bash
|
||||
git push -f origin <your-pr-branch>
|
||||
```
|
||||
|
||||
### 脚本做什么
|
||||
|
||||
1. ✅ 与最新的 `upstream/dev` 同步
|
||||
2. ✅ Rebase 你的更改
|
||||
3. ✅ 格式化 Go 代码(`go fmt`)
|
||||
4. ✅ 运行 Go linting(`go vet`)
|
||||
5. ✅ 运行测试
|
||||
6. ✅ 格式化前端代码(如果适用)
|
||||
7. ✅ 推送更改到你的 PR
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 手动迁移(逐步指南)
|
||||
|
||||
如果你更喜欢手动操作:
|
||||
|
||||
### 步骤 1:与 Upstream 同步
|
||||
|
||||
```bash
|
||||
# 如果还没添加 upstream,添加它
|
||||
git remote add upstream https://github.com/tinkle-community/nofx.git
|
||||
|
||||
# 获取最新更改
|
||||
git fetch upstream
|
||||
|
||||
# Rebase 你的分支
|
||||
git checkout <your-pr-branch>
|
||||
git rebase upstream/dev
|
||||
```
|
||||
|
||||
### 步骤 2:后端检查(Go)
|
||||
|
||||
```bash
|
||||
# 格式化 Go 代码
|
||||
go fmt ./...
|
||||
|
||||
# 运行 linting
|
||||
go vet ./...
|
||||
|
||||
# 运行测试
|
||||
go test ./...
|
||||
|
||||
# 如果有更改,提交它们
|
||||
git add .
|
||||
git commit -m "chore: format and fix backend issues"
|
||||
```
|
||||
|
||||
### 步骤 3:前端检查(如果适用)
|
||||
|
||||
```bash
|
||||
cd web
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 修复 linting 问题
|
||||
npm run lint -- --fix
|
||||
|
||||
# 检查类型
|
||||
npm run type-check
|
||||
|
||||
# 测试构建
|
||||
npm run build
|
||||
|
||||
cd ..
|
||||
|
||||
# 提交任何修复
|
||||
git add .
|
||||
git commit -m "chore: fix frontend issues"
|
||||
```
|
||||
|
||||
### 步骤 4:更新 PR 标题(如果需要)
|
||||
|
||||
确保你的 PR 标题遵循 [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
示例:
|
||||
feat(exchange): add OKX integration
|
||||
fix(trader): resolve position tracking bug
|
||||
docs(readme): update installation guide
|
||||
```
|
||||
|
||||
**类型:**
|
||||
- `feat` - 新功能
|
||||
- `fix` - Bug 修复
|
||||
- `docs` - 文档
|
||||
- `refactor` - 代码重构
|
||||
- `perf` - 性能改进
|
||||
- `test` - 测试更新
|
||||
- `chore` - 构建/配置更改
|
||||
- `security` - 安全改进
|
||||
|
||||
### 步骤 5:推送更改
|
||||
|
||||
```bash
|
||||
git push -f origin <your-pr-branch>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 检查清单
|
||||
|
||||
迁移后,验证:
|
||||
|
||||
- [ ] PR 已基于最新 `dev` rebase
|
||||
- [ ] 没有合并冲突
|
||||
- [ ] 后端测试在本地通过
|
||||
- [ ] 前端构建成功
|
||||
- [ ] PR 标题遵循 Conventional Commits 格式
|
||||
- [ ] 所有 commit 都有意义
|
||||
- [ ] 更改已推送到 GitHub
|
||||
|
||||
---
|
||||
|
||||
## 🤖 迁移后会发生什么?
|
||||
|
||||
推送更改后:
|
||||
|
||||
1. **自动化检查将运行**(不会阻止合并,只提供反馈)
|
||||
2. **你将收到评论**,包含检查结果和建议
|
||||
3. **维护者将审核** 你的 PR,有了新的上下文
|
||||
4. **更快的审核** 得益于预检查
|
||||
|
||||
---
|
||||
|
||||
## ❓ 故障排除
|
||||
|
||||
### "Rebase 冲突"
|
||||
|
||||
如果在 rebase 期间遇到冲突:
|
||||
|
||||
```bash
|
||||
# 在编辑器中修复冲突
|
||||
# 然后:
|
||||
git add <fixed-files>
|
||||
git rebase --continue
|
||||
|
||||
# 或中止并寻求帮助:
|
||||
git rebase --abort
|
||||
```
|
||||
|
||||
**需要帮助?** 在你的 PR 中评论,我们会协助!
|
||||
|
||||
### "测试失败"
|
||||
|
||||
如果测试失败:
|
||||
|
||||
```bash
|
||||
# 运行测试查看错误
|
||||
go test ./...
|
||||
|
||||
# 修复问题
|
||||
# 然后提交并推送
|
||||
git add .
|
||||
git commit -m "fix: resolve test failures"
|
||||
git push -f origin <your-pr-branch>
|
||||
```
|
||||
|
||||
### "脚本不工作"
|
||||
|
||||
如果迁移脚本不工作:
|
||||
|
||||
1. 检查你是否安装了 Go 和 Node.js
|
||||
2. 尝试手动迁移(上面的步骤)
|
||||
3. 在你的 PR 评论中寻求帮助
|
||||
|
||||
---
|
||||
|
||||
## 💡 提示
|
||||
|
||||
**不想迁移?**
|
||||
- 没关系!你的 PR 仍将被审核和合并
|
||||
- 迁移是可选的但推荐的
|
||||
|
||||
**第一次使用 Git rebase?**
|
||||
- 查看我们的 [Git 指南](https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA)
|
||||
- 在你的 PR 中提问 - 我们在这里帮助!
|
||||
|
||||
**想了解更多?**
|
||||
- [贡献指南](../../docs/i18n/zh-CN/CONTRIBUTING.md)
|
||||
- [迁移公告](MIGRATION_ANNOUNCEMENT.zh-CN.md)
|
||||
- [PR 审核指南](../maintainers/PR_REVIEW_GUIDE.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 📞 需要帮助?
|
||||
|
||||
**迁移遇到困难?**
|
||||
- 在你的 PR 中评论
|
||||
- 在 [Telegram](https://t.me/nofx_dev_community) 提问
|
||||
- 开启 [Discussion](https://github.com/tinkle-community/nofx/discussions)
|
||||
|
||||
**我们在这里帮助你成功!** 🚀
|
||||
|
||||
---
|
||||
|
||||
## 🎉 迁移后
|
||||
|
||||
迁移完成后:
|
||||
1. ✅ 等待自动化检查运行
|
||||
2. ✅ 处理评论中的任何反馈
|
||||
3. ✅ 等待维护者审核
|
||||
4. ✅ 合并时庆祝!🎉
|
||||
|
||||
**感谢你为 NOFX 做出贡献!**
|
||||
358
docs/community/MIGRATION_ANNOUNCEMENT.md
Normal file
@@ -0,0 +1,358 @@
|
||||
# 📢 PR Management System Update - What Contributors Need to Know
|
||||
|
||||
**Language:** [English](MIGRATION_ANNOUNCEMENT.md) | [中文](MIGRATION_ANNOUNCEMENT.zh-CN.md)
|
||||
|
||||
We're introducing a new PR management system to improve code quality and make contributing easier! This guide explains what's changing and what you need to do.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 What's Changing?
|
||||
|
||||
We're introducing:
|
||||
|
||||
✅ **Clear contribution guidelines** aligned with our [roadmap](../roadmap/README.md)
|
||||
✅ **Automated checks** (tests, linting, security scans)
|
||||
✅ **Better labeling** for organization and prioritization
|
||||
✅ **Faster review turnaround** with pre-checks
|
||||
✅ **Transparent process** so you know exactly what to expect
|
||||
|
||||
---
|
||||
|
||||
## 📅 Timeline
|
||||
|
||||
```
|
||||
Week 1-2: Existing PR Review Period
|
||||
Week 3: Soft Launch (checks are advisory only)
|
||||
Week 4+: Full Launch (checks are required)
|
||||
```
|
||||
|
||||
**Important:** This rollout is gradual. You'll have time to adapt!
|
||||
|
||||
---
|
||||
|
||||
## 🤔 What This Means for YOU
|
||||
|
||||
### If You Have an Existing Open PR
|
||||
|
||||
**Good news:** Your PR will NOT be blocked by new rules!
|
||||
|
||||
- ✅ Your PR will be reviewed under current (relaxed) standards
|
||||
- ✅ We'll review and provide feedback within 1-2 weeks
|
||||
- ✅ Some PRs may need a quick rebase or minor updates
|
||||
|
||||
**What you might need to do:**
|
||||
1. **Rebase on latest `dev` branch** if there are conflicts
|
||||
2. **Respond to review comments** within 1 week
|
||||
3. **Be patient** as we work through the backlog
|
||||
|
||||
**What happens if I don't respond?**
|
||||
- We may close your PR after 2 weeks of inactivity
|
||||
- You can always reopen it later with updates!
|
||||
- No hard feelings - we're just cleaning up the backlog
|
||||
|
||||
### 🚀 Want to Check Your PR? (Optional)
|
||||
|
||||
We've created a **PR health check tool** to help you see if your PR meets the new standards!
|
||||
|
||||
**Run this in your local fork:**
|
||||
```bash
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- 🔍 Analyzes your PR (doesn't modify anything)
|
||||
- ✅ Shows what's good
|
||||
- ⚠️ Points out issues
|
||||
- 💡 Gives you specific fix suggestions
|
||||
- 📊 Overall health score
|
||||
|
||||
**Then fix issues and push:**
|
||||
```bash
|
||||
# Fix the issues (see suggestions from script)
|
||||
# Run check again
|
||||
./scripts/pr-check.sh
|
||||
|
||||
# Push when ready
|
||||
git push -f origin <your-branch>
|
||||
```
|
||||
|
||||
**📖 Full Guide:** [How to Migrate Your PR](HOW_TO_MIGRATE_YOUR_PR.md)
|
||||
|
||||
**Remember:** This is completely **optional** for existing PRs!
|
||||
|
||||
---
|
||||
|
||||
### If You're Submitting a NEW PR
|
||||
|
||||
**Timeline matters:**
|
||||
|
||||
#### Week 3 (Soft Launch):
|
||||
- ✅ Automated checks will run (tests, linting, security)
|
||||
- ⚠️ **Checks are advisory only** - they won't block your PR
|
||||
- ✅ This is a learning period - we're here to help!
|
||||
- ✅ Get familiar with the new [Contributing Guidelines](../../CONTRIBUTING.md)
|
||||
|
||||
#### Week 4+ (Full Launch):
|
||||
- ✅ All automated checks must pass before merge
|
||||
- ✅ PR must follow [Conventional Commits](https://www.conventionalcommits.org/) format
|
||||
- ✅ PR template must be filled out
|
||||
- ✅ Must align with [roadmap](../roadmap/README.md) priorities
|
||||
|
||||
---
|
||||
|
||||
## ✅ How to Prepare for New System
|
||||
|
||||
### 1. Read the Contributing Guidelines
|
||||
|
||||
📖 [CONTRIBUTING.md](../../CONTRIBUTING.md)
|
||||
|
||||
**Key points:**
|
||||
- We accept PRs aligned with our roadmap (security, AI, exchanges, UI/UX)
|
||||
- PRs should be focused and small (<300 lines preferred)
|
||||
- Use Conventional Commits format: `feat(area): description`
|
||||
- Include tests for new features
|
||||
|
||||
### 2. Check the Roadmap
|
||||
|
||||
🗺️ [Roadmap](../roadmap/README.md)
|
||||
|
||||
**Current priorities (Phase 1):**
|
||||
- 🔒 Security enhancements
|
||||
- 🧠 AI model integrations
|
||||
- 🔗 Exchange integrations (OKX, Bybit, Lighter, EdgeX)
|
||||
- 🎨 UI/UX improvements
|
||||
- ⚡ Performance optimizations
|
||||
- 🐛 Bug fixes
|
||||
|
||||
**Lower priority (Phase 2+):**
|
||||
- Universal market expansion (stocks, futures)
|
||||
- Advanced AI features
|
||||
- Enterprise features
|
||||
|
||||
💡 **Pro tip:** If your PR aligns with Phase 1, it'll be reviewed faster!
|
||||
|
||||
### 3. Set Up Local Testing
|
||||
|
||||
Before submitting a PR, test locally:
|
||||
|
||||
```bash
|
||||
# Backend tests
|
||||
go test ./...
|
||||
go fmt ./...
|
||||
go vet ./...
|
||||
|
||||
# Frontend tests
|
||||
cd web
|
||||
npm run lint
|
||||
npm run type-check
|
||||
npm run build
|
||||
```
|
||||
|
||||
This helps your PR pass automated checks on first try!
|
||||
|
||||
---
|
||||
|
||||
## 📝 PR Title Format
|
||||
|
||||
Use [Conventional Commits](https://www.conventionalcommits.org/) format:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
Examples:
|
||||
feat(exchange): add OKX futures support
|
||||
fix(trader): resolve position tracking bug
|
||||
docs(readme): update installation instructions
|
||||
perf(ai): optimize prompt generation
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat` - New feature
|
||||
- `fix` - Bug fix
|
||||
- `docs` - Documentation
|
||||
- `refactor` - Code refactoring
|
||||
- `perf` - Performance improvement
|
||||
- `test` - Test updates
|
||||
- `chore` - Build/config changes
|
||||
- `security` - Security improvements
|
||||
|
||||
---
|
||||
|
||||
## 🎯 What Makes a Good PR?
|
||||
|
||||
### ✅ Good PR Example
|
||||
|
||||
```
|
||||
Title: feat(exchange): add OKX exchange integration
|
||||
|
||||
Description:
|
||||
Implements OKX exchange support with the following features:
|
||||
- Order placement and cancellation
|
||||
- Balance and position retrieval
|
||||
- Leverage configuration
|
||||
- Error handling and retry logic
|
||||
|
||||
Closes #123
|
||||
|
||||
Testing:
|
||||
- [x] Unit tests added and passing
|
||||
- [x] Manually tested with real API
|
||||
- [x] Documentation updated
|
||||
```
|
||||
|
||||
**Why it's good:**
|
||||
- ✅ Clear, descriptive title
|
||||
- ✅ Explains what and why
|
||||
- ✅ Links to issue
|
||||
- ✅ Includes testing details
|
||||
- ✅ Small, focused change
|
||||
|
||||
### ❌ Avoid These
|
||||
|
||||
**Too vague:**
|
||||
```
|
||||
Title: update code
|
||||
Description: made some changes
|
||||
```
|
||||
|
||||
**Too large:**
|
||||
```
|
||||
Title: feat: complete rewrite of entire trading system
|
||||
Files changed: 2,500+
|
||||
```
|
||||
|
||||
**Off roadmap:**
|
||||
```
|
||||
Title: feat: add support for stock trading
|
||||
(This is Phase 3, not current priority)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 If Your PR Fails Checks
|
||||
|
||||
Don't panic! We're here to help.
|
||||
|
||||
**Week 3 (Soft Launch):**
|
||||
- Checks are advisory - we'll help you fix issues
|
||||
- Ask questions in your PR comments
|
||||
- We can guide you through debugging
|
||||
|
||||
**Week 4+ (Full Launch):**
|
||||
- Checks must pass, but we still help!
|
||||
- Common issues:
|
||||
- Test failures → Run `go test ./...` locally
|
||||
- Linting errors → Run `go fmt` and `npm run lint`
|
||||
- Merge conflicts → Rebase on latest `dev`
|
||||
|
||||
**Need help?** Just ask! Comment in your PR or reach out:
|
||||
- [GitHub Discussions](https://github.com/tinkle-community/nofx/discussions)
|
||||
- [Telegram Community](https://t.me/nofx_dev_community)
|
||||
|
||||
---
|
||||
|
||||
## 💰 Special Note for Bounty Contributors
|
||||
|
||||
If you're working on a bounty:
|
||||
|
||||
✅ **Your PRs get priority review** (24-48 hours)
|
||||
✅ **Extra support** to meet requirements
|
||||
✅ **Flexible during transition** - we'll work with you
|
||||
|
||||
Just make sure to:
|
||||
- Reference the bounty issue number
|
||||
- Meet all acceptance criteria
|
||||
- Include demo video/screenshots
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
### Q: Will my existing PR be rejected?
|
||||
|
||||
**A:** No! Existing PRs use relaxed standards. We may ask for minor updates (rebase, small fixes), but you won't be held to new strict requirements.
|
||||
|
||||
### Q: What if I can't pass the new CI checks?
|
||||
|
||||
**A:** Week 3 is a learning period. We'll help you understand and fix issues. By Week 4, you'll be familiar with the process!
|
||||
|
||||
### Q: Will this slow down contributions?
|
||||
|
||||
**A:** Actually, no! Automated checks catch issues early, making reviews faster. Clear guidelines help you submit better PRs on first try.
|
||||
|
||||
### Q: Can I still contribute if I'm a beginner?
|
||||
|
||||
**A:** Absolutely! Look for issues labeled `good first issue`. We're here to mentor and help you succeed.
|
||||
|
||||
### Q: My PR is large (>1000 lines). What should I do?
|
||||
|
||||
**A:** Consider breaking it into smaller PRs. This gets you:
|
||||
- ✅ Faster reviews
|
||||
- ✅ Easier testing
|
||||
- ✅ Higher chance of quick merge
|
||||
|
||||
Need help planning? Just ask in your PR!
|
||||
|
||||
### Q: What if my feature isn't on the roadmap?
|
||||
|
||||
**A:** Open an issue first to discuss! We're open to good ideas, but want to ensure alignment before you spend time coding.
|
||||
|
||||
### Q: When will this be fully active?
|
||||
|
||||
**A:** Week 4+ (approximately 4 weeks from announcement date). Check the pinned Discussion post for exact dates.
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Benefits for Contributors
|
||||
|
||||
This new system helps YOU by:
|
||||
|
||||
✅ **Faster reviews** - Automated pre-checks reduce review time
|
||||
✅ **Clear expectations** - You know exactly what's required
|
||||
✅ **Better feedback** - Automated checks catch issues early
|
||||
✅ **Fair prioritization** - Roadmap-aligned PRs reviewed faster
|
||||
✅ **Recognition** - Contributor tiers and recognition program
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
### Must Read
|
||||
- [Contributing Guidelines](../../CONTRIBUTING.md) - Complete guide
|
||||
- [Roadmap](../roadmap/README.md) - Current priorities
|
||||
|
||||
### Helpful Links
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/) - Commit format
|
||||
- [Good First Issues](https://github.com/tinkle-community/nofx/labels/good%20first%20issue) - Beginner-friendly tasks
|
||||
- [Bounty Program](../bounty-guide.md) - Get paid to contribute
|
||||
|
||||
### Get Help
|
||||
- [GitHub Discussions](https://github.com/tinkle-community/nofx/discussions) - Ask questions
|
||||
- [Telegram](https://t.me/nofx_dev_community) - Community chat
|
||||
- [Twitter](https://x.com/nofx_ai) - Updates and announcements
|
||||
|
||||
---
|
||||
|
||||
## 💬 Feedback Welcome!
|
||||
|
||||
This is a new system and we want YOUR input:
|
||||
|
||||
- 📝 What's unclear?
|
||||
- 🤔 What concerns do you have?
|
||||
- 💡 How can we improve?
|
||||
|
||||
Share in the [Migration Feedback Discussion](https://github.com/tinkle-community/nofx/discussions) (link TBD)
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Thank You!
|
||||
|
||||
We appreciate your contributions and patience during this transition. Together, we're building something amazing!
|
||||
|
||||
**Questions?** Don't hesitate to ask. We're here to help! 🚀
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-01-XX
|
||||
**Status:** Announcement (Week 0)
|
||||
**Full Launch:** Week 4+ (TBD)
|
||||
358
docs/community/MIGRATION_ANNOUNCEMENT.zh-CN.md
Normal file
@@ -0,0 +1,358 @@
|
||||
# 📢 PR 管理系统更新 - 贡献者须知
|
||||
|
||||
**语言:** [English](MIGRATION_ANNOUNCEMENT.md) | [中文](MIGRATION_ANNOUNCEMENT.zh-CN.md)
|
||||
|
||||
我们正在引入新的 PR 管理系统,以提高代码质量并让贡献变得更容易!本指南解释了变化内容以及你需要做什么。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 有什么变化?
|
||||
|
||||
我们正在引入:
|
||||
|
||||
✅ **清晰的贡献指南** 与我们的[路线图](../roadmap/README.zh-CN.md)对齐
|
||||
✅ **自动化检查**(测试、linting、安全扫描)
|
||||
✅ **更好的标签** 用于组织和优先级排序
|
||||
✅ **更快的审核周转** 通过预检查
|
||||
✅ **透明的流程** 让你准确知道期望什么
|
||||
|
||||
---
|
||||
|
||||
## 📅 时间表
|
||||
|
||||
```
|
||||
第 1-2 周:现有 PR 审核期
|
||||
第 3 周: 软启动(检查仅是建议性的)
|
||||
第 4 周+: 完全启动(检查是必需的)
|
||||
```
|
||||
|
||||
**重要:** 这个推出是渐进式的。你将有时间适应!
|
||||
|
||||
---
|
||||
|
||||
## 🤔 这对你意味着什么
|
||||
|
||||
### 如果你有现有的打开的 PR
|
||||
|
||||
**好消息:** 你的 PR 不会被新规则阻塞!
|
||||
|
||||
- ✅ 你的 PR 将按照当前(宽松)标准进行审核
|
||||
- ✅ 我们将在 1-2 周内审核并提供反馈
|
||||
- ✅ 一些 PR 可能需要快速 rebase 或次要更新
|
||||
|
||||
**你可能需要做什么:**
|
||||
1. **基于最新 `dev` 分支 rebase** 如果有冲突
|
||||
2. **在 1 周内回应审核评论**
|
||||
3. **保持耐心** 我们正在处理积压
|
||||
|
||||
**如果我不回应会怎样?**
|
||||
- 我们可能会在 2 周不活动后关闭你的 PR
|
||||
- 你随时可以稍后重新打开并更新!
|
||||
- 没有恶意 - 我们只是在清理积压
|
||||
|
||||
### 🚀 想要检查你的 PR?(可选)
|
||||
|
||||
我们创建了一个 **PR 健康检查工具**来帮助你看 PR 是否符合新标准!
|
||||
|
||||
**在你的本地 fork 中运行:**
|
||||
```bash
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
**它做什么:**
|
||||
- 🔍 分析你的 PR(不修改任何内容)
|
||||
- ✅ 显示什么是好的
|
||||
- ⚠️ 指出问题
|
||||
- 💡 给你具体的修复建议
|
||||
- 📊 整体健康评分
|
||||
|
||||
**然后修复问题并推送:**
|
||||
```bash
|
||||
# 修复问题(查看脚本的建议)
|
||||
# 再次运行检查
|
||||
./scripts/pr-check.sh
|
||||
|
||||
# 准备好后推送
|
||||
git push -f origin <your-branch>
|
||||
```
|
||||
|
||||
**📖 完整指南:** [如何迁移你的 PR](HOW_TO_MIGRATE_YOUR_PR.zh-CN.md)
|
||||
|
||||
**记住:** 对于现有 PR,这是完全**可选的**!
|
||||
|
||||
---
|
||||
|
||||
### 如果你要提交新的 PR
|
||||
|
||||
**时间很重要:**
|
||||
|
||||
#### 第 3 周(软启动):
|
||||
- ✅ 自动化检查将运行(测试、linting、安全性)
|
||||
- ⚠️ **检查仅是建议性的** - 不会阻塞你的 PR
|
||||
- ✅ 这是一个学习期 - 我们在这里帮助!
|
||||
- ✅ 熟悉新的[贡献指南](../../docs/i18n/zh-CN/CONTRIBUTING.md)
|
||||
|
||||
#### 第 4 周+(完全启动):
|
||||
- ✅ 所有自动化检查必须通过才能合并
|
||||
- ✅ PR 必须遵循 [Conventional Commits](https://www.conventionalcommits.org/) 格式
|
||||
- ✅ 必须填写 PR 模板
|
||||
- ✅ 必须与[路线图](../roadmap/README.zh-CN.md)优先级对齐
|
||||
|
||||
---
|
||||
|
||||
## ✅ 如何为新系统做准备
|
||||
|
||||
### 1. 阅读贡献指南
|
||||
|
||||
📖 [CONTRIBUTING.md](../../docs/i18n/zh-CN/CONTRIBUTING.md)
|
||||
|
||||
**关键点:**
|
||||
- 我们接受与路线图对齐的 PR(安全性、AI、交易所、UI/UX)
|
||||
- PR 应该集中且小型(<300 行优先)
|
||||
- 使用 Conventional Commits 格式:`feat(area): description`
|
||||
- 为新功能包含测试
|
||||
|
||||
### 2. 查看路线图
|
||||
|
||||
🗺️ [路线图](../roadmap/README.zh-CN.md)
|
||||
|
||||
**当前优先级(Phase 1):**
|
||||
- 🔒 安全增强
|
||||
- 🧠 AI 模型集成
|
||||
- 🔗 交易所集成(OKX、Bybit、Lighter、EdgeX)
|
||||
- 🎨 UI/UX 改进
|
||||
- ⚡ 性能优化
|
||||
- 🐛 Bug 修复
|
||||
|
||||
**较低优先级(Phase 2+):**
|
||||
- 通用市场扩展(股票、期货)
|
||||
- 高级 AI 功能
|
||||
- 企业功能
|
||||
|
||||
💡 **专业提示:** 如果你的 PR 与 Phase 1 对齐,它会被更快审核!
|
||||
|
||||
### 3. 设置本地测试
|
||||
|
||||
提交 PR 前,在本地测试:
|
||||
|
||||
```bash
|
||||
# 后端测试
|
||||
go test ./...
|
||||
go fmt ./...
|
||||
go vet ./...
|
||||
|
||||
# 前端测试
|
||||
cd web
|
||||
npm run lint
|
||||
npm run type-check
|
||||
npm run build
|
||||
```
|
||||
|
||||
这有助于你的 PR 第一次就通过自动化检查!
|
||||
|
||||
---
|
||||
|
||||
## 📝 PR 标题格式
|
||||
|
||||
使用 [Conventional Commits](https://www.conventionalcommits.org/) 格式:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
示例:
|
||||
feat(exchange): add OKX futures support
|
||||
fix(trader): resolve position tracking bug
|
||||
docs(readme): update installation instructions
|
||||
perf(ai): optimize prompt generation
|
||||
```
|
||||
|
||||
**类型:**
|
||||
- `feat` - 新功能
|
||||
- `fix` - Bug 修复
|
||||
- `docs` - 文档
|
||||
- `refactor` - 代码重构
|
||||
- `perf` - 性能改进
|
||||
- `test` - 测试更新
|
||||
- `chore` - 构建/配置变更
|
||||
- `security` - 安全改进
|
||||
|
||||
---
|
||||
|
||||
## 🎯 什么是好的 PR?
|
||||
|
||||
### ✅ 好的 PR 示例
|
||||
|
||||
```
|
||||
标题:feat(exchange): add OKX exchange integration
|
||||
|
||||
描述:
|
||||
使用以下功能实现 OKX 交易所支持:
|
||||
- 订单下达和取消
|
||||
- 余额和仓位检索
|
||||
- 杠杆配置
|
||||
- 错误处理和重试逻辑
|
||||
|
||||
关闭 #123
|
||||
|
||||
测试:
|
||||
- [x] 单元测试已添加并通过
|
||||
- [x] 使用真实 API 手动测试
|
||||
- [x] 文档已更新
|
||||
```
|
||||
|
||||
**为什么好:**
|
||||
- ✅ 清晰、描述性标题
|
||||
- ✅ 解释了什么和为什么
|
||||
- ✅ 链接到 issue
|
||||
- ✅ 包含测试详情
|
||||
- ✅ 小型、集中的变更
|
||||
|
||||
### ❌ 避免这些
|
||||
|
||||
**太模糊:**
|
||||
```
|
||||
标题:update code
|
||||
描述:made some changes
|
||||
```
|
||||
|
||||
**太大:**
|
||||
```
|
||||
标题:feat: complete rewrite of entire trading system
|
||||
文件变更:2,500+
|
||||
```
|
||||
|
||||
**不在路线图上:**
|
||||
```
|
||||
标题:feat: add support for stock trading
|
||||
(这是 Phase 3,不是当前优先级)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 如果你的 PR 检查失败
|
||||
|
||||
不要恐慌!我们在这里帮助。
|
||||
|
||||
**第 3 周(软启动):**
|
||||
- 检查是建议性的 - 我们会帮你解决问题
|
||||
- 在你的 PR 评论中提问
|
||||
- 我们可以指导你进行调试
|
||||
|
||||
**第 4 周+(完全启动):**
|
||||
- 检查必须通过,但我们仍然会帮助!
|
||||
- 常见问题:
|
||||
- 测试失败 → 在本地运行 `go test ./...`
|
||||
- Linting 错误 → 运行 `go fmt` 和 `npm run lint`
|
||||
- 合并冲突 → 基于最新 `dev` rebase
|
||||
|
||||
**需要帮助?** 只管问!在你的 PR 中评论或联系:
|
||||
- [GitHub Discussions](https://github.com/tinkle-community/nofx/discussions)
|
||||
- [Telegram 社区](https://t.me/nofx_dev_community)
|
||||
|
||||
---
|
||||
|
||||
## 💰 悬赏贡献者特别说明
|
||||
|
||||
如果你正在做悬赏任务:
|
||||
|
||||
✅ **你的 PR 获得优先审核**(24-48 小时)
|
||||
✅ **额外支持** 以满足要求
|
||||
✅ **过渡期间灵活** - 我们会与你合作
|
||||
|
||||
只需确保:
|
||||
- 引用悬赏 issue 编号
|
||||
- 满足所有验收标准
|
||||
- 包含演示视频/截图
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q:我的现有 PR 会被拒绝吗?
|
||||
|
||||
**A:** 不会!现有 PR 使用宽松标准。我们可能会要求次要更新(rebase、小修复),但你不会被要求满足新的严格要求。
|
||||
|
||||
### Q:如果我无法通过新的 CI 检查怎么办?
|
||||
|
||||
**A:** 第 3 周是学习期。我们会帮你理解和修复问题。到第 4 周,你将熟悉这个流程!
|
||||
|
||||
### Q:这会减慢贡献速度吗?
|
||||
|
||||
**A:** 实际上不会!自动化检查尽早捕获问题,使审核更快。清晰的指南帮助你第一次就提交更好的 PR。
|
||||
|
||||
### Q:如果我是初学者,我还能贡献吗?
|
||||
|
||||
**A:** 绝对可以!查找标记为 `good first issue` 的 issue。我们在这里指导并帮助你成功。
|
||||
|
||||
### Q:我的 PR 很大(>1000 行)。我应该怎么做?
|
||||
|
||||
**A:** 考虑将其拆分为更小的 PR。这让你获得:
|
||||
- ✅ 更快的审核
|
||||
- ✅ 更容易的测试
|
||||
- ✅ 更高的快速合并机会
|
||||
|
||||
需要帮助规划?在你的 PR 中提问即可!
|
||||
|
||||
### Q:如果我的功能不在路线图上怎么办?
|
||||
|
||||
**A:** 先开一个 issue 讨论!我们对好想法持开放态度,但在你花时间编码之前想确保对齐。
|
||||
|
||||
### Q:这将何时完全激活?
|
||||
|
||||
**A:** 第 4 周+(从公告日期起大约 4 周)。查看置顶的 Discussion 帖子了解确切日期。
|
||||
|
||||
---
|
||||
|
||||
## 🎉 对贡献者的好处
|
||||
|
||||
这个新系统通过以下方式帮助你:
|
||||
|
||||
✅ **更快的审核** - 自动化预检查减少审核时间
|
||||
✅ **清晰的期望** - 你准确知道需要什么
|
||||
✅ **更好的反馈** - 自动化检查尽早捕获问题
|
||||
✅ **公平的优先级排序** - 路线图对齐的 PR 审核更快
|
||||
✅ **表彰** - 贡献者等级和表彰计划
|
||||
|
||||
---
|
||||
|
||||
## 📚 资源
|
||||
|
||||
### 必读
|
||||
- [贡献指南](../../docs/i18n/zh-CN/CONTRIBUTING.md) - 完整指南
|
||||
- [路线图](../roadmap/README.zh-CN.md) - 当前优先级
|
||||
|
||||
### 有用链接
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/) - Commit 格式
|
||||
- [Good First Issues](https://github.com/tinkle-community/nofx/labels/good%20first%20issue) - 适合初学者的任务
|
||||
- [悬赏计划](../bounty-guide.md) - 获得报酬来贡献
|
||||
|
||||
### 获取帮助
|
||||
- [GitHub Discussions](https://github.com/tinkle-community/nofx/discussions) - 提问
|
||||
- [Telegram](https://t.me/nofx_dev_community) - 社区聊天
|
||||
- [Twitter](https://x.com/nofx_ai) - 更新和公告
|
||||
|
||||
---
|
||||
|
||||
## 💬 欢迎反馈!
|
||||
|
||||
这是一个新系统,我们想要你的意见:
|
||||
|
||||
- 📝 什么不清楚?
|
||||
- 🤔 你有什么顾虑?
|
||||
- 💡 我们如何改进?
|
||||
|
||||
在[迁移反馈讨论](https://github.com/tinkle-community/nofx/discussions)中分享(链接待定)
|
||||
|
||||
---
|
||||
|
||||
## 🙏 谢谢你!
|
||||
|
||||
我们感谢你的贡献和在这次过渡期间的耐心。我们一起正在构建令人惊叹的东西!
|
||||
|
||||
**问题?** 不要犹豫提问。我们在这里帮助!🚀
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2025-01-XX
|
||||
**状态:** 公告(第 0 周)
|
||||
**完全启动:** 第 4 周+(待定)
|
||||
173
docs/community/PR_COMMENT_TEMPLATE.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# 📢 PR Comment Template for Existing PRs
|
||||
|
||||
This template is for maintainers to comment on existing PRs to introduce the new system.
|
||||
|
||||
---
|
||||
|
||||
## Template (English)
|
||||
|
||||
```markdown
|
||||
Hi @{username}! 👋
|
||||
|
||||
Thank you for your contribution to NOFX!
|
||||
|
||||
## 🚀 New PR Management System
|
||||
|
||||
We're introducing a new PR management system to improve code quality and make reviews faster. Your PR will **not be blocked** by these changes - we'll review it under current standards.
|
||||
|
||||
### ✨ Optional: Want to check your PR against new standards?
|
||||
|
||||
We've created a **PR health check tool** that analyzes your PR and gives you suggestions!
|
||||
|
||||
**How to use:**
|
||||
|
||||
```bash
|
||||
# In your local fork, on your PR branch
|
||||
cd /path/to/your/nofx-fork
|
||||
git checkout <your-branch-name>
|
||||
|
||||
# Run the health check (reads only, doesn't modify)
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- 🔍 Analyzes your PR (doesn't modify anything)
|
||||
- ✅ Shows what's already good
|
||||
- ⚠️ Points out issues
|
||||
- 💡 Gives specific suggestions on how to fix
|
||||
- 📊 Overall health score
|
||||
|
||||
**Then fix and re-check:**
|
||||
```bash
|
||||
# Fix the issues based on suggestions
|
||||
# Run check again to verify
|
||||
./scripts/pr-check.sh
|
||||
|
||||
# Push when everything looks good
|
||||
git push origin <your-branch-name>
|
||||
```
|
||||
|
||||
### 📖 Learn More
|
||||
|
||||
- [Migration Announcement](https://github.com/tinkle-community/nofx/blob/dev/docs/community/MIGRATION_ANNOUNCEMENT.md)
|
||||
- [Contributing Guidelines](https://github.com/tinkle-community/nofx/blob/dev/CONTRIBUTING.md)
|
||||
|
||||
### ❓ Questions?
|
||||
|
||||
Just ask here! We're happy to help. 🙏
|
||||
|
||||
---
|
||||
|
||||
**Note:** This migration is **completely optional** for existing PRs. We'll review and merge your PR either way!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template (Chinese / 中文)
|
||||
|
||||
```markdown
|
||||
嗨 @{username}!👋
|
||||
|
||||
感谢你为 NOFX 做出的贡献!
|
||||
|
||||
## 🚀 新的 PR 管理系统
|
||||
|
||||
我们正在引入新的 PR 管理系统,以提高代码质量并加快审核速度。你的 PR **不会被阻止** - 我们将按照当前标准审核它。
|
||||
|
||||
### ✨ 可选:想要检查你的 PR 吗?
|
||||
|
||||
我们创建了一个 **PR 健康检查工具**来帮助你看 PR 是否符合新标准!
|
||||
|
||||
**在你的本地 fork 中运行:**
|
||||
|
||||
```bash
|
||||
# 在你的本地 fork 中,切换到你的 PR 分支
|
||||
cd /path/to/your/nofx-fork
|
||||
git checkout <your-branch-name>
|
||||
|
||||
# 运行健康检查(只读,不修改任何内容)
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
**它做什么:**
|
||||
- 🔍 分析你的 PR(不修改任何内容)
|
||||
- ✅ 显示什么是好的
|
||||
- ⚠️ 指出问题
|
||||
- 💡 给你具体的修复建议
|
||||
- 📊 整体健康评分
|
||||
|
||||
**然后修复问题并推送:**
|
||||
```bash
|
||||
# 修复问题(查看脚本的建议)
|
||||
# 再次运行检查
|
||||
./scripts/pr-check.sh
|
||||
|
||||
# 准备好后推送
|
||||
git push origin <your-branch-name>
|
||||
```
|
||||
|
||||
### 📖 了解更多
|
||||
|
||||
- [迁移公告](https://github.com/tinkle-community/nofx/blob/dev/docs/community/MIGRATION_ANNOUNCEMENT.zh-CN.md)
|
||||
- [贡献指南](https://github.com/tinkle-community/nofx/blob/dev/docs/i18n/zh-CN/CONTRIBUTING.md)
|
||||
|
||||
### ❓ 问题?
|
||||
|
||||
在这里提问即可!我们很乐意帮助。🙏
|
||||
|
||||
---
|
||||
|
||||
**注意:** 对于现有 PR,此迁移是**完全可选的**。无论如何我们都会审核和合并你的 PR!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Copy-Paste Template
|
||||
|
||||
For quick commenting on multiple PRs:
|
||||
|
||||
```markdown
|
||||
👋 Hi! Thanks for your PR!
|
||||
|
||||
We're introducing a new PR system. Your PR won't be blocked - we'll review it normally.
|
||||
|
||||
**Want to check your PR?** Run this in your fork:
|
||||
```bash
|
||||
./scripts/pr-check.sh
|
||||
```
|
||||
|
||||
[Learn more](https://github.com/tinkle-community/nofx/blob/dev/docs/community/MIGRATION_ANNOUNCEMENT.md) | This is optional!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bulk Comment Script (for maintainers)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Comment on all open PRs
|
||||
gh pr list --state open --json number --jq '.[].number' | while read pr_number; do
|
||||
echo "Commenting on PR #$pr_number"
|
||||
|
||||
gh pr comment "$pr_number" --body "👋 Hi! Thanks for your PR!
|
||||
|
||||
We're introducing a new PR system. Your PR won't be blocked - we'll review it normally.
|
||||
|
||||
**Want to check your PR?** Run this in your fork:
|
||||
\`\`\`bash
|
||||
./scripts/pr-check.sh
|
||||
\`\`\`
|
||||
|
||||
[Learn more](https://github.com/tinkle-community/nofx/blob/dev/docs/community/MIGRATION_ANNOUNCEMENT.md) | This is optional!"
|
||||
|
||||
echo "✅ Commented on PR #$pr_number"
|
||||
sleep 2 # Be nice to GitHub API
|
||||
done
|
||||
```
|
||||
|
||||
Save as `comment-all-prs.sh` and run:
|
||||
```bash
|
||||
chmod +x comment-all-prs.sh
|
||||
./comment-all-prs.sh
|
||||
```
|
||||
247
docs/community/README.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# 👥 NOFX Community
|
||||
|
||||
Welcome to the NOFX community! This section contains everything you need to contribute and participate.
|
||||
|
||||
---
|
||||
|
||||
## 📢 Important Announcement
|
||||
|
||||
**🚀 New PR Management System Coming Soon!**
|
||||
|
||||
We're introducing a new PR management system to improve code quality and make contributing easier!
|
||||
|
||||
**📖 Read:** [Migration Announcement](MIGRATION_ANNOUNCEMENT.md) | [迁移公告(中文)](MIGRATION_ANNOUNCEMENT.zh-CN.md)
|
||||
|
||||
**Timeline:** 4-week gradual rollout starting soon
|
||||
|
||||
**For existing PRs:** Don't worry! Your PRs will not be blocked by new rules.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 How to Contribute
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. **Read the Guides**
|
||||
- [Contributing Guide](../../CONTRIBUTING.md) - Complete contribution workflow
|
||||
- [Code of Conduct](../../CODE_OF_CONDUCT.md) - Community standards
|
||||
- [Security Policy](../../SECURITY.md) - Report vulnerabilities
|
||||
|
||||
2. **Find Something to Work On**
|
||||
- Browse [GitHub Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
- Look for `good first issue` label
|
||||
- Check out [bounty tasks](#-bounty-program)
|
||||
|
||||
3. **Join the Community**
|
||||
- 💬 [Telegram Developer Community](https://t.me/nofx_dev_community)
|
||||
- 🐦 [Twitter @nofx_ai](https://x.com/nofx_ai)
|
||||
- 🐙 [GitHub Discussions](https://github.com/tinkle-community/nofx/discussions)
|
||||
|
||||
---
|
||||
|
||||
## 💰 Bounty Program
|
||||
|
||||
### Active Bounties
|
||||
|
||||
NOFX offers bounties for valuable contributions:
|
||||
|
||||
| Category | Reward Range | Examples |
|
||||
|----------|--------------|----------|
|
||||
| 🥇 Major Features | $500-1000 | Exchange integration, core architecture |
|
||||
| 🥈 Medium Features | $200-500 | WebSocket support, new AI models |
|
||||
| 🥉 Small Features | $50-200 | Bug fixes, UI improvements, documentation |
|
||||
|
||||
### How to Claim Bounties
|
||||
|
||||
**📖 Complete Guide:** [bounty-guide.md](bounty-guide.md)
|
||||
|
||||
**Quick Steps:**
|
||||
1. Find issue tagged `[BOUNTY]`
|
||||
2. Comment with your proposal
|
||||
3. Wait for approval
|
||||
4. Work on the task
|
||||
5. Submit PR with demo
|
||||
6. Get paid after merge!
|
||||
|
||||
### Current Bounty Tasks
|
||||
|
||||
| Task | Reward | Difficulty | Status |
|
||||
|------|--------|------------|--------|
|
||||
| [Hyperliquid Integration](bounty-hyperliquid.md) | TBD | Hard | 🟡 Open |
|
||||
| [Aster DEX Integration](bounty-aster.md) | TBD | Medium | ✅ Completed |
|
||||
|
||||
---
|
||||
|
||||
## 🏆 Recognition
|
||||
|
||||
### Ways to Get Recognized
|
||||
|
||||
**Contributor Levels:**
|
||||
- 🌟 **Active Contributor** - Submit quality PRs
|
||||
- ⭐ **Trusted Contributor** - 3+ merged PRs, given review rights
|
||||
- 💎 **Core Team** - Top contributors, invited by maintainers
|
||||
|
||||
**Benefits:**
|
||||
- Listed in README and release notes
|
||||
- Direct access to maintainer discussions
|
||||
- Priority support for your issues
|
||||
- Invitation to private roadmap planning
|
||||
|
||||
### Hall of Fame
|
||||
|
||||
**Top Contributors:**
|
||||
- Coming soon! Be the first! 🚀
|
||||
|
||||
---
|
||||
|
||||
## 📋 Contribution Types
|
||||
|
||||
### Code Contributions
|
||||
- New exchange integrations
|
||||
- AI model adapters
|
||||
- Bug fixes and improvements
|
||||
- Performance optimizations
|
||||
|
||||
**Required:**
|
||||
- ✅ Code compiles and runs
|
||||
- ✅ Follows code style guidelines
|
||||
- ✅ Includes basic tests (preferred)
|
||||
- ✅ Updates documentation if needed
|
||||
|
||||
### Documentation
|
||||
- Tutorial writing
|
||||
- Translation (中文, Русский, Українська)
|
||||
- FAQ updates
|
||||
- Video guides
|
||||
|
||||
**Rewards:**
|
||||
- $50-200 for comprehensive guides
|
||||
- Recognition in docs
|
||||
- Contributor badge
|
||||
|
||||
### Testing & QA
|
||||
- Bug reports with reproduction steps
|
||||
- Security vulnerability reports (see [Security Policy](../../SECURITY.md))
|
||||
- Testnet verification
|
||||
- Performance testing
|
||||
|
||||
**Rewards:**
|
||||
- $50-500 for critical bug finds
|
||||
- Up to $1000 for security vulnerabilities
|
||||
- Recognition in security hall of fame
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Community Channels
|
||||
|
||||
### Primary Channels
|
||||
|
||||
| Platform | Purpose | Link |
|
||||
|----------|---------|------|
|
||||
| 💬 Telegram | Real-time chat, questions | [Join](https://t.me/nofx_dev_community) |
|
||||
| 🐙 GitHub | Issues, PRs, discussions | [Visit](https://github.com/tinkle-community/nofx) |
|
||||
| 🐦 Twitter | Announcements, updates | [@nofx_ai](https://x.com/nofx_ai) |
|
||||
|
||||
### Core Team
|
||||
|
||||
- **Tinkle** - [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
- **Zack** - [@0x_ZackH](https://x.com/0x_ZackH)
|
||||
|
||||
**Contact:**
|
||||
- Technical questions → Telegram or GitHub Issues
|
||||
- Business inquiries → Twitter DM to core team
|
||||
- Security reports → [SECURITY.md](../../SECURITY.md)
|
||||
|
||||
---
|
||||
|
||||
## 📅 Community Events
|
||||
|
||||
### Regular Activities
|
||||
- **Weekly Updates** - Development progress (Telegram)
|
||||
- **Monthly AMA** - Ask maintainers anything
|
||||
- **Quarterly Roadmap** - Future plans discussion
|
||||
|
||||
### Upcoming Events
|
||||
- *No scheduled events yet*
|
||||
|
||||
**Want to organize an event?**
|
||||
- Contact core team on Telegram
|
||||
- Propose in GitHub Discussions
|
||||
- Tweet and tag @nofx_ai
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Learning Resources
|
||||
|
||||
### For Contributors
|
||||
|
||||
**Understanding NOFX:**
|
||||
- [System Architecture](../architecture/README.md) *(coming soon)*
|
||||
- [API Reference](../architecture/api-reference.md) *(coming soon)*
|
||||
- [Database Schema](../architecture/database-schema.md) *(coming soon)*
|
||||
|
||||
**Learning Materials:**
|
||||
- Go programming: [Tour of Go](https://go.dev/tour/)
|
||||
- React/TypeScript: [React Docs](https://react.dev/)
|
||||
- Trading basics: [Binance Academy](https://academy.binance.com/)
|
||||
|
||||
### Recommended Reading
|
||||
|
||||
1. **Before Contributing:**
|
||||
- [Contributing Guide](../../CONTRIBUTING.md)
|
||||
- [Code of Conduct](../../CODE_OF_CONDUCT.md)
|
||||
|
||||
2. **For Exchange Integration:**
|
||||
- [Hyperliquid Bounty](bounty-hyperliquid.md)
|
||||
- [Aster Bounty](bounty-aster.md)
|
||||
- Existing code: `trader/binance_futures.go`
|
||||
|
||||
3. **For AI Features:**
|
||||
- [Custom API Guide](../getting-started/custom-api.md)
|
||||
- MCP client code: `mcp/client.go`
|
||||
- Decision engine: `decision/engine.go`
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Community Guidelines
|
||||
|
||||
### Our Values
|
||||
- **Respect** - Treat everyone with courtesy
|
||||
- **Transparency** - Open communication and decisions
|
||||
- **Quality** - High standards for contributions
|
||||
- **Collaboration** - Work together, help each other
|
||||
|
||||
### Not Acceptable
|
||||
- ❌ Harassment or discrimination
|
||||
- ❌ Spam or self-promotion
|
||||
- ❌ Sharing malicious code
|
||||
- ❌ Violating [Code of Conduct](../../CODE_OF_CONDUCT.md)
|
||||
|
||||
**Violations will result in:**
|
||||
1. Warning
|
||||
2. Temporary ban
|
||||
3. Permanent ban (serious cases)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Community Stats
|
||||
|
||||
| Metric | Count |
|
||||
|--------|-------|
|
||||
| GitHub Stars | Check [repo](https://github.com/tinkle-community/nofx) |
|
||||
| Contributors | 21+ |
|
||||
| Open Issues | Check [issues](https://github.com/tinkle-community/nofx/issues) |
|
||||
| Merged PRs | Check [pulls](https://github.com/tinkle-community/nofx/pulls?q=is%3Apr+is%3Amerged) |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Links
|
||||
|
||||
- **Want to contribute code?** → [Contributing Guide](../../CONTRIBUTING.md)
|
||||
- **Want to claim bounty?** → [Bounty Guide](bounty-guide.md)
|
||||
- **Found a security issue?** → [Security Policy](../../SECURITY.md)
|
||||
- **Have questions?** → [Telegram Community](https://t.me/nofx_dev_community)
|
||||
|
||||
---
|
||||
|
||||
[← Back to Documentation Home](../README.md)
|
||||
127
docs/getting-started/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 🚀 Getting Started with NOFX
|
||||
|
||||
**Language:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
This section contains all the documentation you need to get NOFX up and running.
|
||||
|
||||
## 📋 Deployment Options
|
||||
|
||||
Choose the method that best fits your needs:
|
||||
|
||||
### 🐳 Docker Deployment (Recommended)
|
||||
|
||||
**Best for:** Beginners, quick setup, production deployments
|
||||
|
||||
- **English:** [docker-deploy.en.md](docker-deploy.en.md)
|
||||
- **中文:** [docker-deploy.zh-CN.md](docker-deploy.zh-CN.md)
|
||||
|
||||
**Pros:**
|
||||
- ✅ One-command setup
|
||||
- ✅ All dependencies included
|
||||
- ✅ Easy to update and manage
|
||||
- ✅ Isolated environment
|
||||
|
||||
**Quick Start:**
|
||||
```bash
|
||||
cp config.example.jsonc config.json
|
||||
./start.sh start --build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔧 PM2 Deployment
|
||||
|
||||
**Best for:** Advanced users, development, custom setups
|
||||
|
||||
- **English:** [pm2-deploy.en.md](pm2-deploy.en.md)
|
||||
- **中文:** [pm2-deploy.md](pm2-deploy.md)
|
||||
|
||||
**Pros:**
|
||||
- ✅ Direct process control
|
||||
- ✅ Better for development
|
||||
- ✅ Lower resource usage
|
||||
- ✅ More flexible
|
||||
|
||||
**Quick Start:**
|
||||
```bash
|
||||
go build -o nofx
|
||||
cd web && npm install && npm run build
|
||||
pm2 start ecosystem.config.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤖 AI Configuration
|
||||
|
||||
### Custom AI Providers
|
||||
|
||||
- **English:** [custom-api.en.md](custom-api.en.md)
|
||||
- **中文:** [custom-api.md](custom-api.md)
|
||||
|
||||
Use custom AI models or third-party OpenAI-compatible APIs:
|
||||
- Custom DeepSeek endpoints
|
||||
- Self-hosted models
|
||||
- Other LLM providers
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Prerequisites
|
||||
|
||||
Before starting, ensure you have:
|
||||
|
||||
### For Docker Method:
|
||||
- ✅ Docker 20.10+
|
||||
- ✅ Docker Compose V2
|
||||
|
||||
### For Manual Method:
|
||||
- ✅ Go 1.21+
|
||||
- ✅ Node.js 18+
|
||||
- ✅ TA-Lib library
|
||||
- ✅ PM2 (optional)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Next Steps
|
||||
|
||||
After deployment:
|
||||
|
||||
1. **Configure AI Models** → Web interface at http://localhost:3000
|
||||
2. **Set Up Exchange** → Add Binance/Hyperliquid credentials
|
||||
3. **Create Traders** → Combine AI models with exchanges
|
||||
4. **Start Trading** → Monitor performance in dashboard
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
**Before Trading:**
|
||||
- ⚠️ Test on testnet first
|
||||
- ⚠️ Start with small amounts
|
||||
- ⚠️ Understand the risks
|
||||
- ⚠️ Read [Security Policy](../../SECURITY.md)
|
||||
|
||||
**API Keys:**
|
||||
- 🔑 Never commit API keys to git
|
||||
- 🔑 Use environment variables
|
||||
- 🔑 Restrict IP access
|
||||
- 🔑 Enable 2FA on exchanges
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
**Common Issues:**
|
||||
|
||||
1. **Docker build fails** → Check Docker version, update to 20.10+
|
||||
2. **TA-Lib not found** → `brew install ta-lib` (macOS) or `apt-get install libta-lib0-dev` (Ubuntu)
|
||||
3. **Port 8080 in use** → Change `API_PORT` in .env file
|
||||
4. **Frontend won't connect** → Check backend is running on port 8080
|
||||
|
||||
**Need more help?**
|
||||
- 📖 [FAQ](../guides/faq.zh-CN.md)
|
||||
- 💬 [Telegram Community](https://t.me/nofx_dev_community)
|
||||
- 🐛 [GitHub Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
|
||||
---
|
||||
|
||||
[← Back to Documentation Home](../README.md)
|
||||
125
docs/getting-started/README.zh-CN.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# 🚀 NOFX 快速开始
|
||||
|
||||
本节包含让 NOFX 运行起来所需的所有文档。
|
||||
|
||||
## 📋 部署选项
|
||||
|
||||
选择最适合您的方式:
|
||||
|
||||
### 🐳 Docker 部署(推荐)
|
||||
|
||||
**适合:** 初学者、快速部署、生产环境
|
||||
|
||||
- **中文文档:** [docker-deploy.zh-CN.md](docker-deploy.zh-CN.md)
|
||||
- **English:** [docker-deploy.en.md](docker-deploy.en.md)
|
||||
|
||||
**优势:**
|
||||
- ✅ 一键启动
|
||||
- ✅ 包含所有依赖
|
||||
- ✅ 易于更新和管理
|
||||
- ✅ 隔离环境
|
||||
|
||||
**快速开始:**
|
||||
```bash
|
||||
cp config.example.jsonc config.json
|
||||
./start.sh start --build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔧 PM2 部署
|
||||
|
||||
**适合:** 进阶用户、开发环境、自定义设置
|
||||
|
||||
- **中文文档:** [pm2-deploy.md](pm2-deploy.md)
|
||||
- **English:** [pm2-deploy.en.md](pm2-deploy.en.md)
|
||||
|
||||
**优势:**
|
||||
- ✅ 直接进程控制
|
||||
- ✅ 更适合开发
|
||||
- ✅ 资源占用更低
|
||||
- ✅ 更灵活
|
||||
|
||||
**快速开始:**
|
||||
```bash
|
||||
go build -o nofx
|
||||
cd web && npm install && npm run build
|
||||
pm2 start ecosystem.config.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤖 AI 配置
|
||||
|
||||
### 自定义 AI 提供商
|
||||
|
||||
- **中文文档:** [custom-api.md](custom-api.md)
|
||||
- **English:** [custom-api.en.md](custom-api.en.md)
|
||||
|
||||
使用自定义 AI 模型或第三方 OpenAI 兼容 API:
|
||||
- 自定义 DeepSeek 端点
|
||||
- 本地部署的模型
|
||||
- 其他 LLM 提供商
|
||||
|
||||
---
|
||||
|
||||
## 🔑 环境要求
|
||||
|
||||
开始之前,请确保已安装:
|
||||
|
||||
### Docker 方式:
|
||||
- ✅ Docker 20.10+
|
||||
- ✅ Docker Compose V2
|
||||
|
||||
### 手动部署方式:
|
||||
- ✅ Go 1.21+
|
||||
- ✅ Node.js 18+
|
||||
- ✅ TA-Lib 库
|
||||
- ✅ PM2(可选)
|
||||
|
||||
---
|
||||
|
||||
## 📚 下一步
|
||||
|
||||
部署完成后:
|
||||
|
||||
1. **配置 AI 模型** → 访问 Web 界面 http://localhost:3000
|
||||
2. **设置交易所** → 添加 Binance/Hyperliquid 凭证
|
||||
3. **创建交易员** → 将 AI 模型与交易所结合
|
||||
4. **开始交易** → 在仪表板中监控表现
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要提示
|
||||
|
||||
**交易前:**
|
||||
- ⚠️ 先在测试网测试
|
||||
- ⚠️ 从小金额开始
|
||||
- ⚠️ 了解风险
|
||||
- ⚠️ 阅读[安全策略](../../SECURITY.md)
|
||||
|
||||
**API 密钥:**
|
||||
- 🔑 永远不要提交 API 密钥到 git
|
||||
- 🔑 使用环境变量
|
||||
- 🔑 限制 IP 访问
|
||||
- 🔑 在交易所启用 2FA
|
||||
|
||||
---
|
||||
|
||||
## 🆘 故障排除
|
||||
|
||||
**常见问题:**
|
||||
|
||||
1. **Docker 构建失败** → 检查 Docker 版本,更新到 20.10+
|
||||
2. **找不到 TA-Lib** → `brew install ta-lib` (macOS) 或 `apt-get install libta-lib0-dev` (Ubuntu)
|
||||
3. **端口 8080 被占用** → 在 .env 文件中更改 `API_PORT`
|
||||
4. **前端无法连接** → 检查后端是否在端口 8080 上运行
|
||||
|
||||
**需要更多帮助?**
|
||||
- 📖 [常见问题](../guides/faq.zh-CN.md)
|
||||
- 💬 [Telegram 社区](https://t.me/nofx_dev_community)
|
||||
- 🐛 [GitHub Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
|
||||
---
|
||||
|
||||
[← 返回文档首页](../README.md)
|
||||
207
docs/getting-started/custom-api.en.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Custom AI API Usage Guide
|
||||
|
||||
## Features
|
||||
|
||||
NOFX now supports using any OpenAI-compatible API format, including:
|
||||
- OpenAI official API (gpt-4o, gpt-4-turbo, etc.)
|
||||
- OpenRouter (access to multiple models)
|
||||
- Locally deployed models (Ollama, LM Studio, etc.)
|
||||
- Other OpenAI-compatible API services
|
||||
|
||||
## Configuration Method
|
||||
|
||||
~~Add trader using custom API in `config.json` (deprecated):~~
|
||||
|
||||
*Note: Custom APIs and traders are now configured through the Web interface. config.json only retains basic settings.*
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "trader_custom",
|
||||
"name": "My Custom AI Trader",
|
||||
"ai_model": "custom",
|
||||
"exchange": "binance",
|
||||
|
||||
"binance_api_key": "your_binance_api_key",
|
||||
"binance_secret_key": "your_binance_secret_key",
|
||||
|
||||
"custom_api_url": "https://api.openai.com/v1",
|
||||
"custom_api_key": "sk-your-openai-api-key",
|
||||
"custom_model_name": "gpt-4o",
|
||||
|
||||
"initial_balance": 1000,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `ai_model` | string | ✅ | Set to `"custom"` to enable custom API |
|
||||
| `custom_api_url` | string | ✅ | API Base URL (without `/chat/completions`). Special usage: If ending with `#`, use full URL (no auto path append) |
|
||||
| `custom_api_key` | string | ✅ | API key |
|
||||
| `custom_model_name` | string | ✅ | Model name (e.g. `gpt-4o`, `claude-3-5-sonnet`, etc.) |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 1. OpenAI Official API
|
||||
|
||||
```json
|
||||
{
|
||||
"ai_model": "custom",
|
||||
"custom_api_url": "https://api.openai.com/v1",
|
||||
"custom_api_key": "sk-proj-xxxxx",
|
||||
"custom_model_name": "gpt-4o"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. OpenRouter
|
||||
|
||||
```json
|
||||
{
|
||||
"ai_model": "custom",
|
||||
"custom_api_url": "https://openrouter.ai/api/v1",
|
||||
"custom_api_key": "sk-or-xxxxx",
|
||||
"custom_model_name": "anthropic/claude-3.5-sonnet"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Local Ollama
|
||||
|
||||
```json
|
||||
{
|
||||
"ai_model": "custom",
|
||||
"custom_api_url": "http://localhost:11434/v1",
|
||||
"custom_api_key": "ollama",
|
||||
"custom_model_name": "llama3.1:70b"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Azure OpenAI
|
||||
|
||||
```json
|
||||
{
|
||||
"ai_model": "custom",
|
||||
"custom_api_url": "https://your-resource.openai.azure.com/openai/deployments/your-deployment",
|
||||
"custom_api_key": "your-azure-api-key",
|
||||
"custom_model_name": "gpt-4"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Using Full Custom Path (append #)
|
||||
|
||||
For certain special API endpoints that already include the full path (including `/chat/completions` or other custom paths), you can append `#` at the end of the URL to force using the full URL:
|
||||
|
||||
```json
|
||||
{
|
||||
"ai_model": "custom",
|
||||
"custom_api_url": "https://api.example.com/v2/ai/chat/completions#",
|
||||
"custom_api_key": "your-api-key",
|
||||
"custom_model_name": "custom-model"
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: The `#` will be automatically removed, and the actual request will be sent to `https://api.example.com/v2/ai/chat/completions`
|
||||
|
||||
## Compatibility Requirements
|
||||
|
||||
Custom APIs must:
|
||||
1. Support OpenAI Chat Completions format
|
||||
2. Accept `POST` requests to `/chat/completions` endpoint (or append `#` at URL end for custom path)
|
||||
3. Support `Authorization: Bearer {api_key}` authentication
|
||||
4. Return standard OpenAI response format
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **URL Format**: `custom_api_url` should be the Base URL, system will auto-append `/chat/completions`
|
||||
- ✅ Correct: `https://api.openai.com/v1`
|
||||
- ❌ Wrong: `https://api.openai.com/v1/chat/completions`
|
||||
- 🔧 **Special usage**: If you need to use a full custom path (without auto-appending `/chat/completions`), append `#` at the URL end
|
||||
- Example: `https://api.example.com/custom/path/chat/completions#`
|
||||
- System will automatically remove `#` and use the full URL directly
|
||||
|
||||
2. **Model Name**: Ensure `custom_model_name` exactly matches the model name supported by your API provider
|
||||
|
||||
3. **API Key**: Some locally deployed models may not require a real API key, you can fill in any string
|
||||
|
||||
4. **Timeout Settings**: Default timeout is 120 seconds, may need adjustment if model response is slow
|
||||
|
||||
## Multi-AI Comparison Trading
|
||||
|
||||
You can configure multiple traders with different AIs for comparison:
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "deepseek_trader",
|
||||
"ai_model": "deepseek",
|
||||
"deepseek_key": "sk-xxxxx",
|
||||
...
|
||||
},
|
||||
{
|
||||
"id": "gpt4_trader",
|
||||
"ai_model": "custom",
|
||||
"custom_api_url": "https://api.openai.com/v1",
|
||||
"custom_api_key": "sk-xxxxx",
|
||||
"custom_model_name": "gpt-4o",
|
||||
...
|
||||
},
|
||||
{
|
||||
"id": "claude_trader",
|
||||
"ai_model": "custom",
|
||||
"custom_api_url": "https://openrouter.ai/api/v1",
|
||||
"custom_api_key": "sk-or-xxxxx",
|
||||
"custom_model_name": "anthropic/claude-3.5-sonnet",
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Configuration Validation Failed
|
||||
|
||||
**Error Message**: `使用自定义API时必须配置custom_api_url` (custom_api_url must be configured when using custom API)
|
||||
|
||||
**Solution**: After setting `ai_model: "custom"`, ensure you also configure:
|
||||
- `custom_api_url`
|
||||
- `custom_api_key`
|
||||
- `custom_model_name`
|
||||
|
||||
### Issue: API Call Failed
|
||||
|
||||
**Possible Causes**:
|
||||
1. URL format error
|
||||
- Normal usage: Should not include `/chat/completions` (system will auto-append)
|
||||
- Special usage: If full path is needed, remember to append `#` at URL end
|
||||
2. Invalid API key
|
||||
3. Incorrect model name
|
||||
4. Network connection issues
|
||||
|
||||
**Debug Method**: Check error messages in logs, usually includes HTTP status code and error details
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
Existing `deepseek` and `qwen` configurations are unaffected and can continue to be used:
|
||||
|
||||
```json
|
||||
{
|
||||
"ai_model": "deepseek",
|
||||
"deepseek_key": "sk-xxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```json
|
||||
{
|
||||
"ai_model": "qwen",
|
||||
"qwen_key": "sk-xxxxx"
|
||||
}
|
||||
```
|
||||
@@ -10,7 +10,9 @@
|
||||
|
||||
## 配置方式
|
||||
|
||||
在 `config.json` 中添加使用自定义 API 的 trader:
|
||||
在 `config.json` 中添加使用自定义 API 的 trader(~~已弃用~~):
|
||||
|
||||
*注意:现在通过Web界面配置自定义API和交易员,config.json仅保留基础设置*
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -50,10 +50,12 @@ docker compose --version # Docker 24+ includes this, no separate installation n
|
||||
|
||||
```bash
|
||||
# Copy configuration template
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
|
||||
# Edit configuration file with your API keys
|
||||
nano config.json # or use any other editor
|
||||
|
||||
⚠️ **Note**: Basic config.json is still needed for some settings, but ~~trader configurations~~ are now done through the web interface.
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
@@ -95,7 +97,7 @@ docker compose up -d
|
||||
Once deployed, open your browser and visit:
|
||||
|
||||
- **Web Interface**: http://localhost:3000
|
||||
- **API Health Check**: http://localhost:8080/health
|
||||
- **API Health Check**: http://localhost:8080/api/health
|
||||
|
||||
## 📊 Service Management
|
||||
|
||||
@@ -216,7 +218,7 @@ The system automatically persists data to local directories:
|
||||
|
||||
- `./decision_logs/`: AI decision logs
|
||||
- `./coin_pool_cache/`: Coin pool cache
|
||||
- `./config.json`: Configuration file (mounted)
|
||||
- ~~`./config.json`: Configuration file (mounted)~~ (Deprecated)
|
||||
|
||||
**Data locations:**
|
||||
```bash
|
||||
@@ -225,7 +227,7 @@ ls -la decision_logs/
|
||||
ls -la coin_pool_cache/
|
||||
|
||||
# Backup data
|
||||
tar -czf backup_$(date +%Y%m%d).tar.gz decision_logs/ coin_pool_cache/ config.json
|
||||
tar -czf backup_$(date +%Y%m%d).tar.gz decision_logs/ coin_pool_cache/ ~~config.json~~ trading.db
|
||||
|
||||
# Restore data
|
||||
tar -xzf backup_20241029.tar.gz
|
||||
@@ -261,11 +263,13 @@ kill -9 <PID>
|
||||
### Configuration File Not Found
|
||||
|
||||
```bash
|
||||
# Ensure config.json exists
|
||||
ls -la config.json
|
||||
# ~~Ensure config.json exists~~
|
||||
# ~~ls -la config.json~~
|
||||
|
||||
# If not exists, copy template
|
||||
cp config.json.example config.json
|
||||
# ~~If not exists, copy template~~
|
||||
# ~~cp config.example.jsonc config.json~~
|
||||
|
||||
*Note: Now using SQLite database for configuration storage, no longer need config.json*
|
||||
```
|
||||
|
||||
### Health Check Failing
|
||||
@@ -276,7 +280,7 @@ docker inspect nofx-backend | jq '.[0].State.Health'
|
||||
docker inspect nofx-frontend | jq '.[0].State.Health'
|
||||
|
||||
# Manually test health endpoints
|
||||
curl http://localhost:8080/health
|
||||
curl http://localhost:8080/api/health
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
@@ -305,11 +309,13 @@ docker system prune -a --volumes
|
||||
|
||||
## 🔐 Security Recommendations
|
||||
|
||||
1. **Don't commit config.json to Git**
|
||||
1. ~~**Don't commit config.json to Git**~~
|
||||
```bash
|
||||
# Ensure config.json is in .gitignore
|
||||
echo "config.json" >> .gitignore
|
||||
# ~~Ensure config.json is in .gitignore~~
|
||||
# ~~echo "config.json" >> .gitignore~~
|
||||
```
|
||||
|
||||
*Note: Now using trading.db database, ensure not to commit sensitive data*
|
||||
|
||||
2. **Use environment variables for sensitive data**
|
||||
```yaml
|
||||
@@ -55,7 +55,7 @@ docker compose --version # Docker 24+ 自带,无需单独安装
|
||||
|
||||
```bash
|
||||
# 复制配置文件模板
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
|
||||
# 编辑配置文件,填入你的 API 密钥
|
||||
nano config.json # 或使用其他编辑器
|
||||
@@ -64,23 +64,23 @@ nano config.json # 或使用其他编辑器
|
||||
**必须配置的字段:**
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "my_trader",
|
||||
"name": "My AI Trader",
|
||||
"ai_model": "deepseek",
|
||||
"binance_api_key": "YOUR_BINANCE_API_KEY", // ← 填入你的币安 API Key
|
||||
"binance_secret_key": "YOUR_BINANCE_SECRET_KEY", // ← 填入你的币安 Secret Key
|
||||
"deepseek_key": "YOUR_DEEPSEEK_API_KEY", // ← 填入你的 DeepSeek API Key
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"use_default_coins": true,
|
||||
"api_server_port": 8080
|
||||
"api_server_port": 8081,
|
||||
"jwt_secret": "YOUR_JWT_SECRET_CHANGE_IN_PRODUCTION" // ← 填入一个长随机字符串作为JWT密钥
|
||||
}
|
||||
```
|
||||
|
||||
> **⚠️ 重要安全提醒**:
|
||||
> - `jwt_secret` 字段是用户认证系统的关键安全配置
|
||||
> - **必须设置一个长度至少32位的随机字符串**
|
||||
> - 在生产环境中,建议使用64位以上的随机字符串
|
||||
> - 可以使用命令生成:`openssl rand -base64 64`
|
||||
|
||||
**配置说明:**
|
||||
- 🔐 **用户认证**:系统现在支持用户注册登录,每个用户都有独立的AI模型和交易所配置
|
||||
- 🚫 **移除traders配置**:不再需要在config.json中预配置交易员,用户可以通过Web界面创建
|
||||
- 🔑 **JWT密钥**:用于保护用户会话安全,强烈建议在生产环境中设置复杂密钥
|
||||
|
||||
### 第 2 步:一键启动
|
||||
|
||||
```bash
|
||||
@@ -100,7 +100,7 @@ docker compose up -d
|
||||
部署成功后,打开浏览器访问:
|
||||
|
||||
- **Web 界面**: http://localhost:3000
|
||||
- **API 文档**: http://localhost:8080/health
|
||||
- **API 文档**: http://localhost:8080/api/health
|
||||
|
||||
## 📊 服务管理
|
||||
|
||||
@@ -270,7 +270,7 @@ kill -9 <PID>
|
||||
ls -la config.json
|
||||
|
||||
# 如果不存在,复制模板
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
```
|
||||
|
||||
### 健康检查失败
|
||||
@@ -281,7 +281,7 @@ docker inspect nofx-backend | jq '.[0].State.Health'
|
||||
docker inspect nofx-frontend | jq '.[0].State.Health'
|
||||
|
||||
# 手动测试健康端点
|
||||
curl http://localhost:8080/health
|
||||
curl http://localhost:8080/api/health
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
@@ -310,23 +310,40 @@ docker system prune -a --volumes
|
||||
|
||||
## 🔐 安全建议
|
||||
|
||||
1. **不要将 config.json 提交到 Git**
|
||||
1. **JWT密钥安全配置**
|
||||
```bash
|
||||
# 确保 config.json 在 .gitignore 中
|
||||
echo "config.json" >> .gitignore
|
||||
# 生成强随机JWT密钥
|
||||
openssl rand -base64 64
|
||||
|
||||
# 或者使用其他工具生成
|
||||
head -c 64 /dev/urandom | base64
|
||||
```
|
||||
|
||||
**JWT密钥要求:**
|
||||
- 长度至少32位,推荐64位以上
|
||||
- 使用随机生成的字符串
|
||||
- 在生产环境中绝不使用默认值
|
||||
- 定期更换(会使现有用户需要重新登录)
|
||||
|
||||
2. **使用环境变量存储敏感信息**
|
||||
2. ~~**不要将 config.json 提交到 Git**~~
|
||||
```bash
|
||||
# ~~确保 config.json 在 .gitignore 中~~
|
||||
# ~~echo "config.json" >> .gitignore~~
|
||||
```
|
||||
|
||||
*注意:现在使用trading.db数据库,请确保不提交敏感数据*
|
||||
|
||||
3. **使用环境变量存储敏感信息**
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
backend:
|
||||
environment:
|
||||
- BINANCE_API_KEY=${BINANCE_API_KEY}
|
||||
- BINANCE_SECRET_KEY=${BINANCE_SECRET_KEY}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
# 用户的API密钥现在通过Web界面配置,不再需要环境变量
|
||||
```
|
||||
|
||||
3. **限制 API 访问**
|
||||
4. **限制 API 访问**
|
||||
```yaml
|
||||
# 只允许本地访问
|
||||
services:
|
||||
303
docs/getting-started/pm2-deploy.en.md
Normal file
@@ -0,0 +1,303 @@
|
||||
# NoFX Trading Bot - PM2 Deployment Guide
|
||||
|
||||
Complete guide for local development and production deployment using PM2.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Install PM2
|
||||
|
||||
```bash
|
||||
npm install -g pm2
|
||||
```
|
||||
|
||||
### 2. One-Command Launch
|
||||
|
||||
```bash
|
||||
./pm2.sh start
|
||||
```
|
||||
|
||||
That's it! Frontend and backend will start automatically.
|
||||
|
||||
---
|
||||
|
||||
## 📋 All Commands
|
||||
|
||||
### Service Management
|
||||
|
||||
```bash
|
||||
# Start services
|
||||
./pm2.sh start
|
||||
|
||||
# Stop services
|
||||
./pm2.sh stop
|
||||
|
||||
# Restart services
|
||||
./pm2.sh restart
|
||||
|
||||
# View status
|
||||
./pm2.sh status
|
||||
|
||||
# Delete services
|
||||
./pm2.sh delete
|
||||
```
|
||||
|
||||
### Log Viewing
|
||||
|
||||
```bash
|
||||
# View all logs (live)
|
||||
./pm2.sh logs
|
||||
|
||||
# Backend logs only
|
||||
./pm2.sh logs backend
|
||||
|
||||
# Frontend logs only
|
||||
./pm2.sh logs frontend
|
||||
```
|
||||
|
||||
### Build & Compile
|
||||
|
||||
```bash
|
||||
# Compile backend
|
||||
./pm2.sh build
|
||||
|
||||
# Recompile backend and restart
|
||||
./pm2.sh rebuild
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
|
||||
```bash
|
||||
# Open PM2 monitoring dashboard (real-time CPU/Memory)
|
||||
./pm2.sh monitor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Access URLs
|
||||
|
||||
After successful startup:
|
||||
|
||||
- **Frontend Web Interface**: http://localhost:3000
|
||||
- **Backend API**: http://localhost:8080
|
||||
- **Health Check**: http://localhost:8080/api/health
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration Files
|
||||
|
||||
### pm2.config.js
|
||||
|
||||
PM2 configuration file, defines frontend and backend startup parameters:
|
||||
|
||||
```javascript
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'nofx-backend',
|
||||
script: './nofx', // Go binary
|
||||
cwd: __dirname, // Dynamically get current directory
|
||||
autorestart: true,
|
||||
max_memory_restart: '500M'
|
||||
},
|
||||
{
|
||||
name: 'nofx-frontend',
|
||||
script: 'npm',
|
||||
args: 'run dev', // Vite dev server
|
||||
cwd: path.join(__dirname, 'web'), // Dynamically join path
|
||||
autorestart: true,
|
||||
max_memory_restart: '300M'
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
**After modifying configuration, restart is required:**
|
||||
```bash
|
||||
./pm2.sh restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Log File Locations
|
||||
|
||||
- **Backend Logs**: `./logs/backend-error.log` and `./logs/backend-out.log`
|
||||
- **Frontend Logs**: `./web/logs/frontend-error.log` and `./web/logs/frontend-out.log`
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Startup on Boot
|
||||
|
||||
Set PM2 to start on boot:
|
||||
|
||||
```bash
|
||||
# 1. Start services
|
||||
./pm2.sh start
|
||||
|
||||
# 2. Save current process list
|
||||
pm2 save
|
||||
|
||||
# 3. Generate startup script
|
||||
pm2 startup
|
||||
|
||||
# 4. Follow the instructions to execute command (requires sudo)
|
||||
```
|
||||
|
||||
**Disable startup on boot:**
|
||||
```bash
|
||||
pm2 unstartup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Common Operations
|
||||
|
||||
### Restart After Code Changes
|
||||
|
||||
**Backend changes:**
|
||||
```bash
|
||||
./pm2.sh rebuild # Auto compile and restart
|
||||
```
|
||||
|
||||
**Frontend changes:**
|
||||
```bash
|
||||
./pm2.sh restart # Vite will auto hot-reload, no restart needed
|
||||
```
|
||||
|
||||
### View Real-time Resource Usage
|
||||
|
||||
```bash
|
||||
./pm2.sh monitor
|
||||
```
|
||||
|
||||
### View Detailed Information
|
||||
|
||||
```bash
|
||||
pm2 info nofx-backend # Backend details
|
||||
pm2 info nofx-frontend # Frontend details
|
||||
```
|
||||
|
||||
### Clear Logs
|
||||
|
||||
```bash
|
||||
pm2 flush
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Service Startup Failed
|
||||
|
||||
```bash
|
||||
# 1. View detailed errors
|
||||
./pm2.sh logs
|
||||
|
||||
# 2. Check port usage
|
||||
lsof -i :8080 # Backend port
|
||||
lsof -i :3000 # Frontend port
|
||||
|
||||
# 3. Manual compile test
|
||||
go build -o nofx
|
||||
./nofx
|
||||
```
|
||||
|
||||
### Backend Won't Start
|
||||
|
||||
```bash
|
||||
# ~~Check if config.json exists~~
|
||||
# ~~ls -l config.json~~
|
||||
|
||||
# Check if database file exists
|
||||
ls -l trading.db
|
||||
|
||||
# Check permissions
|
||||
chmod +x nofx
|
||||
|
||||
# Run manually to see errors
|
||||
./nofx
|
||||
```
|
||||
|
||||
### Frontend Not Accessible
|
||||
|
||||
```bash
|
||||
# Check node_modules
|
||||
cd web && npm install
|
||||
|
||||
# Manual start test
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Production Environment Recommendations
|
||||
|
||||
### 1. Use Production Mode
|
||||
|
||||
Modify `pm2.config.js`:
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: 'nofx-frontend',
|
||||
script: 'npm',
|
||||
args: 'run preview', // Change to preview (requires npm run build first)
|
||||
env: {
|
||||
NODE_ENV: 'production'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Increase Instances (Load Balancing)
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: 'nofx-backend',
|
||||
script: './nofx',
|
||||
instances: 2, // Start 2 instances
|
||||
exec_mode: 'cluster'
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Auto Restart Strategy
|
||||
|
||||
```javascript
|
||||
{
|
||||
autorestart: true,
|
||||
max_restarts: 10,
|
||||
min_uptime: '10s',
|
||||
max_memory_restart: '500M'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Comparison with Docker Deployment
|
||||
|
||||
| Feature | PM2 Deployment | Docker Deployment |
|
||||
|---------|---------------|-------------------|
|
||||
| Startup Speed | ⚡ Fast | 🐌 Slower |
|
||||
| Resource Usage | 💚 Low | 🟡 Medium |
|
||||
| Isolation | 🟡 Medium | 💚 High |
|
||||
| Use Case | Dev/Single-machine | Production/Cluster |
|
||||
| Configuration Complexity | 💚 Simple | 🟡 Medium |
|
||||
|
||||
**Recommendations:**
|
||||
- **Development Environment**: Use `./pm2.sh`
|
||||
- **Production Environment**: Use `./start.sh` (Docker)
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
```bash
|
||||
./pm2.sh help
|
||||
```
|
||||
|
||||
Or check PM2 official documentation: https://pm2.keymetrics.io/
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT
|
||||
@@ -79,7 +79,7 @@ npm install -g pm2
|
||||
|
||||
- **前端 Web 界面**: http://localhost:3000
|
||||
- **后端 API**: http://localhost:8080
|
||||
- **健康检查**: http://localhost:8080/health
|
||||
- **健康检查**: http://localhost:8080/api/health
|
||||
|
||||
---
|
||||
|
||||
@@ -206,8 +206,11 @@ go build -o nofx
|
||||
### 后端无法启动
|
||||
|
||||
```bash
|
||||
# 检查 config.json 是否存在
|
||||
ls -l config.json
|
||||
# ~~检查 config.json 是否存在~~
|
||||
# ~~ls -l config.json~~
|
||||
|
||||
# 检查数据库文件是否存在
|
||||
ls -l trading.db
|
||||
|
||||
# 检查权限
|
||||
chmod +x nofx
|
||||
138
docs/guides/README.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# 📘 NOFX User Guides
|
||||
|
||||
**Language:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
Comprehensive guides to help you use NOFX effectively.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Available Guides
|
||||
|
||||
### 🔧 Basic Usage
|
||||
|
||||
| Guide | Description | Status |
|
||||
|-------|-------------|--------|
|
||||
| [FAQ (English)](faq.en.md) | Frequently asked questions | ✅ Available |
|
||||
| [FAQ (中文)](faq.zh-CN.md) | 常见问题解答 | ✅ Available |
|
||||
| Configuration Guide | Advanced settings and options | 🚧 Coming Soon |
|
||||
| Trading Strategies | AI trading strategy examples | 🚧 Coming Soon |
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue: TA-Lib not found**
|
||||
```bash
|
||||
# macOS
|
||||
brew install ta-lib
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get install libta-lib0-dev
|
||||
```
|
||||
|
||||
**Issue: Precision error**
|
||||
- System auto-handles LOT_SIZE from exchange
|
||||
- Check network connection
|
||||
- Verify exchange API is accessible
|
||||
|
||||
**Issue: AI API timeout**
|
||||
- Check API key validity
|
||||
- Verify network connection
|
||||
- Check API balance/credits
|
||||
- Timeout is set to 120 seconds
|
||||
|
||||
**Issue: Frontend can't connect**
|
||||
- Ensure backend is running (http://localhost:8080)
|
||||
- Check if port 8080 is available
|
||||
- Check browser console for errors
|
||||
|
||||
---
|
||||
|
||||
## 📖 Usage Tips
|
||||
|
||||
### Best Practices
|
||||
|
||||
**1. Risk Management**
|
||||
- Start with small amounts (100-500 USDT)
|
||||
- Use subaccounts for additional safety
|
||||
- Set reasonable leverage limits
|
||||
- Monitor daily loss limits
|
||||
|
||||
**2. Performance Monitoring**
|
||||
- Check decision logs regularly
|
||||
- Analyze win rate and profit factor
|
||||
- Review AI reasoning (Chain of Thought)
|
||||
- Track equity curve trends
|
||||
|
||||
**3. Configuration**
|
||||
- Test on testnet first
|
||||
- Gradually increase trading amounts
|
||||
- Adjust scan intervals (3-5 minutes recommended)
|
||||
- Use default coin list for beginners
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Advanced Topics
|
||||
|
||||
### Multi-Trader Competition
|
||||
Run multiple AI models simultaneously:
|
||||
- Qwen vs DeepSeek head-to-head
|
||||
- Compare performance in real-time
|
||||
- Identify best-performing strategies
|
||||
|
||||
### Custom Coin Pools
|
||||
- Use external API for coin selection
|
||||
- Combine AI500 + OI Top data
|
||||
- Filter by liquidity and volume
|
||||
|
||||
### Exchange Integration
|
||||
- Binance Futures (CEX)
|
||||
- Hyperliquid (DEX)
|
||||
- Aster DEX (Binance-compatible)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Understanding Metrics
|
||||
|
||||
### Key Performance Indicators
|
||||
|
||||
**Win Rate**
|
||||
- Percentage of profitable trades
|
||||
- Target: >50% for consistent profit
|
||||
|
||||
**Profit Factor**
|
||||
- Ratio of gross profit to gross loss
|
||||
- Target: >1.5 (1.5:1 or better)
|
||||
|
||||
**Sharpe Ratio**
|
||||
- Risk-adjusted return measure
|
||||
- Higher is better (>1.0 is good)
|
||||
|
||||
**Maximum Drawdown**
|
||||
- Largest peak-to-trough decline
|
||||
- Keep under 20% for safety
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Getting Started (EN)](../getting-started/README.md) - Initial setup
|
||||
- [Getting Started (中文)](../getting-started/README.zh-CN.md) - 初始设置
|
||||
- [Community](../community/README.md) - Contributing and bounties
|
||||
- [FAQ (English)](faq.en.md) - Common questions
|
||||
- [FAQ (中文)](faq.zh-CN.md) - 常见问题
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Need Help?
|
||||
|
||||
**Can't find what you need?**
|
||||
- 💬 [Telegram Community](https://t.me/nofx_dev_community)
|
||||
- 🐛 [GitHub Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
- 🐦 [Twitter @nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
---
|
||||
|
||||
[← Back to Documentation Home](../README.md)
|
||||
137
docs/guides/README.zh-CN.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# 📘 NOFX 使用指南
|
||||
|
||||
**语言:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
帮助您有效使用 NOFX 的综合指南。
|
||||
|
||||
---
|
||||
|
||||
## 📚 可用指南
|
||||
|
||||
### 🔧 基础使用
|
||||
|
||||
| 指南 | 描述 | 状态 |
|
||||
|------|------|------|
|
||||
| [FAQ (中文)](faq.zh-CN.md) | 常见问题解答 | ✅ 可用 |
|
||||
| [FAQ (English)](faq.en.md) | Frequently asked questions | ✅ 可用 |
|
||||
| 配置指南 | 高级设置和选项 | 🚧 即将推出 |
|
||||
| 交易策略 | AI 交易策略示例 | 🚧 即将推出 |
|
||||
|
||||
---
|
||||
|
||||
## 🐛 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
**问题:找不到 TA-Lib**
|
||||
```bash
|
||||
# macOS
|
||||
brew install ta-lib
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get install libta-lib0-dev
|
||||
```
|
||||
|
||||
**问题:精度错误**
|
||||
- 系统自动处理交易所的 LOT_SIZE
|
||||
- 检查网络连接
|
||||
- 验证交易所 API 可访问
|
||||
|
||||
**问题:AI API 超时**
|
||||
- 检查 API 密钥有效性
|
||||
- 验证网络连接
|
||||
- 检查 API 余额/额度
|
||||
- 超时设置为 120 秒
|
||||
|
||||
**问题:前端无法连接**
|
||||
- 确保后端正在运行 (http://localhost:8080)
|
||||
- 检查端口 8080 是否可用
|
||||
- 检查浏览器控制台错误
|
||||
|
||||
---
|
||||
|
||||
## 📖 使用技巧
|
||||
|
||||
### 最佳实践
|
||||
|
||||
**1. 风险管理**
|
||||
- 从小金额开始(100-500 USDT)
|
||||
- 使用子账户增加安全性
|
||||
- 设置合理的杠杆限制
|
||||
- 监控每日亏损限制
|
||||
|
||||
**2. 性能监控**
|
||||
- 定期检查决策日志
|
||||
- 分析胜率和盈利因子
|
||||
- 审查 AI 推理(思维链)
|
||||
- 跟踪权益曲线趋势
|
||||
|
||||
**3. 配置**
|
||||
- 先在测试网测试
|
||||
- 逐步增加交易金额
|
||||
- 调整扫描间隔(推荐 3-5 分钟)
|
||||
- 初学者使用默认币种列表
|
||||
|
||||
---
|
||||
|
||||
## 🎯 进阶主题
|
||||
|
||||
### 多交易员竞赛
|
||||
同时运行多个 AI 模型:
|
||||
- Qwen vs DeepSeek 对决
|
||||
- 实时比较性能
|
||||
- 识别表现最佳的策略
|
||||
|
||||
### 自定义币种池
|
||||
- 使用外部 API 进行币种选择
|
||||
- 结合 AI500 + OI Top 数据
|
||||
- 按流动性和交易量过滤
|
||||
|
||||
### 交易所集成
|
||||
- Binance Futures(中心化交易所)
|
||||
- Hyperliquid(去中心化交易所)
|
||||
- Aster DEX(兼容 Binance)
|
||||
|
||||
---
|
||||
|
||||
## 📊 理解指标
|
||||
|
||||
### 关键性能指标
|
||||
|
||||
**胜率(Win Rate)**
|
||||
- 盈利交易的百分比
|
||||
- 目标:>50% 以获得稳定盈利
|
||||
|
||||
**盈利因子(Profit Factor)**
|
||||
- 总盈利与总亏损的比率
|
||||
- 目标:>1.5(1.5:1 或更好)
|
||||
|
||||
**夏普比率(Sharpe Ratio)**
|
||||
- 风险调整后的收益衡量
|
||||
- 越高越好(>1.0 为良好)
|
||||
|
||||
**最大回撤(Maximum Drawdown)**
|
||||
- 从峰值到谷值的最大跌幅
|
||||
- 为安全起见保持在 20% 以下
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关文档
|
||||
|
||||
- [快速开始](../getting-started/README.zh-CN.md) - 初始设置
|
||||
- [社区](../community/README.md) - 贡献和悬赏
|
||||
- [FAQ 中文](faq.zh-CN.md) - 常见问题
|
||||
- [FAQ English](faq.en.md) - Common questions
|
||||
|
||||
---
|
||||
|
||||
## 🆘 需要帮助?
|
||||
|
||||
**找不到您需要的内容?**
|
||||
- 💬 [Telegram 社区](https://t.me/nofx_dev_community)
|
||||
- 🐛 [GitHub Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
- 🐦 [Twitter @nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
---
|
||||
|
||||
[← 返回文档首页](../README.md)
|
||||
25
docs/guides/faq.en.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
## Binance Position Mode Error (code=-4061)
|
||||
|
||||
**Error Message**: `Order's position side does not match user's setting`
|
||||
|
||||
**Cause**: The system requires Hedge Mode (dual position), but your Binance account is set to One-way Mode.
|
||||
|
||||
### Solution
|
||||
|
||||
1. Login to [Binance Futures Trading Platform](https://www.binance.com/en/futures/BTCUSDT)
|
||||
|
||||
2. Click **⚙️ Preferences** in the top right corner
|
||||
|
||||
3. Select **Position Mode**
|
||||
|
||||
4. Switch to **Hedge Mode** (Dual Position)
|
||||
|
||||
5. Confirm the change
|
||||
|
||||
**Note**: You must close all open positions before switching modes.
|
||||
|
||||
---
|
||||
|
||||
For more issues, check [GitHub Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
231
docs/i18n/README.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# 🌍 International Documentation / 国际化文档
|
||||
|
||||
NOFX documentation is available in multiple languages.
|
||||
|
||||
NOFX 文档提供多种语言版本。
|
||||
|
||||
---
|
||||
|
||||
## 📚 Available Languages / 可用语言
|
||||
|
||||
| Language | Main README | Status | Maintainers |
|
||||
|----------|-------------|--------|-------------|
|
||||
| 🇬🇧 **English** | [README.md](../../README.md) | ✅ Complete | Core Team |
|
||||
| 🇨🇳 **Chinese (中文)** | [README.md](zh-CN/README.md) | ✅ Complete | Community |
|
||||
| 🇷🇺 **Russian (Русский)** | [README.md](ru/README.md) | ✅ Complete | Community |
|
||||
| 🇺🇦 **Ukrainian (Українська)** | [README.md](uk/README.md) | ✅ Complete | Community |
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Quick Access / 快速访问
|
||||
|
||||
### English 🇬🇧
|
||||
- **Main README:** [../../README.md](../../README.md)
|
||||
- **Contributing:** [../../CONTRIBUTING.md](../../CONTRIBUTING.md)
|
||||
- **Security:** [../../SECURITY.md](../../SECURITY.md)
|
||||
|
||||
### 中文 🇨🇳
|
||||
- **主 README:** [zh-CN/README.md](zh-CN/README.md)
|
||||
- **贡献指南:** [../../CONTRIBUTING.md](../../CONTRIBUTING.md#中文)
|
||||
- **安全政策:** [../../SECURITY.md](../../SECURITY.md#中文)
|
||||
- **常见问题:** [../guides/faq.zh-CN.md](../guides/faq.zh-CN.md)
|
||||
|
||||
### Русский 🇷🇺
|
||||
- **Основной README:** [ru/README.md](ru/README.md)
|
||||
- **Руководство по участию:** [../../CONTRIBUTING.md](../../CONTRIBUTING.md)
|
||||
- **Политика безопасности:** [../../SECURITY.md](../../SECURITY.md)
|
||||
|
||||
### Українська 🇺🇦
|
||||
- **Головний README:** [uk/README.md](uk/README.md)
|
||||
- **Посібник із внесків:** [../../CONTRIBUTING.md](../../CONTRIBUTING.md)
|
||||
- **Політика безпеки:** [../../SECURITY.md](../../SECURITY.md)
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Help with Translations / 帮助翻译
|
||||
|
||||
### Want to Contribute Translations? / 想要贡献翻译?
|
||||
|
||||
We welcome translation contributions! / 我们欢迎翻译贡献!
|
||||
|
||||
**What needs translation? / 需要翻译什么?**
|
||||
- ✅ Main README (complete for 4 languages)
|
||||
- 🚧 Deployment guides (partial)
|
||||
- 📋 User guides (needed)
|
||||
- 📋 Contributing guide (needed for RU/UK)
|
||||
|
||||
**How to contribute translations? / 如何贡献翻译?**
|
||||
|
||||
1. **Check existing translations / 检查现有翻译**
|
||||
- Browse this directory
|
||||
- See what's missing
|
||||
|
||||
2. **Claim a translation task / 认领翻译任务**
|
||||
- Open a GitHub Issue
|
||||
- Title: `[TRANSLATION] Document name to Language`
|
||||
- Example: `[TRANSLATION] CONTRIBUTING.md to Chinese`
|
||||
|
||||
3. **Submit translation / 提交翻译**
|
||||
- Follow [Contributing Guide](../../CONTRIBUTING.md)
|
||||
- Place file in appropriate language folder
|
||||
- Keep formatting and structure consistent
|
||||
|
||||
4. **Get recognized / 获得认可**
|
||||
- Listed as translator in credits
|
||||
- Eligible for contributor badges
|
||||
- Possible bounty rewards ($50-200)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Translation Guidelines / 翻译指南
|
||||
|
||||
### File Naming Convention / 文件命名规范
|
||||
|
||||
**Pattern:** `document-name.{language-code}.md`
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
README.md → en (default)
|
||||
docker-deploy.zh-CN.md → Chinese
|
||||
docker-deploy.ru.md → Russian
|
||||
faq.zh-CN.md → Chinese FAQ
|
||||
```
|
||||
|
||||
**Language Codes:**
|
||||
- `en` - English (default, no suffix needed)
|
||||
- `zh-CN` - Simplified Chinese
|
||||
- `ru` - Russian
|
||||
- `uk` - Ukrainian
|
||||
- `ja` - Japanese *(future)*
|
||||
- `ko` - Korean *(future)*
|
||||
|
||||
### Quality Standards / 质量标准
|
||||
|
||||
**Must have / 必须具备:**
|
||||
- ✅ Accurate technical terms
|
||||
- ✅ Natural, fluent language
|
||||
- ✅ Consistent terminology
|
||||
- ✅ Preserved formatting (markdown)
|
||||
- ✅ Working internal links
|
||||
|
||||
**Avoid / 避免:**
|
||||
- ❌ Machine translation without review
|
||||
- ❌ Inconsistent terminology
|
||||
- ❌ Broken links or formatting
|
||||
- ❌ Cultural insensitivity
|
||||
|
||||
### Technical Terms / 技术术语
|
||||
|
||||
**Keep in English (don't translate):**
|
||||
- API, HTTP, REST, JSON
|
||||
- Docker, Kubernetes
|
||||
- GitHub, Git, Pull Request
|
||||
- Specific tool names (Binance, Hyperliquid)
|
||||
|
||||
**Example - Chinese:**
|
||||
- ✅ "启动 Docker 容器" (start Docker container)
|
||||
- ❌ "启动 多克 容器" (transliterated Docker)
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Request a New Language / 请求新语言
|
||||
|
||||
### Want NOFX in your language? / 希望 NOFX 支持你的语言?
|
||||
|
||||
**Steps / 步骤:**
|
||||
|
||||
1. **Check if it's planned / 检查是否已计划**
|
||||
- See list below
|
||||
- Search GitHub Issues
|
||||
|
||||
2. **Create a request / 创建请求**
|
||||
- Open GitHub Issue
|
||||
- Title: `[TRANSLATION REQUEST] Language name`
|
||||
- Explain: Number of potential users, your willingness to help
|
||||
|
||||
3. **Volunteer to help / 志愿帮助**
|
||||
- Offer to translate
|
||||
- Find other speakers to review
|
||||
- Commit to maintaining updates
|
||||
|
||||
### Planned Languages / 计划中的语言
|
||||
|
||||
| Language | Status | Need Volunteers? |
|
||||
|----------|--------|------------------|
|
||||
| 🇯🇵 Japanese | 📋 Planned | ✅ Yes |
|
||||
| 🇰🇷 Korean | 📋 Planned | ✅ Yes |
|
||||
| 🇪🇸 Spanish | 📋 Planned | ✅ Yes |
|
||||
| 🇫🇷 French | 📋 Planned | ✅ Yes |
|
||||
| 🇩🇪 German | 📋 Planned | ✅ Yes |
|
||||
|
||||
---
|
||||
|
||||
## 👥 Translation Team / 翻译团队
|
||||
|
||||
### Current Translators / 当前翻译者
|
||||
|
||||
| Language | Translators | Status |
|
||||
|----------|-------------|--------|
|
||||
| 🇨🇳 Chinese | Community | Active |
|
||||
| 🇷🇺 Russian | Community | Active |
|
||||
| 🇺🇦 Ukrainian | Community | Active |
|
||||
|
||||
**Want to join the team? / 想加入团队?**
|
||||
- Contact on [Telegram](https://t.me/nofx_dev_community)
|
||||
- Open an issue on GitHub
|
||||
- DM [@nofx_ai](https://x.com/nofx_ai) on Twitter
|
||||
|
||||
---
|
||||
|
||||
## 📊 Translation Progress / 翻译进度
|
||||
|
||||
### Document Coverage / 文档覆盖率
|
||||
|
||||
| Document | EN | 中文 | РУ | УК |
|
||||
|----------|----|----|----|----|
|
||||
| Main README | ✅ | ✅ | ✅ | ✅ |
|
||||
| CONTRIBUTING | ✅ | ✅ | 🚧 | 🚧 |
|
||||
| CODE_OF_CONDUCT | ✅ | ✅ | 🚧 | 🚧 |
|
||||
| SECURITY | ✅ | ✅ | 🚧 | 🚧 |
|
||||
| Docker Deploy | ✅ | ✅ | ❌ | ❌ |
|
||||
| FAQ | ✅ | ✅ | ❌ | ❌ |
|
||||
|
||||
**Legend / 图例:**
|
||||
- ✅ Complete / 完成
|
||||
- 🚧 In Progress / 进行中
|
||||
- ❌ Not Started / 未开始
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Priority Translations / 优先翻译
|
||||
|
||||
**High Priority / 高优先级:**
|
||||
1. CONTRIBUTING.md (all languages)
|
||||
2. Docker deployment guides
|
||||
3. FAQ sections
|
||||
|
||||
**Medium Priority / 中优先级:**
|
||||
1. User guides
|
||||
2. Troubleshooting docs
|
||||
3. API reference
|
||||
|
||||
**Low Priority / 低优先级:**
|
||||
1. Architecture docs (technical, less urgent)
|
||||
2. Advanced configuration guides
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Translation Help / 翻译帮助
|
||||
|
||||
**Questions? / 有问题?**
|
||||
- 💬 Ask in [Telegram Community](https://t.me/nofx_dev_community)
|
||||
- 🐙 Open a [GitHub Issue](https://github.com/tinkle-community/nofx/issues)
|
||||
- 📧 Contact maintainers
|
||||
|
||||
**Resources / 资源:**
|
||||
- [Contributing Guide](../../CONTRIBUTING.md) - How to submit
|
||||
- [Markdown Guide](https://www.markdownguide.org/) - Formatting reference
|
||||
|
||||
---
|
||||
|
||||
[← Back to Documentation Home](../README.md)
|
||||
@@ -6,10 +6,29 @@
|
||||
[](LICENSE)
|
||||
[](https://amber.ac)
|
||||
|
||||
**Языки / Languages:** [English](README.md) | [中文](README.zh-CN.md) | [Українська](README.uk.md) | [Русский](README.ru.md)
|
||||
**Языки / Languages:** [English](../../../README.md) | [中文](../zh-CN/README.md) | [Українська](../uk/README.md) | [Русский](../ru/README.md)
|
||||
|
||||
**Официальный Twitter:** [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
**📚 Документация:** [Главная](../../README.md) | [Начало работы](../../getting-started/README.md) | [Журнал изменений](../../../CHANGELOG.zh-CN.md) | [Сообщество](../../community/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 📑 Содержание
|
||||
|
||||
- [🚀 Универсальная AI Торговая Операционная Система](#-универсальная-ai-торговая-операционная-система)
|
||||
- [👥 Сообщество Разработчиков](#-сообщество-разработчиков)
|
||||
- [🆕 Что Нового](#-что-нового)
|
||||
- [📸 Скриншоты](#-скриншоты)
|
||||
- [✨ Текущая Реализация - Криптовалютные Рынки](#-текущая-реализация---криптовалютные-рынки)
|
||||
- [🔮 Дорожная Карта](#-дорожная-карта---расширение-на-универсальные-рынки)
|
||||
- [🏗️ Техническая Архитектура](#️-техническая-архитектура)
|
||||
- [🚀 Быстрый Старт](#-быстрый-старт)
|
||||
- [📊 Функции Web-интерфейса](#-функции-web-интерфейса)
|
||||
- [⚠️ Важные Предупреждения о Рисках](#️-важные-предупреждения-о-рисках)
|
||||
- [🛠️ Общие Проблемы](#️-общие-проблемы)
|
||||
- [🔄 Журнал Изменений](#-журнал-изменений)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Универсальная AI Торговая Операционная Система
|
||||
@@ -74,7 +93,7 @@ NOFX теперь поддерживает **три основные биржи*
|
||||
|
||||
**Быстрый старт:**
|
||||
1. Получите приватный ключ MetaMask (удалите префикс `0x`)
|
||||
2. Установите `"exchange": "hyperliquid"` в config.json
|
||||
2. ~~Установите `"exchange": "hyperliquid"` в config.json~~ *Настройте через веб-интерфейс*
|
||||
3. Добавьте `"hyperliquid_private_key": "your_key"`
|
||||
4. Начните торговать!
|
||||
|
||||
@@ -110,15 +129,19 @@ NOFX теперь поддерживает **три основные биржи*
|
||||
## 📸 Скриншоты
|
||||
|
||||
### 🏆 Режим конкуренции - Битва AI в реальном времени
|
||||

|
||||

|
||||
*Лидерборд с несколькими AI и графики сравнения производительности в реальном времени показывают битву Qwen против DeepSeek*
|
||||
|
||||
### 📊 Детали трейдера - Полная торговая панель
|
||||

|
||||

|
||||
*Профессиональный торговый интерфейс с кривыми капитала, живыми позициями и логами решений AI с раскрываемыми входными промптами и цепочкой рассуждений*
|
||||
|
||||
---
|
||||
|
||||
> 📘 **Примечание**: Это упрощенная русская версия README. Для получения полной технической документации, включая архитектуру системы, API-интерфейсы и расширенные конфигурации, см. [Английскую версию](../../../README.md) или [Китайскую версию](../zh-CN/README.md).
|
||||
|
||||
---
|
||||
|
||||
## ✨ Основные возможности
|
||||
|
||||
### 🏆 Режим конкуренции нескольких AI
|
||||
@@ -174,6 +197,55 @@ NOFX теперь поддерживает **три основные биржи*
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Дорожная Карта - Расширение на Универсальные Рынки
|
||||
|
||||
Миссия NOFX - стать **Универсальной AI Торговой Операционной Системой** для всех финансовых рынков.
|
||||
|
||||
**Видение:** Та же архитектура. Та же агентная структура. Все рынки.
|
||||
|
||||
**Расширение на Рынки:**
|
||||
- 📈 **Фондовые Рынки**: Акции США, A-акции, Гонконгская биржа
|
||||
- 📊 **Рынки Фьючерсов**: Товарные фьючерсы, индексные фьючерсы
|
||||
- 🎯 **Опционная Торговля**: Опционы на акции, крипто опционы
|
||||
- 💱 **Рынки Форекс**: Основные валютные пары, кросс-курсы
|
||||
|
||||
**Предстоящие Функции:**
|
||||
- Расширенные AI возможности (GPT-4, Claude 3, Gemini Pro, гибкие шаблоны промптов)
|
||||
- Новые интеграции бирж (OKX, Bybit, Lighter, EdgeX + CEX/Perp-DEX)
|
||||
- Рефакторинг структуры проекта (высокая связность, низкая связанность, принципы SOLID)
|
||||
- Улучшения безопасности (AES-256 шифрование API ключей, RBAC, улучшения 2FA)
|
||||
- Улучшения пользовательского опыта (мобильный интерфейс, графики TradingView, система оповещений)
|
||||
|
||||
📖 **Для подробной дорожной карты и сроков см.:**
|
||||
- **English:** [Roadmap Documentation](../../roadmap/README.md)
|
||||
- **中文:** [路线图文档](../../roadmap/README.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Техническая Архитектура
|
||||
|
||||
NOFX построен на современной модульной архитектуре:
|
||||
|
||||
- **Backend:** Go с фреймворком Gin, база данных SQLite
|
||||
- **Frontend:** React 18 + TypeScript + Vite + TailwindCSS
|
||||
- **Поддержка Бирж:** Binance, Hyperliquid, Aster DEX
|
||||
- **Интеграция AI:** DeepSeek, Qwen и пользовательские OpenAI-совместимые API
|
||||
- **Управление Состоянием:** Zustand для фронтенда, на основе базы данных для бэкенда
|
||||
- **Обновления в Реальном Времени:** SWR с интервалами опроса 5-10 секунд
|
||||
|
||||
**Ключевые Особенности:**
|
||||
- 🗄️ Конфигурация на основе базы данных (больше никакого редактирования JSON)
|
||||
- 🔐 JWT аутентификация с опциональной поддержкой 2FA
|
||||
- 📊 Отслеживание производительности и аналитика в реальном времени
|
||||
- 🤖 Режим конкуренции Multi-AI с живым сравнением
|
||||
- 🔌 RESTful API для всех настроек и мониторинга
|
||||
|
||||
📖 **Для подробной документации по архитектуре см.:**
|
||||
- **Русский:** [Документация по Архитектуре](../../architecture/README.md)
|
||||
- **中文:** [架构文档](../../architecture/README.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 💰 Регистрация аккаунта Binance (Экономьте на комиссиях!)
|
||||
|
||||
Перед использованием этой системы вам нужен аккаунт Binance Futures. **Используйте нашу реферальную ссылку для получения скидки на комиссии:**
|
||||
@@ -213,7 +285,7 @@ Docker автоматически обрабатывает все зависим
|
||||
#### Шаг 1: Подготовьте конфигурацию
|
||||
```bash
|
||||
# Скопируйте шаблон конфигурации
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
|
||||
# Отредактируйте и заполните ваши API ключи
|
||||
nano config.json # или используйте любой редактор
|
||||
@@ -328,7 +400,7 @@ cd ..
|
||||
|
||||
**Как получить Qwen API ключ:**
|
||||
|
||||
1. **Посетите**: [https://dashscope.aliyuncs.com](https://dashscope.aliyuncs.com)
|
||||
1. **Посетите**: [https://dashscope.console.aliyun.com](https://dashscope.console.aliyun.com)
|
||||
2. **Зарегистрируйтесь**: Используя аккаунт Alibaba Cloud
|
||||
3. **Активируйте сервис**: Активируйте DashScope сервис
|
||||
4. **Создайте API ключ**:
|
||||
@@ -351,7 +423,7 @@ cd ..
|
||||
**Шаг 1**: Скопируйте и переименуйте файл примера конфигурации
|
||||
|
||||
```bash
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
```
|
||||
|
||||
**Шаг 2**: Отредактируйте `config.json` и заполните ваши API ключи
|
||||
@@ -724,7 +796,7 @@ go build -o nofx
|
||||
|---------------------|---------|
|
||||
| `invalid API key` | Проверьте Binance API ключи в config.json |
|
||||
| `TA-Lib not found` | Выполните `brew install ta-lib` (macOS) |
|
||||
| `port 8080 already in use` | Измените `api_server_port` в config.json |
|
||||
| `port 8080 already in use` | ~~Измените `api_server_port` в config.json~~ *Измените `API_PORT` в файле .env* |
|
||||
| `DeepSeek API error` | Проверьте DeepSeek API ключ и баланс |
|
||||
|
||||
**✅ Признаки работы Backend:**
|
||||
@@ -798,7 +870,7 @@ VITE v5.x.x ready in xxx ms
|
||||
|
||||
```bash
|
||||
# В новом окне терминала
|
||||
curl http://localhost:8080/health
|
||||
curl http://localhost:8080/api/health
|
||||
```
|
||||
|
||||
Должно вернуть: `{"status":"ok"}`
|
||||
@@ -826,103 +898,73 @@ curl http://localhost:8080/health
|
||||
|
||||
Каждый цикл принятия решений (по умолчанию 3 минуты), система работает по следующему процессу:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 1. 📊 Анализ исторической производительности │
|
||||
│ (последние 20 циклов) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ✓ Расчет общего процента выигрышей, средней прибыли, │
|
||||
│ соотношения прибыли/убытка │
|
||||
│ ✓ Статистика по каждой монете (процент выигрышей, │
|
||||
│ средний P/L в USDT) │
|
||||
│ ✓ Определение лучших/худших монет по производительности │
|
||||
│ ✓ Список деталей последних 5 сделок с точным P/L │
|
||||
│ ✓ Расчет коэффициента Шарпа для оценки риска │
|
||||
│ 📌 НОВОЕ (v2.0.2): Точный P/L в USDT с учетом плеча │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 2. 💰 Получение состояния аккаунта │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Капитал аккаунта, доступный баланс, нереализованный │
|
||||
│ P/L │
|
||||
│ • Количество позиций, общий P/L (реализованный + │
|
||||
│ нереализованный) │
|
||||
│ • Использование маржи (текущее/максимальное) │
|
||||
│ • Индикаторы оценки риска │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 3. 🔍 Анализ существующих позиций (если есть) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Получение рыночных данных для каждой позиции │
|
||||
│ (3-минутные + 4-часовые свечи) │
|
||||
│ • Расчет технических индикаторов (RSI, MACD, EMA) │
|
||||
│ • Отображение длительности удержания позиции │
|
||||
│ (например, "удерживается 2 часа 15 минут") │
|
||||
│ • AI определяет, нужно ли закрыть (тейк-профит, │
|
||||
│ стоп-лосс или корректировка) │
|
||||
│ 📌 НОВОЕ (v2.0.2): Отслеживание длительности позиции │
|
||||
│ помогает AI решать │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 4. 🎯 Оценка новых возможностей (пул кандидатов монет) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Получение топ-20 монет с высоким рейтингом AI500 │
|
||||
│ • Получение топ-20 монет с самым быстрым ростом OI │
|
||||
│ • Объединение, удаление дубликатов, фильтрация монет с │
|
||||
│ низкой ликвидностью (OI < 15M USD) │
|
||||
│ • Массовое получение рыночных данных и технических │
|
||||
│ индикаторов │
|
||||
│ • Подготовка полных последовательностей сырых данных │
|
||||
│ для каждой монеты-кандидата │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 5. 🧠 Комплексное решение AI │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Просмотр исторической обратной связи (процент │
|
||||
│ выигрышей, коэффициент P/L, лучшие/худшие монеты) │
|
||||
│ • Получение всех данных последовательностей (свечи, │
|
||||
│ индикаторы, открытый интерес) │
|
||||
│ • Анализ Chain of Thought │
|
||||
│ • Вывод решения: закрыть/открыть/удерживать/наблюдать │
|
||||
│ • Включает параметры плеча, размера, стоп-лосса, │
|
||||
│ тейк-профита │
|
||||
│ 📌 НОВОЕ (v2.0.2): AI может свободно анализировать │
|
||||
│ сырые последовательности, не ограничен заранее │
|
||||
│ определенными индикаторами │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 6. ⚡ Исполнение сделок │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Приоритизация: сначала закрытие, затем открытие │
|
||||
│ • Автоматическая адаптация точности (правила LOT_SIZE) │
|
||||
│ • Предотвращение накопления позиций (отклонение │
|
||||
│ дублирования монета/направление) │
|
||||
│ • Автоматическая отмена всех ордеров после закрытия │
|
||||
│ • Запись времени открытия для отслеживания │
|
||||
│ длительности позиции │
|
||||
│ 📌 НОВОЕ (v2.0.2): Отслеживание времени открытия │
|
||||
│ позиции │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 7. 📝 Запись логов │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Сохранение полной записи решения в decision_logs/ │
|
||||
│ • Включает цепочку рассуждений, JSON решения, снимок │
|
||||
│ аккаунта, результаты исполнения │
|
||||
│ • Хранение полных данных позиции (количество, плечо, │
|
||||
│ время открытия/закрытия) │
|
||||
│ • Использование ключей symbol_side для предотвращения │
|
||||
│ конфликтов лонг/шорт │
|
||||
│ 📌 НОВОЕ (v2.0.2): Предотвращение конфликтов при │
|
||||
│ удержании лонг + шорт, учет количества + плеча │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
### Шаг 1: 📊 Анализ исторической производительности (последние 20 циклов)
|
||||
- ✓ Расчет общего процента выигрышей, средней прибыли, соотношения прибыли/убытка
|
||||
- ✓ Статистика по каждой монете (процент выигрышей, средний P/L в USDT)
|
||||
- ✓ Определение лучших/худших монет по производительности
|
||||
- ✓ Список деталей последних 5 сделок с точным P/L
|
||||
- ✓ Расчет коэффициента Шарпа для оценки риска
|
||||
- 📌 **НОВОЕ (v2.0.2)**: Точный P/L в USDT с учетом плеча
|
||||
|
||||
**↓**
|
||||
|
||||
### Шаг 2: 💰 Получение состояния аккаунта
|
||||
- Капитал аккаунта, доступный баланс, нереализованный P/L
|
||||
- Количество позиций, общий P/L (реализованный + нереализованный)
|
||||
- Использование маржи (текущее/максимальное)
|
||||
- Индикаторы оценки риска
|
||||
|
||||
**↓**
|
||||
|
||||
### Шаг 3: 🔍 Анализ существующих позиций (если есть)
|
||||
- Получение рыночных данных для каждой позиции (3-минутные + 4-часовые свечи)
|
||||
- Расчет технических индикаторов (RSI, MACD, EMA)
|
||||
- Отображение длительности удержания позиции (например, "удерживается 2 часа 15 минут")
|
||||
- AI определяет, нужно ли закрыть (тейк-профит, стоп-лосс или корректировка)
|
||||
- 📌 **НОВОЕ (v2.0.2)**: Отслеживание длительности позиции помогает AI решать
|
||||
|
||||
**↓**
|
||||
|
||||
### Шаг 4: 🎯 Оценка новых возможностей (пул кандидатов монет)
|
||||
- Получение пула монет (2 режима):
|
||||
- 🌟 **Режим по умолчанию**: BTC, ETH, SOL, BNB, XRP и т.д.
|
||||
- ⚙️ **Расширенный режим**: AI500 (топ-20) + OI Top (топ-20)
|
||||
- Объединение, удаление дубликатов, фильтрация монет с низкой ликвидностью (OI < 15M USD)
|
||||
- Массовое получение рыночных данных и технических индикаторов
|
||||
- Подготовка полных последовательностей сырых данных для каждой монеты-кандидата
|
||||
|
||||
**↓**
|
||||
|
||||
### Шаг 5: 🧠 Комплексное решение AI
|
||||
- Просмотр исторической обратной связи (процент выигрышей, коэффициент P/L, лучшие/худшие монеты)
|
||||
- Получение всех данных последовательностей (свечи, индикаторы, открытый интерес)
|
||||
- Анализ Chain of Thought
|
||||
- Вывод решения: закрыть/открыть/удерживать/наблюдать
|
||||
- Включает параметры плеча, размера, стоп-лосса, тейк-профита
|
||||
- 📌 **НОВОЕ (v2.0.2)**: AI может свободно анализировать сырые последовательности, не ограничен заранее определенными индикаторами
|
||||
|
||||
**↓**
|
||||
|
||||
### Шаг 6: ⚡ Исполнение сделок
|
||||
- Приоритизация: сначала закрытие, затем открытие
|
||||
- Автоматическая адаптация точности (правила LOT_SIZE)
|
||||
- Предотвращение накопления позиций (отклонение дублирования монета/направление)
|
||||
- Автоматическая отмена всех ордеров после закрытия
|
||||
- Запись времени открытия для отслеживания длительности позиции
|
||||
- 📌 Отслеживание времени открытия позиции
|
||||
|
||||
**↓**
|
||||
|
||||
### Шаг 7: 📝 Запись логов
|
||||
- Сохранение полной записи решения в `decision_logs/`
|
||||
- Включает цепочку рассуждений, JSON решения, снимок аккаунта, результаты исполнения
|
||||
- Хранение полных данных позиции (количество, плечо, время открытия/закрытия)
|
||||
- Использование ключей `symbol_side` для предотвращения конфликтов лонг/шорт
|
||||
- 📌 **НОВОЕ (v2.0.2)**: Предотвращение конфликтов при удержании лонг + шорт, учет количества + плеча
|
||||
|
||||
**↓**
|
||||
|
||||
**🔄 (Повтор каждые 3-5 минут)**
|
||||
|
||||
### Ключевые улучшения в v2.0.2
|
||||
|
||||
@@ -1054,269 +1096,30 @@ sudo apt-get install libta-lib0-dev
|
||||
|
||||
- [Binance API](https://binance-docs.github.io/apidocs/futures/en/) - Binance Futures API
|
||||
- [DeepSeek](https://platform.deepseek.com/) - DeepSeek AI API
|
||||
- [Qwen](https://dashscope.aliyuncs.com/) - Alibaba Cloud Qwen
|
||||
- [Qwen](https://dashscope.console.aliyun.com/) - Alibaba Cloud Qwen
|
||||
- [TA-Lib](https://ta-lib.org/) - Библиотека технических индикаторов
|
||||
- [Recharts](https://recharts.org/) - Библиотека графиков React
|
||||
|
||||
---
|
||||
|
||||
## 🔄 История изменений
|
||||
## 🔄 Журнал Изменений
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "qwen_trader",
|
||||
"name": "Qwen AI Trader",
|
||||
"ai_model": "qwen",
|
||||
"binance_api_key": "ВАШ_BINANCE_API_KEY",
|
||||
"binance_secret_key": "ВАШ_BINANCE_SECRET_KEY",
|
||||
"use_qwen": true,
|
||||
"qwen_key": "sk-xxxxx",
|
||||
"scan_interval_minutes": 3,
|
||||
"initial_balance": 1000.0
|
||||
},
|
||||
{
|
||||
"id": "deepseek_trader",
|
||||
"name": "DeepSeek AI Trader",
|
||||
"ai_model": "deepseek",
|
||||
"binance_api_key": "ВАШ_BINANCE_API_KEY_2",
|
||||
"binance_secret_key": "ВАШ_BINANCE_SECRET_KEY_2",
|
||||
"use_qwen": false,
|
||||
"deepseek_key": "sk-xxxxx",
|
||||
"scan_interval_minutes": 3,
|
||||
"initial_balance": 1000.0
|
||||
}
|
||||
],
|
||||
"use_default_coins": false,
|
||||
"coin_pool_api_url": "http://x.x.x.x:xxx/api/ai500/list?auth=ВАШ_AUTH",
|
||||
"oi_top_api_url": "http://x.x.x.x:xxx/api/oi/top?auth=ВАШ_AUTH",
|
||||
"api_server_port": 8080
|
||||
}
|
||||
```
|
||||
📖 **Для подробной истории версий и обновлений см.:**
|
||||
|
||||
**Примечания к конфигурации:**
|
||||
- `traders`: Настройте 1-N трейдеров (один AI или соревнование нескольких AI)
|
||||
- `id`: Уникальный идентификатор трейдера (используется для директории логов)
|
||||
- `ai_model`: "qwen" или "deepseek"
|
||||
- `binance_api_key/secret_key`: Каждый трейдер использует независимый аккаунт Binance
|
||||
- `initial_balance`: Начальный баланс (для расчета P/L%)
|
||||
- `scan_interval_minutes`: Цикл принятия решений (рекомендуется 3-5 минут)
|
||||
- `use_default_coins`: **true** = Использовать 8 основных монет по умолчанию | **false** = Использовать API пул монет (рекомендуется для новичков: true)
|
||||
- `coin_pool_api_url`: API пула монет AI500 (опционально, игнорируется при use_default_coins=true)
|
||||
- `oi_top_api_url`: API открытого интереса OI Top (опционально, если пусто, данные OI Top пропускаются)
|
||||
- **Русский:** [CHANGELOG.zh-CN.md](../../../CHANGELOG.zh-CN.md)
|
||||
- **English:** [CHANGELOG.md](../../../CHANGELOG.md)
|
||||
|
||||
**Список монет по умолчанию** (когда `use_default_coins: true`):
|
||||
- BTC, ETH, SOL, BNB, XRP, DOGE, ADA, HYPE
|
||||
**Последняя Версия:** v3.0.0 (2025-10-30) - Масштабная Трансформация Архитектуры
|
||||
|
||||
### 5. Запуск системы
|
||||
|
||||
**Запуск backend (система AI торговли + API сервер):**
|
||||
|
||||
```bash
|
||||
go build -o nofx
|
||||
./nofx
|
||||
```
|
||||
|
||||
**Запуск frontend (веб-панель):**
|
||||
|
||||
Откройте новый терминал:
|
||||
|
||||
```bash
|
||||
cd web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Доступ к интерфейсу:**
|
||||
```
|
||||
Веб-панель: http://localhost:3000
|
||||
API сервер: http://localhost:8080
|
||||
```
|
||||
|
||||
### 6. Остановка системы
|
||||
|
||||
Нажмите `Ctrl+C` в обоих терминалах
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Важные предупреждения о рисках
|
||||
|
||||
### Торговые риски
|
||||
|
||||
1. **Рынки криптовалют чрезвычайно волатильны**, решения AI не гарантируют прибыль
|
||||
2. **Торговля фьючерсами использует плечо**, убытки могут превысить основную сумму
|
||||
3. **Экстремальные рыночные условия** могут привести к ликвидации
|
||||
4. **Комиссии за финансирование** могут повлиять на стоимость удержания
|
||||
5. **Риск ликвидности**: Некоторые монеты могут испытывать проскальзывание
|
||||
|
||||
### Технические риски
|
||||
|
||||
1. **Задержка сети** может вызвать проскальзывание цены
|
||||
2. **Лимиты API** могут повлиять на исполнение сделок
|
||||
3. **Тайм-ауты AI API** могут вызвать сбои решений
|
||||
4. **Системные ошибки** могут вызвать неожиданное поведение
|
||||
|
||||
### Рекомендации по использованию
|
||||
|
||||
✅ **Рекомендуется**
|
||||
- Используйте только средства, потерю которых вы можете позволить для тестирования
|
||||
- Начните с небольших сумм (рекомендуется 100-500 USDT)
|
||||
- Регулярно проверяйте состояние работы системы
|
||||
- Отслеживайте изменения баланса счета
|
||||
- Анализируйте логи решений AI для понимания стратегии
|
||||
|
||||
❌ **Не рекомендуется**
|
||||
- Инвестировать все средства или заемные деньги
|
||||
- Запускать без присмотра на длительные периоды
|
||||
- Слепо доверять решениям AI
|
||||
- Использовать без понимания системы
|
||||
- Запускать во время экстремальной волатильности рынка
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Частые проблемы
|
||||
|
||||
### 1. Ошибка компиляции: TA-Lib не найдена
|
||||
|
||||
**Решение**: Установите библиотеку TA-Lib
|
||||
```bash
|
||||
# macOS
|
||||
brew install ta-lib
|
||||
|
||||
# Ubuntu
|
||||
sudo apt-get install libta-lib0-dev
|
||||
```
|
||||
|
||||
### 2. Ошибка точности: Точность превышает максимум
|
||||
|
||||
**Решение**: Система автоматически обрабатывает точность из Binance LOT_SIZE. Если ошибка сохраняется, проверьте сетевое подключение.
|
||||
|
||||
### 3. Тайм-аут AI API
|
||||
|
||||
**Решение**:
|
||||
- Проверьте правильность API ключа
|
||||
- Проверьте сетевое подключение (может потребоваться прокси)
|
||||
- Тайм-аут системы установлен на 120 секунд
|
||||
|
||||
### 4. Frontend не может подключиться к backend
|
||||
|
||||
**Решение**:
|
||||
- Убедитесь, что backend запущен (http://localhost:8080)
|
||||
- Проверьте, не занят ли порт 8080
|
||||
- Проверьте ошибки в консоли браузера
|
||||
|
||||
### 5. Сбой API пула монет
|
||||
|
||||
**Решение**:
|
||||
- API пула монет опционален
|
||||
- Если API не работает, система использует основные монеты по умолчанию (BTC, ETH и т.д.)
|
||||
- Проверьте URL API и параметр auth в config.json
|
||||
|
||||
---
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Лицензия MIT - См. файл [LICENSE](LICENSE) для деталей
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Вклад в проект
|
||||
|
||||
Приветствуются Issues и Pull Requests!
|
||||
|
||||
### Руководство по разработке
|
||||
|
||||
1. Сделайте Fork проекта
|
||||
2. Создайте ветку функции (`git checkout -b feature/AmazingFeature`)
|
||||
3. Зафиксируйте изменения (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Отправьте в ветку (`git push origin feature/AmazingFeature`)
|
||||
5. Откройте Pull Request
|
||||
|
||||
---
|
||||
|
||||
## 📬 Контакты
|
||||
|
||||
- **Twitter/X**: [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
- **GitHub Issues**: [Создать Issue](https://github.com/tinkle-community/nofx/issues)
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Благодарности
|
||||
|
||||
- [Binance API](https://binance-docs.github.io/apidocs/futures/en/) - Binance Futures API
|
||||
- [DeepSeek](https://platform.deepseek.com/) - DeepSeek AI API
|
||||
- [Qwen](https://dashscope.aliyuncs.com/) - Alibaba Cloud Qwen
|
||||
- [TA-Lib](https://ta-lib.org/) - Библиотека технических индикаторов
|
||||
- [Recharts](https://recharts.org/) - Библиотека графиков React
|
||||
|
||||
---
|
||||
|
||||
## 🔄 История изменений
|
||||
|
||||
### v2.0.2 (2025-10-29)
|
||||
|
||||
**Критические исправления ошибок - История сделок и анализ производительности:**
|
||||
|
||||
Эта версия исправляет **критические ошибки расчета** в системе исторических записей сделок и анализа производительности, которые значительно влияли на статистику прибыльности.
|
||||
|
||||
**1. Расчет P/L - Исправление крупной ошибки** (logger/decision_logger.go)
|
||||
- **Проблема**: Ранее P/L рассчитывался только как процент, полностью игнорируя размер позиции и плечо
|
||||
- Пример: Позиция 100 USDT с доходом 5% и позиция 1000 USDT с доходом 5% обе показывали `5.0` как прибыль
|
||||
- Это делало анализ производительности полностью неточным
|
||||
- **Решение**: Теперь рассчитывается фактическая прибыль в USDT
|
||||
```
|
||||
P/L (USDT) = Стоимость позиции × Изменение цены % × Плечо
|
||||
Пример: 1000 USDT × 5% × 20x = 1000 USDT фактической прибыли
|
||||
```
|
||||
- **Влияние**: Процент выигрышей, коэффициент прибыли и коэффициент Шарпа теперь основаны на точных суммах USDT
|
||||
|
||||
**2. Отслеживание позиций - Отсутствие критических данных**
|
||||
- **Проблема**: Записи открытых позиций хранили только цену и время, пропуская количество и плечо
|
||||
- **Решение**: Теперь хранит полные торговые данные:
|
||||
- `quantity`: Размер позиции (в монетах)
|
||||
- `leverage`: Множитель плеча (например, 20x)
|
||||
- Эти данные необходимы для точного расчета P/L
|
||||
|
||||
**3. Логика ключа позиции - Конфликт Long/Short**
|
||||
- **Проблема**: Использовался `symbol` как ключ позиции, что вызывало конфликты данных при одновременном удержании лонгов и шортов
|
||||
- Пример: BTCUSDT лонг и BTCUSDT шорт перезаписывали друг друга
|
||||
- **Решение**: Изменено на формат `symbol_side` (например, `BTCUSDT_long`, `BTCUSDT_short`)
|
||||
- Теперь правильно различает лонг и шорт позиции
|
||||
|
||||
**4. Расчет коэффициента Шарпа - Оптимизация кода**
|
||||
- **Проблема**: Использовался пользовательский метод Ньютона для расчета квадратного корня
|
||||
- **Решение**: Заменен на стандартную библиотеку `math.Sqrt`
|
||||
- Более надежный, поддерживаемый и эффективный
|
||||
|
||||
**Почему это обновление важно:**
|
||||
- ✅ Историческая статистика сделок теперь показывает **реальную прибыль/убыток в USDT** вместо бессмысленных процентов
|
||||
- ✅ Сравнение производительности между сделками с разным плечом теперь точно
|
||||
- ✅ Механизм самообучения AI получает правильную историческую обратную связь
|
||||
- ✅ Расчеты коэффициента прибыли и коэффициента Шарпа теперь имеют смысл
|
||||
- ✅ Отслеживание нескольких позиций (лонг + шорт одновременно) теперь работает правильно
|
||||
|
||||
**Рекомендация**: Если вы запускали систему до этого обновления, ваша историческая статистика была неточной. После обновления до v2.0.2, новые сделки будут рассчитываться правильно.
|
||||
|
||||
### v2.0.1 (2025-10-29)
|
||||
|
||||
**Исправления ошибок:**
|
||||
- ✅ Исправлена логика обработки данных ComparisonChart - переход от группировки по cycle_number к timestamp
|
||||
- ✅ Решена проблема замораживания графика при перезапуске backend и сбросе cycle_number
|
||||
- ✅ Улучшено отображение данных графика - теперь показывает все исторические точки в хронологическом порядке
|
||||
- ✅ Улучшенные отладочные логи для лучшей диагностики
|
||||
|
||||
### v2.0.0 (2025-10-28)
|
||||
|
||||
**Основные обновления:**
|
||||
- ✅ Механизм самообучения AI (исторический анализ, анализ производительности)
|
||||
- ✅ Режим конкуренции нескольких трейдеров (Qwen vs DeepSeek)
|
||||
- ✅ UI в стиле Binance (полная имитация интерфейса Binance)
|
||||
- ✅ Графики сравнения производительности (сравнение ROI в реальном времени)
|
||||
- ✅ Оптимизация контроля рисков (корректировка лимита позиции по монетам)
|
||||
|
||||
---
|
||||
|
||||
**Последнее обновление**: 2025-10-29 (v2.0.2)
|
||||
**Недавние Основные Моменты:**
|
||||
- 🚀 Полная переработка системы с веб-конфигурацией
|
||||
- 🗄️ Архитектура на основе базы данных (SQLite)
|
||||
- 🎨 Никакого редактирования JSON - вся конфигурация через веб-интерфейс
|
||||
- 🔧 Комбинируйте AI модели с любой биржей
|
||||
- 📊 Расширенный API слой с комплексными эндпоинтами
|
||||
- 🔐 Аутентификация JWT + поддержка 2FA
|
||||
- 🌐 Поддержка кастомных API (совместимых с OpenAI)
|
||||
- 📈 Система шаблонов промптов с удаленной аутентификацией
|
||||
|
||||
**⚡ Исследуйте возможности количественной торговли с силой AI!**
|
||||
|
||||
@@ -6,10 +6,30 @@
|
||||
[](LICENSE)
|
||||
[](https://amber.ac)
|
||||
|
||||
**Мови / Languages:** [English](README.md) | [中文](README.zh-CN.md) | [Українська](README.uk.md) | [Русский](README.ru.md)
|
||||
**Мови / Languages:** [English](../../../README.md) | [中文](../zh-CN/README.md) | [Українська](../uk/README.md) | [Русский](../ru/README.md)
|
||||
|
||||
**Офіційний Twitter:** [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
**📚 Документація:** [Головна](../../README.md) | [Початок роботи](../../getting-started/README.md) | [Спільнота](../../community/README.md) | [Журнал Змін](../../../CHANGELOG.md)
|
||||
|
||||
---
|
||||
|
||||
## 📑 Зміст
|
||||
|
||||
- [🚀 Універсальна AI Торгова Операційна Система](#-універсальна-ai-торгова-операційна-система)
|
||||
- [👥 Спільнота розробників](#-спільнота-розробників)
|
||||
- [🆕 Останні оновлення](#-останні-оновлення)
|
||||
- [🏗️ Технічна Архітектура](#️-технічна-архітектура)
|
||||
- [📸 Системні Скріншоти](#-системні-скріншоти)
|
||||
- [🎮 Швидкий Старт](#-швидкий-старт)
|
||||
- [📊 AI Модель](#-ai-модель)
|
||||
- [📈 Огляд Продуктивності](#-огляд-продуктивності)
|
||||
- [📄 Ліцензія](#-ліцензія)
|
||||
- [🤝 Внесок у проєкт](#-внесок-у-проєкт)
|
||||
- [📬 Контакти](#-контакти)
|
||||
- [🙏 Подяки](#-подяки)
|
||||
- [🔄 Журнал Змін](#-журнал-змін)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Універсальна AI Торгова Операційна Система
|
||||
@@ -74,7 +94,7 @@ NOFX тепер підтримує **три основні біржі**: Binance
|
||||
|
||||
**Швидкий старт:**
|
||||
1. Отримайте приватний ключ MetaMask (видаліть префікс `0x`)
|
||||
2. Встановіть `"exchange": "hyperliquid"` в config.json
|
||||
2. ~~Встановіть `"exchange": "hyperliquid"` в config.json~~ *Налаштуйте через веб-інтерфейс*
|
||||
3. Додайте `"hyperliquid_private_key": "your_key"`
|
||||
4. Почніть торгувати!
|
||||
|
||||
@@ -110,15 +130,19 @@ NOFX тепер підтримує **три основні біржі**: Binance
|
||||
## 📸 Скриншоти
|
||||
|
||||
### 🏆 Режим змагання - Битва AI в реальному часі
|
||||

|
||||

|
||||
*Лідерборд з кількома AI та графіки порівняння продуктивності в реальному часі показують битву Qwen проти DeepSeek*
|
||||
|
||||
### 📊 Деталі трейдера - Повна торгова панель
|
||||

|
||||

|
||||
*Професійний торговий інтерфейс з кривими капіталу, живими позиціями та логами рішень AI з розкриваємими вхідними промптами та ланцюгом міркувань*
|
||||
|
||||
---
|
||||
|
||||
> 📘 **Примітка**: Це спрощена українська версія README. Для отримання повної технічної документації, включаючи архітектуру системи, API-інтерфейси та розширені конфігурації, див. [Англійську версію](../../../README.md) або [Китайську версію](../zh-CN/README.md).
|
||||
|
||||
---
|
||||
|
||||
## ✨ Основні можливості
|
||||
|
||||
### 🏆 Режим змагання кількох AI
|
||||
@@ -174,6 +198,57 @@ NOFX тепер підтримує **три основні біржі**: Binance
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Дорожня Карта - Розширення на Універсальні Ринки
|
||||
|
||||
Місія NOFX - стати **Універсальною AI Торговою Операційною Системою** для всіх фінансових ринків.
|
||||
|
||||
**Бачення:** Та сама архітектура. Та сама агентна структура. Всі ринки.
|
||||
|
||||
**Розширення на Ринки:**
|
||||
- 📈 **Фондові Ринки**: Акції США, A-акції, Гонконгська біржа
|
||||
- 📊 **Ринки Ф'ючерсів**: Товарні ф'ючерси, індексні ф'ючерси
|
||||
- 🎯 **Опціонна Торгівля**: Опціони на акції, крипто опціони
|
||||
- 💱 **Ринки Форекс**: Основні валютні пари, крос-курси
|
||||
|
||||
**Майбутні Функції:**
|
||||
- Розширені AI можливості (GPT-4, Claude 3, Gemini Pro, гнучкі шаблони промптів)
|
||||
- Нові інтеграції бірж (OKX, Bybit, Lighter, EdgeX + CEX/Perp-DEX)
|
||||
- Рефакторинг структури проєкту (висока зв'язність, низька зчепленість, принципи SOLID)
|
||||
- Покращення безпеки (AES-256 шифрування API ключів, RBAC, покращення 2FA)
|
||||
- Покращення користувацького досвіду (мобільний інтерфейс, графіки TradingView, система сповіщень)
|
||||
|
||||
📖 **Для детальної дорожньої карти та термінів див.:**
|
||||
- **English:** [Roadmap Documentation](../../roadmap/README.md)
|
||||
- **中文:** [路线图文档](../../roadmap/README.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Технічна Архітектура
|
||||
|
||||
NOFX побудовано на сучасній модульній архітектурі:
|
||||
|
||||
- **Backend:** Go з фреймворком Gin, база даних SQLite
|
||||
- **Frontend:** React 18 + TypeScript + Vite + TailwindCSS
|
||||
- **AI інтеграція:** DeepSeek, Qwen, кастомні API (сумісні з OpenAI)
|
||||
- **Підтримка бірж:** Binance Futures, Hyperliquid DEX, Aster DEX
|
||||
- **Аутентифікація:** JWT токени + підтримка 2FA
|
||||
- **Управління станом:** Zustand (легковагове)
|
||||
- **Отримання даних:** SWR з опитуванням 5-10с
|
||||
- **Графіки:** Recharts для кривих капіталу та порівнянь
|
||||
|
||||
**Ключові особливості:**
|
||||
- 🔧 Архітектура на основі бази даних (конфігурація через веб-інтерфейс, без JSON)
|
||||
- 🎯 Комбінуйте будь-яку AI модель з будь-якою біржею
|
||||
- 📊 RESTful API з комплексними ендпоінтами
|
||||
- 🔐 Безпечне управління облікових даних
|
||||
- 📈 Система шаблонів промптів з віддаленою аутентифікацією
|
||||
|
||||
📖 **Детальна документація по архітектурі:**
|
||||
- **English:** [Architecture Documentation](../../architecture/README.md)
|
||||
- **中文:** [架构文档](../../architecture/README.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 💰 Реєстрація акаунта Binance (Заощаджуйте на комісіях!)
|
||||
|
||||
Перед використанням цієї системи вам потрібен акаунт Binance Futures. **Використовуйте наше реферальне посилання для отримання знижки на комісії:**
|
||||
@@ -213,7 +288,7 @@ Docker автоматично обробляє всі залежності (Go,
|
||||
#### Крок 1: Підготуйте конфігурацію
|
||||
```bash
|
||||
# Скопіюйте шаблон конфігурації
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
|
||||
# Відредагуйте та заповніть ваші API ключі
|
||||
nano config.json # або використайте будь-який редактор
|
||||
@@ -328,7 +403,7 @@ cd ..
|
||||
|
||||
**Як отримати Qwen API ключ:**
|
||||
|
||||
1. **Відвідайте**: [https://dashscope.aliyuncs.com](https://dashscope.aliyuncs.com)
|
||||
1. **Відвідайте**: [https://dashscope.console.aliyun.com](https://dashscope.console.aliyun.com)
|
||||
2. **Зареєструйтеся**: Використовуючи акаунт Alibaba Cloud
|
||||
3. **Активуйте сервіс**: Активуйте DashScope сервіс
|
||||
4. **Створіть API ключ**:
|
||||
@@ -351,7 +426,7 @@ cd ..
|
||||
**Крок 1**: Скопіюйте та перейменуйте файл прикладу конфігурації
|
||||
|
||||
```bash
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
```
|
||||
|
||||
**Крок 2**: Відредагуйте `config.json` та заповніть ваші API ключі
|
||||
@@ -724,7 +799,7 @@ go build -o nofx
|
||||
|--------------------------|---------|
|
||||
| `invalid API key` | Перевірте Binance API ключі в config.json |
|
||||
| `TA-Lib not found` | Виконайте `brew install ta-lib` (macOS) |
|
||||
| `port 8080 already in use` | Змініть `api_server_port` в config.json |
|
||||
| `port 8080 already in use` | ~~Змініть `api_server_port` в config.json~~ *Змініть `API_PORT` у файлі .env* |
|
||||
| `DeepSeek API error` | Перевірте DeepSeek API ключ та баланс |
|
||||
|
||||
**✅ Ознаки роботи Backend:**
|
||||
@@ -798,7 +873,7 @@ VITE v5.x.x ready in xxx ms
|
||||
|
||||
```bash
|
||||
# У новому вікні терміналу
|
||||
curl http://localhost:8080/health
|
||||
curl http://localhost:8080/api/health
|
||||
```
|
||||
|
||||
Повинно повернути: `{"status":"ok"}`
|
||||
@@ -826,104 +901,73 @@ curl http://localhost:8080/health
|
||||
|
||||
Кожен цикл прийняття рішень (за замовчуванням 3 хвилини), система працює за наступним процесом:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 1. 📊 Аналіз історичної продуктивності │
|
||||
│ (останні 20 циклів) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ✓ Розрахунок загального відсотка виграшів, середнього │
|
||||
│ прибутку, співвідношення прибутку/збитку │
|
||||
│ ✓ Статистика по кожній монеті (відсоток виграшів, │
|
||||
│ середній P/L в USDT) │
|
||||
│ ✓ Визначення найкращих/найгірших монет за │
|
||||
│ продуктивністю │
|
||||
│ ✓ Список деталей останніх 5 угод з точним P/L │
|
||||
│ ✓ Розрахунок коефіцієнта Шарпа для оцінки ризику │
|
||||
│ 📌 НОВЕ (v2.0.2): Точний P/L в USDT з врахуванням │
|
||||
│ плеча │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 2. 💰 Отримання стану акаунта │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Капітал акаунта, доступний баланс, нереалізований │
|
||||
│ P/L │
|
||||
│ • Кількість позицій, загальний P/L (реалізований + │
|
||||
│ нереалізований) │
|
||||
│ • Використання маржі (поточне/максимальне) │
|
||||
│ • Індикатори оцінки ризику │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 3. 🔍 Аналіз існуючих позицій (якщо є) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Отримання ринкових даних для кожної позиції │
|
||||
│ (3-хвилинні + 4-годинні свічки) │
|
||||
│ • Розрахунок технічних індикаторів (RSI, MACD, EMA) │
|
||||
│ • Відображення тривалості утримання позиції │
|
||||
│ (наприклад, "утримується 2 години 15 хвилин") │
|
||||
│ • AI визначає, чи потрібно закрити (тейк-профіт, │
|
||||
│ стоп-лосс або коригування) │
|
||||
│ 📌 НОВЕ (v2.0.2): Відстеження тривалості позиції │
|
||||
│ допомагає AI вирішувати │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 4. 🎯 Оцінка нових можливостей (пул кандидатів монет) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Отримання топ-20 монет з високим рейтингом AI500 │
|
||||
│ • Отримання топ-20 монет з найшвидшим зростанням OI │
|
||||
│ • Об'єднання, видалення дублікатів, фільтрація монет з │
|
||||
│ низькою ліквідністю (OI < 15M USD) │
|
||||
│ • Масове отримання ринкових даних та технічних │
|
||||
│ індикаторів │
|
||||
│ • Підготовка повних послідовностей сирих даних для │
|
||||
│ кожної монети-кандидата │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 5. 🧠 Комплексне рішення AI │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Перегляд історичного зворотного зв'язку (відсоток │
|
||||
│ виграшів, коефіцієнт P/L, найкращі/найгірші монети) │
|
||||
│ • Отримання всіх даних послідовностей (свічки, │
|
||||
│ індикатори, відкритий інтерес) │
|
||||
│ • Аналіз Chain of Thought │
|
||||
│ • Вивід рішення: закрити/відкрити/утримувати/спостерігати │
|
||||
│ • Включає параметри плеча, розміру, стоп-лосса, │
|
||||
│ тейк-профіта │
|
||||
│ 📌 НОВЕ (v2.0.2): AI може вільно аналізувати сирі │
|
||||
│ послідовності, не обмежений заздалегідь визначеними │
|
||||
│ індикаторами │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 6. ⚡ Виконання угод │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Пріоритизація: спочатку закриття, потім відкриття │
|
||||
│ • Автоматична адаптація точності (правила LOT_SIZE) │
|
||||
│ • Запобігання накопиченню позицій (відхилення │
|
||||
│ дублювання монета/напрямок) │
|
||||
│ • Автоматична відміна всіх ордерів після закриття │
|
||||
│ • Запис часу відкриття для відстеження тривалості │
|
||||
│ позиції │
|
||||
│ 📌 НОВЕ (v2.0.2): Відстеження часу відкриття позиції │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 7. 📝 Запис логів │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • Збереження повного запису рішення в decision_logs/ │
|
||||
│ • Включає ланцюг міркувань, JSON рішення, знімок │
|
||||
│ акаунта, результати виконання │
|
||||
│ • Зберігання повних даних позиції (кількість, плече, │
|
||||
│ час відкриття/закриття) │
|
||||
│ • Використання ключів symbol_side для запобігання │
|
||||
│ конфліктів лонг/шорт │
|
||||
│ 📌 НОВЕ (v2.0.2): Запобігання конфліктів при утриманні │
|
||||
│ лонг + шорт, врахування кількості + плеча │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
### Крок 1: 📊 Аналіз історичної продуктивності (останні 20 циклів)
|
||||
- ✓ Розрахунок загального відсотка виграшів, середнього прибутку, співвідношення прибутку/збитку
|
||||
- ✓ Статистика по кожній монеті (відсоток виграшів, середній P/L в USDT)
|
||||
- ✓ Визначення найкращих/найгірших монет за продуктивністю
|
||||
- ✓ Список деталей останніх 5 угод з точним P/L
|
||||
- ✓ Розрахунок коефіцієнта Шарпа для оцінки ризику
|
||||
- 📌 **НОВЕ (v2.0.2)**: Точний P/L в USDT з врахуванням плеча
|
||||
|
||||
**↓**
|
||||
|
||||
### Крок 2: 💰 Отримання стану акаунта
|
||||
- Капітал акаунта, доступний баланс, нереалізований P/L
|
||||
- Кількість позицій, загальний P/L (реалізований + нереалізований)
|
||||
- Використання маржі (поточне/максимальне)
|
||||
- Індикатори оцінки ризику
|
||||
|
||||
**↓**
|
||||
|
||||
### Крок 3: 🔍 Аналіз існуючих позицій (якщо є)
|
||||
- Отримання ринкових даних для кожної позиції (3-хвилинні + 4-годинні свічки)
|
||||
- Розрахунок технічних індикаторів (RSI, MACD, EMA)
|
||||
- Відображення тривалості утримання позиції (наприклад, "утримується 2 години 15 хвилин")
|
||||
- AI визначає, чи потрібно закрити (тейк-профіт, стоп-лосс або коригування)
|
||||
- 📌 **НОВЕ (v2.0.2)**: Відстеження тривалості позиції допомагає AI вирішувати
|
||||
|
||||
**↓**
|
||||
|
||||
### Крок 4: 🎯 Оцінка нових можливостей (пул кандидатів монет)
|
||||
- Отримання пулу монет (2 режими):
|
||||
- 🌟 **Режим за замовчуванням**: BTC, ETH, SOL, BNB, XRP тощо
|
||||
- ⚙️ **Розширений режим**: AI500 (топ-20) + OI Top (топ-20)
|
||||
- Об'єднання, видалення дублікатів, фільтрація монет з низькою ліквідністю (OI < 15M USD)
|
||||
- Масове отримання ринкових даних та технічних індикаторів
|
||||
- Підготовка повних послідовностей сирих даних для кожної монети-кандидата
|
||||
|
||||
**↓**
|
||||
|
||||
### Крок 5: 🧠 Комплексне рішення AI
|
||||
- Перегляд історичного зворотного зв'язку (відсоток виграшів, коефіцієнт P/L, найкращі/найгірші монети)
|
||||
- Отримання всіх даних послідовностей (свічки, індикатори, відкритий інтерес)
|
||||
- Аналіз Chain of Thought
|
||||
- Вивід рішення: закрити/відкрити/утримувати/спостерігати
|
||||
- Включає параметри плеча, розміру, стоп-лосса, тейк-профіта
|
||||
- 📌 **НОВЕ (v2.0.2)**: AI може вільно аналізувати сирі послідовності, не обмежений заздалегідь визначеними індикаторами
|
||||
|
||||
**↓**
|
||||
|
||||
### Крок 6: ⚡ Виконання угод
|
||||
- Пріоритизація: спочатку закриття, потім відкриття
|
||||
- Автоматична адаптація точності (правила LOT_SIZE)
|
||||
- Запобігання накопиченню позицій (відхилення дублювання монета/напрямок)
|
||||
- Автоматична відміна всіх ордерів після закриття
|
||||
- Запис часу відкриття для відстеження тривалості позиції
|
||||
- 📌 Відстеження часу відкриття позиції
|
||||
|
||||
**↓**
|
||||
|
||||
### Крок 7: 📝 Запис логів
|
||||
- Збереження повного запису рішення в `decision_logs/`
|
||||
- Включає ланцюг міркувань, JSON рішення, знімок акаунта, результати виконання
|
||||
- Зберігання повних даних позиції (кількість, плече, час відкриття/закриття)
|
||||
- Використання ключів `symbol_side` для запобігання конфліктів лонг/шорт
|
||||
- 📌 **НОВЕ (v2.0.2)**: Запобігання конфліктів при утриманні лонг + шорт, врахування кількості + плеча
|
||||
|
||||
**↓**
|
||||
|
||||
**🔄 (Повтор кожні 3-5 хвилин)**
|
||||
|
||||
### Ключові покращення в v2.0.2
|
||||
|
||||
@@ -952,6 +996,7 @@ curl http://localhost:8080/health
|
||||
## ⚠️ Важливі попередження про ризики
|
||||
|
||||
### Торговельні ризики
|
||||
```
|
||||
{
|
||||
"id": "qwen_trader",
|
||||
"name": "Qwen AI Trader",
|
||||
@@ -1015,6 +1060,7 @@ npm run dev
|
||||
```
|
||||
|
||||
**Доступ до інтерфейсу:**
|
||||
|
||||
```
|
||||
Веб-панель: http://localhost:3000
|
||||
API сервер: http://localhost:8080
|
||||
@@ -1026,184 +1072,26 @@ API сервер: http://localhost:8080
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Важливі попередження про ризики
|
||||
|
||||
### Торговельні ризики
|
||||
|
||||
1. **Ринки криптовалют надзвичайно волатильні**, рішення AI не гарантують прибуток
|
||||
2. **Торгівля ф'ючерсами використовує плече**, збитки можуть перевищити основну суму
|
||||
3. **Екстремальні ринкові умови** можуть призвести до ліквідації
|
||||
4. **Комісії за фінансування** можуть вплинути на вартість утримання
|
||||
5. **Ризик ліквідності**: Деякі монети можуть відчувати проковзування
|
||||
|
||||
### Технічні ризики
|
||||
|
||||
1. **Затримка мережі** може викликати проковзування ціни
|
||||
2. **Ліміти API** можуть вплинути на виконання угод
|
||||
3. **Тайм-аути AI API** можуть викликати збої рішень
|
||||
4. **Системні помилки** можуть викликати неочікувану поведінку
|
||||
|
||||
### Рекомендації щодо використання
|
||||
|
||||
✅ **Рекомендується**
|
||||
- Використовуйте лише кошти, втрату яких ви можете дозволити для тестування
|
||||
- Почніть з невеликих сум (рекомендується 100-500 USDT)
|
||||
- Регулярно перевіряйте стан роботи системи
|
||||
- Відстежуйте зміни балансу рахунку
|
||||
- Аналізуйте логи рішень AI для розуміння стратегії
|
||||
|
||||
❌ **Не рекомендується**
|
||||
- Інвестувати всі кошти або позичені гроші
|
||||
- Запускати без нагляду на тривалі періоди
|
||||
- Сліпо довіряти рішенням AI
|
||||
- Використовувати без розуміння системи
|
||||
- Запускати під час екстремальної волатильності ринку
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Часті проблеми
|
||||
## 🔄 Журнал Змін
|
||||
|
||||
### 1. Помилка компіляції: TA-Lib не знайдена
|
||||
📖 **Для детальної історії версій та оновлень див.:**
|
||||
|
||||
**Рішення**: Встановіть бібліотеку TA-Lib
|
||||
```bash
|
||||
# macOS
|
||||
brew install ta-lib
|
||||
- **Українська:** [CHANGELOG.zh-CN.md](../../../CHANGELOG.zh-CN.md)
|
||||
- **English:** [CHANGELOG.md](../../../CHANGELOG.md)
|
||||
|
||||
# Ubuntu
|
||||
sudo apt-get install libta-lib0-dev
|
||||
```
|
||||
**Остання Версія:** v3.0.0 (2025-10-30) - Масштабна Трансформація Архітектури
|
||||
|
||||
### 2. Помилка точності: Точність перевищує максимум
|
||||
|
||||
**Рішення**: Система автоматично обробляє точність з Binance LOT_SIZE. Якщо помилка зберігається, перевірте мережеве підключення.
|
||||
|
||||
### 3. Тайм-аут AI API
|
||||
|
||||
**Рішення**:
|
||||
- Перевірте правильність API ключа
|
||||
- Перевірте мережеве підключення (може знадобитися проксі)
|
||||
- Тайм-аут системи встановлено на 120 секунд
|
||||
|
||||
### 4. Frontend не може підключитися до backend
|
||||
|
||||
**Рішення**:
|
||||
- Переконайтеся, що backend запущено (http://localhost:8080)
|
||||
- Перевірте, чи не зайнятий порт 8080
|
||||
- Перевірте помилки в консолі браузера
|
||||
|
||||
### 5. Збій API пулу монет
|
||||
|
||||
**Рішення**:
|
||||
- API пулу монет опціонален
|
||||
- Якщо API не працює, система використовує основні монети за замовчуванням (BTC, ETH тощо)
|
||||
- Перевірте URL API та параметр auth в config.json
|
||||
|
||||
---
|
||||
|
||||
## 📄 Ліцензія
|
||||
|
||||
Ліцензія MIT - Див. файл [LICENSE](LICENSE) для деталей
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Внесок у проєкт
|
||||
|
||||
Вітаються Issues та Pull Requests!
|
||||
|
||||
### Керівництво з розробки
|
||||
|
||||
1. Зробіть Fork проєкту
|
||||
2. Створіть гілку функції (`git checkout -b feature/AmazingFeature`)
|
||||
3. Зафіксуйте зміни (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Надішліть до гілки (`git push origin feature/AmazingFeature`)
|
||||
5. Відкрийте Pull Request
|
||||
|
||||
---
|
||||
|
||||
## 📬 Контакти
|
||||
|
||||
- **Twitter/X**: [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
- **GitHub Issues**: [Створити Issue](https://github.com/tinkle-community/nofx/issues)
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Подяки
|
||||
|
||||
- [Binance API](https://binance-docs.github.io/apidocs/futures/en/) - Binance Futures API
|
||||
- [DeepSeek](https://platform.deepseek.com/) - DeepSeek AI API
|
||||
- [Qwen](https://dashscope.aliyuncs.com/) - Alibaba Cloud Qwen
|
||||
- [TA-Lib](https://ta-lib.org/) - Бібліотека технічних індикаторів
|
||||
- [Recharts](https://recharts.org/) - Бібліотека графіків React
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Історія змін
|
||||
|
||||
### v2.0.2 (2025-10-29)
|
||||
|
||||
**Критичні виправлення помилок - Історія угод та аналіз продуктивності:**
|
||||
|
||||
Ця версія виправляє **критичні помилки розрахунку** в системі історичних записів угод та аналізу продуктивності, які значно впливали на статистику прибутковості.
|
||||
|
||||
**1. Розрахунок P/L - Виправлення великої помилки** (logger/decision_logger.go)
|
||||
- **Проблема**: Раніше P/L розраховувався лише як відсоток, повністю ігноруючи розмір позиції та плече
|
||||
- Приклад: Позиція 100 USDT з доходом 5% та позиція 1000 USDT з доходом 5% обидві показували `5.0` як прибуток
|
||||
- Це робило аналіз продуктивності повністю неточним
|
||||
- **Рішення**: Тепер розраховується фактичний прибуток в USDT
|
||||
```
|
||||
P/L (USDT) = Вартість позиції × Зміна ціни % × Плече
|
||||
Приклад: 1000 USDT × 5% × 20x = 1000 USDT фактичного прибутку
|
||||
```
|
||||
- **Вплив**: Відсоток виграшів, коефіцієнт прибутку та коефіцієнт Шарпа тепер засновані на точних сумах USDT
|
||||
|
||||
**2. Відстеження позицій - Відсутність критичних даних**
|
||||
- **Проблема**: Записи відкритих позицій зберігали лише ціну та час, пропускаючи кількість та плече
|
||||
- **Рішення**: Тепер зберігає повні торгові дані:
|
||||
- `quantity`: Розмір позиції (в монетах)
|
||||
- `leverage`: Множник плеча (наприклад, 20x)
|
||||
- Ці дані необхідні для точного розрахунку P/L
|
||||
|
||||
**3. Логіка ключа позиції - Конфлікт Long/Short**
|
||||
- **Проблема**: Використовувався `symbol` як ключ позиції, що викликало конфлікти даних при одночасному утриманні лонгів та шортів
|
||||
- Приклад: BTCUSDT лонг та BTCUSDT шорт перезаписували один одного
|
||||
- **Рішення**: Змінено на формат `symbol_side` (наприклад, `BTCUSDT_long`, `BTCUSDT_short`)
|
||||
- Тепер правильно розрізняє лонг та шорт позиції
|
||||
|
||||
**4. Розрахунок коефіцієнта Шарпа - Оптимізація коду**
|
||||
- **Проблема**: Використовувався користувацький метод Ньютона для розрахунку квадратного кореня
|
||||
- **Рішення**: Замінено на стандартну бібліотеку `math.Sqrt`
|
||||
- Більш надійний, підтримуваний та ефективний
|
||||
|
||||
**Чому це оновлення важливе:**
|
||||
- ✅ Історична статистика угод тепер показує **реальний прибуток/збиток в USDT** замість безглуздих відсотків
|
||||
- ✅ Порівняння продуктивності між угодами з різним плечем тепер точне
|
||||
- ✅ Механізм самонавчання AI отримує правильний історичний зворотний зв'язок
|
||||
- ✅ Розрахунки коефіцієнта прибутку та коефіцієнта Шарпа тепер мають сенс
|
||||
- ✅ Відстеження кількох позицій (лонг + шорт одночасно) тепер працює правильно
|
||||
|
||||
**Рекомендація**: Якщо ви запускали систему до цього оновлення, ваша історична статистика була неточною. Після оновлення до v2.0.2, нові угоди будуть розраховуватися правильно.
|
||||
|
||||
### v2.0.1 (2025-10-29)
|
||||
|
||||
**Виправлення помилок:**
|
||||
- ✅ Виправлено логіку обробки даних ComparisonChart - перехід від групування по cycle_number до timestamp
|
||||
- ✅ Вирішено проблему заморожування графіка при перезапуску backend та скиданні cycle_number
|
||||
- ✅ Покращено відображення даних графіка - тепер показує всі історичні точки в хронологічному порядку
|
||||
- ✅ Покращені відладочні логи для кращої діагностики
|
||||
|
||||
### v2.0.0 (2025-10-28)
|
||||
|
||||
**Основні оновлення:**
|
||||
- ✅ Механізм самонавчання AI (історичний аналіз, аналіз продуктивності)
|
||||
- ✅ Режим змагання кількох трейдерів (Qwen vs DeepSeek)
|
||||
- ✅ UI в стилі Binance (повна імітація інтерфейсу Binance)
|
||||
- ✅ Графіки порівняння продуктивності (порівняння ROI в реальному часі)
|
||||
- ✅ Оптимізація контролю ризиків (коригування ліміту позиції по монетах)
|
||||
|
||||
---
|
||||
|
||||
**Останнє оновлення**: 2025-10-29 (v2.0.2)
|
||||
**Недавні Основні Моменти:**
|
||||
- 🚀 Повна переробка системи з веб-конфігурацією
|
||||
- 🗄️ Архітектура на основі бази даних (SQLite)
|
||||
- 🎨 Ніякого редагування JSON - вся конфігурація через веб-інтерфейс
|
||||
- 🔧 Комбінуйте AI моделі з будь-якою біржею
|
||||
- 📊 Розширений API шар з комплексними ендпоінтами
|
||||
- 🔐 Аутентифікація JWT + підтримка 2FA
|
||||
- 🌐 Підтримка кастомних API (сумісних з OpenAI)
|
||||
- 📈 Система шаблонів промптів з віддаленою аутентифікацією
|
||||
|
||||
**⚡ Досліджуйте можливості кількісної торгівлі з силою AI!**
|
||||
|
||||
481
docs/i18n/zh-CN/CONTRIBUTING.md
Normal file
@@ -0,0 +1,481 @@
|
||||
# 🤝 为 NOFX 做贡献
|
||||
|
||||
**语言:** [English](../../../CONTRIBUTING.md) | [中文](CONTRIBUTING.md)
|
||||
|
||||
感谢您有兴趣为 NOFX 做贡献!本文档提供了为项目做贡献的指南和工作流程。
|
||||
|
||||
---
|
||||
|
||||
## 📑 目录
|
||||
|
||||
- [行为准则](#行为准则)
|
||||
- [如何贡献](#如何贡献)
|
||||
- [开发工作流程](#开发工作流程)
|
||||
- [PR 提交指南](#pr-提交指南)
|
||||
- [编码规范](#编码规范)
|
||||
- [提交信息指南](#提交信息指南)
|
||||
- [审核流程](#审核流程)
|
||||
- [悬赏计划](#悬赏计划)
|
||||
|
||||
---
|
||||
|
||||
## 📜 行为准则
|
||||
|
||||
本项目遵守[行为准则](../../../CODE_OF_CONDUCT.md)。参与项目即表示您同意遵守此准则。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 如何贡献
|
||||
|
||||
### 1. 报告 Bug 🐛
|
||||
|
||||
- 使用 [Bug 报告模板](../../../.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
- 检查 bug 是否已被报告
|
||||
- 包含详细的重现步骤
|
||||
- 提供环境信息(操作系统、Go 版本等)
|
||||
|
||||
### 2. 建议功能 ✨
|
||||
|
||||
- 使用[功能请求模板](../../../.github/ISSUE_TEMPLATE/feature_request.md)
|
||||
- 解释使用场景和好处
|
||||
- 检查是否与[项目路线图](../../roadmap/README.zh-CN.md)一致
|
||||
|
||||
### 3. 提交 Pull Request 🔧
|
||||
|
||||
提交 PR 前,请检查以下内容:
|
||||
|
||||
#### ✅ **接受的贡献**
|
||||
|
||||
**高优先级**(与路线图一致):
|
||||
- 🔒 安全增强(加密、认证、RBAC)
|
||||
- 🧠 AI 模型集成(GPT-4、Claude、Gemini Pro)
|
||||
- 🔗 交易所集成(OKX、Bybit、Lighter、EdgeX)
|
||||
- 📊 交易数据 API(AI500、OI 分析、NetFlow)
|
||||
- 🎨 UI/UX 改进(移动端响应式、图表)
|
||||
- ⚡ 性能优化
|
||||
- 🐛 Bug 修复
|
||||
- 📝 文档改进
|
||||
|
||||
**中等优先级:**
|
||||
- ✅ 测试覆盖率改进
|
||||
- 🌐 国际化(新语言支持)
|
||||
- 🔧 构建/部署工具
|
||||
- 📈 监控和日志增强
|
||||
|
||||
#### ❌ **不接受**(未经事先讨论)
|
||||
|
||||
- 没有 RFC(征求意见稿)的重大架构变更
|
||||
- 与项目路线图不一致的功能
|
||||
- 没有迁移路径的破坏性变更
|
||||
- 引入新依赖但没有充分理由的代码
|
||||
- 没有可选标志的实验性功能
|
||||
|
||||
**⚠️ 重要:** 对于重大功能,请在开始工作**之前**先开 issue 讨论。
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 开发工作流程
|
||||
|
||||
### 1. Fork 和 Clone
|
||||
|
||||
```bash
|
||||
# 在 GitHub 上 Fork 仓库
|
||||
# 然后 clone 你的 fork
|
||||
git clone https://github.com/YOUR_USERNAME/nofx.git
|
||||
cd nofx
|
||||
|
||||
# 添加 upstream remote
|
||||
git remote add upstream https://github.com/tinkle-community/nofx.git
|
||||
```
|
||||
|
||||
### 2. 创建功能分支
|
||||
|
||||
```bash
|
||||
# 更新你的本地 dev 分支
|
||||
git checkout dev
|
||||
git pull upstream dev
|
||||
|
||||
# 创建新分支
|
||||
git checkout -b feature/your-feature-name
|
||||
# 或
|
||||
git checkout -b fix/your-bug-fix
|
||||
```
|
||||
|
||||
**分支命名规范:**
|
||||
- `feature/` - 新功能
|
||||
- `fix/` - Bug 修复
|
||||
- `docs/` - 文档更新
|
||||
- `refactor/` - 代码重构
|
||||
- `perf/` - 性能改进
|
||||
- `test/` - 测试更新
|
||||
- `chore/` - 构建/配置更改
|
||||
|
||||
### 3. 设置开发环境
|
||||
|
||||
```bash
|
||||
# 安装 Go 依赖
|
||||
go mod download
|
||||
|
||||
# 安装前端依赖
|
||||
cd web
|
||||
npm install
|
||||
cd ..
|
||||
|
||||
# 安装 TA-Lib(必需)
|
||||
# macOS:
|
||||
brew install ta-lib
|
||||
|
||||
# Ubuntu/Debian:
|
||||
sudo apt-get install libta-lib0-dev
|
||||
```
|
||||
|
||||
### 4. 进行更改
|
||||
|
||||
- 遵循[编码规范](#编码规范)
|
||||
- 为新功能编写测试
|
||||
- 根据需要更新文档
|
||||
- 保持提交专注和原子性
|
||||
|
||||
### 5. 测试你的更改
|
||||
|
||||
```bash
|
||||
# 运行后端测试
|
||||
go test ./...
|
||||
|
||||
# 构建后端
|
||||
go build -o nofx
|
||||
|
||||
# 以开发模式运行前端
|
||||
cd web
|
||||
npm run dev
|
||||
|
||||
# 构建前端
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 6. 提交你的更改
|
||||
|
||||
遵循[提交信息指南](#提交信息指南):
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: add support for OKX exchange integration"
|
||||
```
|
||||
|
||||
### 7. 推送并创建 PR
|
||||
|
||||
```bash
|
||||
# 推送到你的 fork
|
||||
git push origin feature/your-feature-name
|
||||
|
||||
# 前往 GitHub 创建 Pull Request
|
||||
# 使用 PR 模板并填写所有部分
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 PR 提交指南
|
||||
|
||||
### 提交前检查
|
||||
|
||||
- [ ] 代码成功编译(`go build` 和 `npm run build`)
|
||||
- [ ] 所有测试通过(`go test ./...`)
|
||||
- [ ] 没有 linting 错误(`go fmt`、`go vet`)
|
||||
- [ ] 文档已更新
|
||||
- [ ] 提交遵循 conventional commits 格式
|
||||
- [ ] 分支已基于最新的 `dev` rebase
|
||||
|
||||
### PR 标题格式
|
||||
|
||||
使用 [Conventional Commits](https://www.conventionalcommits.org/) 格式:
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
示例:
|
||||
feat(exchange): add OKX exchange integration
|
||||
fix(trader): resolve position tracking bug
|
||||
docs(readme): update installation instructions
|
||||
perf(ai): optimize prompt generation
|
||||
refactor(core): extract common exchange interface
|
||||
```
|
||||
|
||||
**类型:**
|
||||
- `feat` - 新功能
|
||||
- `fix` - Bug 修复
|
||||
- `docs` - 文档
|
||||
- `style` - 代码样式(格式化,无逻辑变更)
|
||||
- `refactor` - 代码重构
|
||||
- `perf` - 性能改进
|
||||
- `test` - 测试更新
|
||||
- `chore` - 构建/配置更改
|
||||
- `ci` - CI/CD 更改
|
||||
- `security` - 安全改进
|
||||
|
||||
### PR 描述
|
||||
|
||||
使用 [PR 模板](../../../.github/PULL_REQUEST_TEMPLATE.md)并确保:
|
||||
|
||||
1. **清晰描述**更改内容和原因
|
||||
2. **变更类型**已标记
|
||||
3. **相关 issue** 已链接
|
||||
4. **测试步骤**已记录
|
||||
5. UI 更改有**截图**
|
||||
6. **所有复选框**已完成
|
||||
|
||||
### PR 大小
|
||||
|
||||
保持 PR 专注且大小合理:
|
||||
|
||||
- ✅ **小型 PR**(< 300 行):理想,审核快速
|
||||
- ⚠️ **中型 PR**(300-1000 行):可接受,可能需要更长时间
|
||||
- ❌ **大型 PR**(> 1000 行):请拆分为更小的 PR
|
||||
|
||||
---
|
||||
|
||||
## 💻 编码规范
|
||||
|
||||
### Go 代码
|
||||
|
||||
```go
|
||||
// ✅ 好:清晰的命名,正确的错误处理
|
||||
func ConnectToExchange(apiKey, secret string) (*Exchange, error) {
|
||||
if apiKey == "" || secret == "" {
|
||||
return nil, fmt.Errorf("API credentials are required")
|
||||
}
|
||||
|
||||
client, err := createClient(apiKey, secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create client: %w", err)
|
||||
}
|
||||
|
||||
return &Exchange{client: client}, nil
|
||||
}
|
||||
|
||||
// ❌ 差:糟糕的命名,没有错误处理
|
||||
func ce(a, s string) *Exchange {
|
||||
c := createClient(a, s)
|
||||
return &Exchange{client: c}
|
||||
}
|
||||
```
|
||||
|
||||
**最佳实践:**
|
||||
- 使用有意义的变量名
|
||||
- 显式处理所有错误
|
||||
- 为复杂逻辑添加注释
|
||||
- 遵循 Go 习惯用法和约定
|
||||
- 提交前运行 `go fmt`
|
||||
- 使用 `go vet` 和 `golangci-lint`
|
||||
|
||||
### TypeScript/React 代码
|
||||
|
||||
```typescript
|
||||
// ✅ 好:类型安全,清晰的命名
|
||||
interface TraderConfig {
|
||||
id: string;
|
||||
exchange: 'binance' | 'hyperliquid' | 'aster';
|
||||
aiModel: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const TraderCard: React.FC<{ trader: TraderConfig }> = ({ trader }) => {
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
const handleStart = async () => {
|
||||
try {
|
||||
await startTrader(trader.id);
|
||||
setIsRunning(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to start trader:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return <div>...</div>;
|
||||
};
|
||||
|
||||
// ❌ 差:没有类型,不清晰的命名
|
||||
const TC = (props) => {
|
||||
const [r, setR] = useState(false);
|
||||
const h = () => { startTrader(props.t.id); setR(true); };
|
||||
return <div>...</div>;
|
||||
};
|
||||
```
|
||||
|
||||
**最佳实践:**
|
||||
- 使用 TypeScript 严格模式
|
||||
- 为所有数据结构定义接口
|
||||
- 避免使用 `any` 类型
|
||||
- 使用带 hooks 的函数式组件
|
||||
- 遵循 React 最佳实践
|
||||
- 提交前运行 `npm run lint`
|
||||
|
||||
### 文件结构
|
||||
|
||||
```
|
||||
NOFX/
|
||||
├── cmd/ # 主应用程序
|
||||
├── internal/ # 私有代码
|
||||
│ ├── exchange/ # 交易所适配器
|
||||
│ ├── trader/ # 交易逻辑
|
||||
│ ├── ai/ # AI 集成
|
||||
│ └── api/ # API 处理器
|
||||
├── pkg/ # 公共库
|
||||
├── web/ # 前端
|
||||
│ ├── src/
|
||||
│ │ ├── components/
|
||||
│ │ ├── pages/
|
||||
│ │ ├── hooks/
|
||||
│ │ └── utils/
|
||||
│ └── public/
|
||||
└── docs/ # 文档
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 提交信息指南
|
||||
|
||||
### 格式
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
### 示例
|
||||
|
||||
```
|
||||
feat(exchange): add OKX futures API integration
|
||||
|
||||
- Implement order placement and cancellation
|
||||
- Add balance and position retrieval
|
||||
- Support leverage configuration
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
```
|
||||
fix(trader): prevent duplicate position opening
|
||||
|
||||
The trader was opening multiple positions in the same direction
|
||||
for the same symbol. Added check to prevent this behavior.
|
||||
|
||||
Fixes #456
|
||||
```
|
||||
|
||||
```
|
||||
docs: update Docker deployment guide
|
||||
|
||||
- Add troubleshooting section
|
||||
- Update environment variables
|
||||
- Add examples for common scenarios
|
||||
```
|
||||
|
||||
### 规则
|
||||
|
||||
- 使用现在时("add" 而非 "added")
|
||||
- 使用祈使语气("move" 而非 "moves")
|
||||
- 第一行 ≤ 72 字符
|
||||
- 引用 issue 和 PR
|
||||
- 解释"是什么"和"为什么",而非"如何做"
|
||||
|
||||
---
|
||||
|
||||
## 🔍 审核流程
|
||||
|
||||
### 时间线
|
||||
|
||||
- **初次审核:** 2-3 个工作日内
|
||||
- **后续审核:** 1-2 个工作日内
|
||||
- **悬赏 PR:** 1 个工作日内优先审核
|
||||
|
||||
### 审核标准
|
||||
|
||||
审核者将检查:
|
||||
|
||||
1. **功能性**
|
||||
- 是否按预期工作?
|
||||
- 边界情况是否处理?
|
||||
- 现有功能没有退化?
|
||||
|
||||
2. **代码质量**
|
||||
- 遵循编码规范?
|
||||
- 结构良好且可读?
|
||||
- 正确的错误处理?
|
||||
|
||||
3. **测试**
|
||||
- 测试覆盖率足够?
|
||||
- CI 中测试通过?
|
||||
- 手动测试已记录?
|
||||
|
||||
4. **文档**
|
||||
- 需要的地方有代码注释?
|
||||
- README/文档已更新?
|
||||
- API 变更已记录?
|
||||
|
||||
5. **安全性**
|
||||
- 没有硬编码的密钥?
|
||||
- 输入验证?
|
||||
- 没有已知漏洞?
|
||||
|
||||
### 回应反馈
|
||||
|
||||
- 处理所有审核评论
|
||||
- 不清楚时提问
|
||||
- 标记对话为已解决
|
||||
- 更改后重新请求审核
|
||||
|
||||
### 批准和合并
|
||||
|
||||
- 需要维护者 **1 个批准**
|
||||
- 所有 CI 检查必须通过
|
||||
- 没有未解决的对话
|
||||
- 维护者将合并(小型 PR 使用 squash merge,功能使用 merge commit)
|
||||
|
||||
---
|
||||
|
||||
## 💰 悬赏计划
|
||||
|
||||
### 工作方式
|
||||
|
||||
1. 查看[悬赏 issue](https://github.com/tinkle-community/nofx/labels/bounty)
|
||||
2. 评论认领(先到先得)
|
||||
3. 在截止日期前完成工作
|
||||
4. 提交 PR 并填写悬赏认领部分
|
||||
5. 合并后获得报酬
|
||||
|
||||
### 指南
|
||||
|
||||
- 阅读[悬赏指南](../../community/bounty-guide.md)
|
||||
- 满足所有验收标准
|
||||
- 包含演示视频/截图
|
||||
- 遵循所有贡献指南
|
||||
- 私下讨论付款详情
|
||||
|
||||
---
|
||||
|
||||
## ❓ 问题?
|
||||
|
||||
- **一般问题:** 加入我们的 [Telegram 社区](https://t.me/nofx_dev_community)
|
||||
- **技术问题:** 开启[讨论](https://github.com/tinkle-community/nofx/discussions)
|
||||
- **安全问题:** 查看[安全政策](../../../SECURITY.md)
|
||||
- **Bug 报告:** 使用 [Bug 报告模板](../../../.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
|
||||
---
|
||||
|
||||
## 📚 其他资源
|
||||
|
||||
- [项目路线图](../../roadmap/README.zh-CN.md)
|
||||
- [架构文档](../../architecture/README.zh-CN.md)
|
||||
- [API 文档](../../api/README.md)
|
||||
- [部署指南](../../getting-started/docker-deploy.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 🙏 感谢你!
|
||||
|
||||
你的贡献让 NOFX 变得更好。我们感谢你的时间和努力!
|
||||
|
||||
**编码愉快!🚀**
|
||||
@@ -6,10 +6,38 @@
|
||||
[](LICENSE)
|
||||
[](https://amber.ac)
|
||||
|
||||
**语言 / Languages:** [English](README.md) | [中文](README.zh-CN.md) | [Українська](README.uk.md) | [Русский](README.ru.md)
|
||||
**语言 / Languages:** [English](../../../README.md) | [中文](../zh-CN/README.md) | [Українська](../uk/README.md) | [Русский](../ru/README.md)
|
||||
|
||||
**官方推特:** [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
**📚 文档中心:** [文档首页](../../README.md) | [快速开始](../../getting-started/README.zh-CN.md) | [更新日志](../../../CHANGELOG.zh-CN.md) | [社区指南](../../community/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 📑 目录
|
||||
|
||||
- [🚀 通用AI交易操作系统](#-通用ai交易操作系统)
|
||||
- [👥 开发者社区](#-开发者社区)
|
||||
- [🆕 最新更新](#-最新更新)
|
||||
- [📸 系统截图](#-系统截图)
|
||||
- [✨ 当前实现](#-当前实现---加密货币市场)
|
||||
- [🔮 路线图](#-路线图---通用市场扩展)
|
||||
- [🏗️ 技术架构](#️-技术架构)
|
||||
- [💰 注册币安账户](#-注册币安账户省手续费)
|
||||
- [🚀 快速开始](#-快速开始)
|
||||
- [📖 AI决策流程](#-ai决策流程)
|
||||
- [🧠 AI自我学习示例](#-ai自我学习示例)
|
||||
- [📊 Web界面功能](#-web界面功能)
|
||||
- [🎛️ API接口](#️-api接口)
|
||||
- [📝 决策日志格式](#-决策日志格式)
|
||||
- [🔧 风险控制详解](#-风险控制详解)
|
||||
- [⚠️ 重要风险提示](#️-重要风险提示)
|
||||
- [🛠️ 常见问题](#️-常见问题)
|
||||
- [📈 性能优化建议](#-性能优化建议)
|
||||
- [🔄 更新日志](#-更新日志)
|
||||
- [📄 开源协议](#-开源协议)
|
||||
- [🤝 贡献指南](#-贡献指南)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 通用AI交易操作系统
|
||||
@@ -74,7 +102,7 @@ NOFX现已支持**三大交易所**:Binance、Hyperliquid和Aster DEX!
|
||||
|
||||
**快速开始:**
|
||||
1. 获取你的MetaMask私钥(去掉`0x`前缀)
|
||||
2. 在config.json中设置`"exchange": "hyperliquid"`
|
||||
2. ~~在config.json中设置`"exchange": "hyperliquid"`~~ *通过Web界面配置*
|
||||
3. 添加`"hyperliquid_private_key": "your_key"`
|
||||
4. 开始交易!
|
||||
|
||||
@@ -110,11 +138,11 @@ NOFX现已支持**三大交易所**:Binance、Hyperliquid和Aster DEX!
|
||||
## 📸 系统截图
|
||||
|
||||
### 🏆 竞赛模式 - AI实时对战
|
||||

|
||||

|
||||
*多AI排行榜和实时性能对比图表,展示Qwen vs DeepSeek实时交易对战*
|
||||
|
||||
### 📊 交易详情 - 完整交易仪表盘
|
||||

|
||||

|
||||
*专业交易界面,包含权益曲线、实时持仓、AI决策日志,支持展开查看输入提示词和AI思维链推理过程*
|
||||
|
||||
---
|
||||
@@ -169,78 +197,50 @@ NOFX 目前已在**加密货币市场全面运行**,具备以下经过验证
|
||||
|
||||
## 🔮 路线图 - 通用市场扩展
|
||||
|
||||
我们经过验证的加密货币基础设施正在扩展到:
|
||||
NOFX 的使命是成为所有金融市场的**通用 AI 交易操作系统**。
|
||||
|
||||
- **📈 股票市场**:美股、A股、港股
|
||||
- **📊 期货市场**:商品期货、指数期货
|
||||
- **🎯 期权交易**:股票期权、加密期权
|
||||
- **💱 外汇市场**:主要货币对、交叉盘
|
||||
**愿景:** 相同架构。相同智能体框架。所有市场。
|
||||
|
||||
**相同架构。相同智能体框架。所有市场。**
|
||||
**扩展市场:**
|
||||
- 📈 **股票市场**:美股、A股、港股
|
||||
- 📊 **期货市场**:商品期货、指数期货
|
||||
- 🎯 **期权交易**:股票期权、加密期权
|
||||
- 💱 **外汇市场**:主要货币对、交叉盘
|
||||
|
||||
**即将推出的功能:**
|
||||
- 增强AI能力(GPT-4、Claude 3、Gemini Pro、灵活prompt模板)
|
||||
- 新交易所集成(OKX、Bybit、Lighter、EdgeX + CEX/Perp-DEX)
|
||||
- 项目结构重构(高内聚低耦合、SOLID原则)
|
||||
- 安全性增强(API密钥AES-256加密、RBAC、2FA改进)
|
||||
- 用户体验改进(移动端响应式、TradingView图表、告警系统)
|
||||
|
||||
📖 **详细路线图和时间表,请参阅:**
|
||||
- **中文:** [路线图文档](../../roadmap/README.zh-CN.md)
|
||||
- **English:** [Roadmap Documentation](../../roadmap/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 技术架构
|
||||
|
||||
```
|
||||
nofx/
|
||||
├── main.go # 程序入口(多trader管理器)
|
||||
├── config.json # 配置文件(API密钥、多trader配置)
|
||||
│
|
||||
├── api/ # HTTP API服务
|
||||
│ └── server.go # Gin框架,RESTful API
|
||||
│
|
||||
├── trader/ # 交易核心
|
||||
│ ├── auto_trader.go # 自动交易主控(单trader)
|
||||
│ └── binance_futures.go # 币安合约API封装
|
||||
│
|
||||
├── manager/ # 多trader管理
|
||||
│ └── trader_manager.go # 管理多个trader实例
|
||||
│
|
||||
├── mcp/ # Model Context Protocol - AI通信
|
||||
│ └── client.go # AI API客户端(DeepSeek/Qwen集成)
|
||||
│
|
||||
├── decision/ # AI决策引擎
|
||||
│ └── engine.go # 决策逻辑(含历史反馈)
|
||||
│
|
||||
├── market/ # 市场数据获取
|
||||
│ └── data.go # 市场数据与技术指标(K线、RSI、MACD)
|
||||
│
|
||||
├── pool/ # 币种池管理
|
||||
│ └── coin_pool.go # AI500 + OI Top合并池
|
||||
│
|
||||
├── logger/ # 日志系统
|
||||
│ └── decision_logger.go # 决策记录 + 表现分析
|
||||
│
|
||||
├── decision_logs/ # 决策日志存储
|
||||
│ ├── qwen_trader/ # Qwen trader日志
|
||||
│ └── deepseek_trader/ # DeepSeek trader日志
|
||||
│
|
||||
└── web/ # React前端
|
||||
├── src/
|
||||
│ ├── components/ # React组件
|
||||
│ │ ├── EquityChart.tsx # 收益率曲线图
|
||||
│ │ ├── ComparisonChart.tsx # 多AI对比图
|
||||
│ │ └── CompetitionPage.tsx # 竞赛排行榜
|
||||
│ ├── lib/api.ts # API调用封装
|
||||
│ ├── types/index.ts # TypeScript类型
|
||||
│ ├── index.css # Binance风格样式
|
||||
│ └── App.tsx # 主应用
|
||||
└── package.json
|
||||
```
|
||||
NOFX 采用现代化的模块化架构:
|
||||
|
||||
### 核心依赖
|
||||
- **后端:** Go + Gin 框架,SQLite 数据库
|
||||
- **前端:** React 18 + TypeScript + Vite + TailwindCSS
|
||||
- **多交易所支持:** Binance、Hyperliquid、Aster DEX
|
||||
- **AI 集成:** DeepSeek、Qwen 及自定义 OpenAI 兼容 API
|
||||
- **状态管理:** 前端 Zustand,后端数据库驱动
|
||||
- **实时更新:** SWR,5-10 秒轮询间隔
|
||||
|
||||
**后端 (Go)**
|
||||
- `github.com/adshao/go-binance/v2` - 币安API客户端
|
||||
- `github.com/markcheno/go-talib` - 技术指标计算(TA-Lib)
|
||||
- `github.com/gin-gonic/gin` - HTTP API框架
|
||||
**核心特性:**
|
||||
- 🗄️ 数据库驱动的配置(无需编辑 JSON)
|
||||
- 🔐 JWT 认证,支持可选的 2FA
|
||||
- 📊 实时性能跟踪和分析
|
||||
- 🤖 多 AI 竞赛模式,实时对比
|
||||
- 🔌 RESTful API,完整的配置和监控
|
||||
|
||||
**前端 (React + TypeScript)**
|
||||
- `react` + `react-dom` - UI框架
|
||||
- `recharts` - 图表库(收益率曲线、对比图)
|
||||
- `swr` - 数据获取和缓存
|
||||
- `tailwindcss` - CSS框架
|
||||
📖 **详细架构文档,请查看:**
|
||||
- **中文版:** [架构文档](../../architecture/README.zh-CN.md)
|
||||
- **English:** [Architecture Documentation](../../architecture/README.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -261,7 +261,7 @@ nofx/
|
||||
5. **创建API密钥**:
|
||||
- 进入账户 → API管理
|
||||
- 创建新的API密钥,**务必勾选"合约"权限**
|
||||
- 保存API Key和Secret Key(config.json中需要)
|
||||
- 保存API Key和Secret Key(~~config.json中需要~~ *Web界面中需要*)
|
||||
- **重要**:添加IP白名单以确保安全
|
||||
|
||||
### 手续费优惠说明:
|
||||
@@ -283,12 +283,14 @@ Docker会自动处理所有依赖(Go、Node.js、TA-Lib)和环境配置,
|
||||
#### 步骤1:准备配置文件
|
||||
```bash
|
||||
# 复制配置文件模板
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
|
||||
# 编辑并填入你的API密钥
|
||||
nano config.json # 或使用其他编辑器
|
||||
```
|
||||
|
||||
⚠️ **注意**: 基础config.json仍需要一些设置,但~~交易员配置~~现在通过Web界面进行。
|
||||
|
||||
#### 步骤2:一键启动
|
||||
```bash
|
||||
# 方式1:使用便捷脚本(推荐)
|
||||
@@ -397,7 +399,7 @@ cd ..
|
||||
|
||||
**如何获取Qwen API密钥:**
|
||||
|
||||
1. **访问**:[https://dashscope.aliyuncs.com](https://dashscope.aliyuncs.com)
|
||||
1. **访问**:[https://dashscope.console.aliyun.com](https://dashscope.console.aliyun.com)
|
||||
2. **注册**:使用阿里云账户注册
|
||||
3. **开通服务**:激活DashScope服务
|
||||
4. **创建API密钥**:
|
||||
@@ -417,13 +419,15 @@ cd ..
|
||||
|
||||
#### 🌟 新手模式配置(推荐)
|
||||
|
||||
**步骤1**:复制并重命名示例配置文件
|
||||
~~**步骤1**:复制并重命名示例配置文件~~
|
||||
|
||||
```bash
|
||||
cp config.json.example config.json
|
||||
cp config.example.jsonc config.json
|
||||
```
|
||||
|
||||
**步骤2**:编辑`config.json`填入您的API密钥
|
||||
~~**步骤2**:编辑`config.json`填入您的API密钥~~
|
||||
|
||||
*现在通过Web界面配置,无需编辑JSON文件*
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -488,7 +492,7 @@ cp config.json.example config.json
|
||||
3. **去掉`0x`前缀**
|
||||
4. 在[Hyperliquid](https://hyperliquid.xyz)上为钱包充值
|
||||
|
||||
**步骤2**:为Hyperliquid配置`config.json`
|
||||
~~**步骤2**:为Hyperliquid配置`config.json`~~ *通过Web界面配置*
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -543,7 +547,7 @@ cp config.json.example config.json
|
||||
- API钱包地址(Signer)
|
||||
- API钱包私钥(⚠️ 仅显示一次!)
|
||||
|
||||
**步骤2**:为Aster配置`config.json`
|
||||
~~**步骤2**:为Aster配置`config.json`~~ *通过Web界面配置*
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -791,9 +795,9 @@ go build -o nofx
|
||||
|
||||
| 错误信息 | 解决方案 |
|
||||
|---------|---------|
|
||||
| `invalid API key` | 检查config.json中的币安API密钥 |
|
||||
| `invalid API key` | ~~检查config.json中的币安API密钥~~ *检查Web界面中的API密钥* |
|
||||
| `TA-Lib not found` | 运行`brew install ta-lib`(macOS) |
|
||||
| `port 8080 already in use` | 修改config.json中的`api_server_port` |
|
||||
| `port 8080 already in use` | ~~修改config.json中的`api_server_port`~~ *修改.env文件中的`API_PORT`* |
|
||||
| `DeepSeek API error` | 验证DeepSeek API密钥和余额 |
|
||||
|
||||
**✅ 后端运行正常的标志:**
|
||||
@@ -867,7 +871,7 @@ VITE v5.x.x ready in xxx ms
|
||||
|
||||
```bash
|
||||
# 在新终端窗口中
|
||||
curl http://localhost:8080/health
|
||||
curl http://localhost:8080/api/health
|
||||
```
|
||||
|
||||
应返回:`{"status":"ok"}`
|
||||
@@ -895,79 +899,73 @@ curl http://localhost:8080/health
|
||||
|
||||
每个决策周期(默认3分钟),系统按以下流程运行:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 1. 📊 分析历史表现(最近20个周期) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ✓ 计算整体胜率、平均盈利、盈亏比 │
|
||||
│ ✓ 统计各币种表现(胜率、平均USDT盈亏) │
|
||||
│ ✓ 识别最佳/最差币种 │
|
||||
│ ✓ 列出最近5笔交易详情(含准确盈亏金额) │
|
||||
│ ✓ 计算夏普比率衡量风险调整后收益 │
|
||||
│ 📌 新增 (v2.0.2): 考虑杠杆的准确USDT盈亏计算 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 2. 💰 获取账户状态 │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • 账户净值、可用余额、未实现盈亏 │
|
||||
│ • 持仓数量、总盈亏(已实现+未实现) │
|
||||
│ • 保证金使用率(current/maximum) │
|
||||
│ • 风险评估指标 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 3. 🔍 分析现有持仓(如果有) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • 获取每个持仓的市场数据(3分钟+4小时K线) │
|
||||
│ • 计算技术指标(RSI、MACD、EMA) │
|
||||
│ • 显示持仓时长(例如"持仓时长2小时15分钟") │
|
||||
│ • AI判断是否需要平仓(止盈、止损或调整) │
|
||||
│ 📌 新增 (v2.0.2): 追踪持仓时长帮助AI决策 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 4. 🎯 评估新机会(候选币种池) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • 获取AI500高评分币种(前20个) │
|
||||
│ • 获取OI Top持仓增长币种(前20个) │
|
||||
│ • 合并去重,过滤低流动性币种(持仓量<15M USD) │
|
||||
│ • 批量获取市场数据和技术指标 │
|
||||
│ • 为每个候选币种准备完整的原始数据序列 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 5. 🧠 AI综合决策 │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • 查看历史反馈(胜率、盈亏比、最佳/最差币种) │
|
||||
│ • 接收所有原始序列数据(K线、指标、持仓量) │
|
||||
│ • Chain of Thought 思维链分析 │
|
||||
│ • 输出决策:平仓/开仓/持有/观望 │
|
||||
│ • 包含杠杆、仓位、止损、止盈参数 │
|
||||
│ 📌 新增 (v2.0.2): AI可自由分析原始序列,不受预定义指标限制 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 6. ⚡ 执行交易 │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • 优先级排序:先平仓,再开仓 │
|
||||
│ • 精度自动适配(LOT_SIZE规则) │
|
||||
│ • 防止仓位叠加(同币种同方向拒绝开仓) │
|
||||
│ • 平仓后自动取消所有挂单 │
|
||||
│ • 记录开仓时间用于持仓时长追踪 │
|
||||
│ 📌 新增 (v2.0.2): 追踪持仓开仓时间 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 7. 📝 记录日志 │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ • 保存完整决策记录到 decision_logs/ │
|
||||
│ • 包含思维链、决策JSON、账户快照、执行结果 │
|
||||
│ • 存储完整持仓数据(数量、杠杆、开/平仓时间) │
|
||||
│ • 使用symbol_side键值防止多空冲突 │
|
||||
│ 📌 新增 (v2.0.2): 防止多空持仓冲突,考虑数量+杠杆 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
### 步骤1: 📊 分析历史表现(最近20个周期)
|
||||
- ✓ 计算整体胜率、平均盈利、盈亏比
|
||||
- ✓ 统计各币种表现(胜率、平均USDT盈亏)
|
||||
- ✓ 识别最佳/最差币种
|
||||
- ✓ 列出最近5笔交易详情(含准确盈亏金额)
|
||||
- ✓ 计算夏普比率衡量风险调整后收益
|
||||
- 📌 **新增 (v2.0.2)**: 考虑杠杆的准确USDT盈亏计算
|
||||
|
||||
**↓**
|
||||
|
||||
### 步骤2: 💰 获取账户状态
|
||||
- 账户净值、可用余额、未实现盈亏
|
||||
- 持仓数量、总盈亏(已实现+未实现)
|
||||
- 保证金使用率(current/maximum)
|
||||
- 风险评估指标
|
||||
|
||||
**↓**
|
||||
|
||||
### 步骤3: 🔍 分析现有持仓(如果有)
|
||||
- 获取每个持仓的市场数据(3分钟+4小时K线)
|
||||
- 计算技术指标(RSI、MACD、EMA)
|
||||
- 显示持仓时长(例如"持仓时长2小时15分钟")
|
||||
- AI判断是否需要平仓(止盈、止损或调整)
|
||||
- 📌 **新增 (v2.0.2)**: 追踪持仓时长帮助AI决策
|
||||
|
||||
**↓**
|
||||
|
||||
### 步骤4: 🎯 评估新机会(候选币种池)
|
||||
- 获取币种池(2种模式):
|
||||
- 🌟 **默认模式**: BTC、ETH、SOL、BNB、XRP等
|
||||
- ⚙️ **高级模式**: AI500(前20) + OI Top(前20)
|
||||
- 合并去重,过滤低流动性币种(持仓量<15M USD)
|
||||
- 批量获取市场数据和技术指标
|
||||
- 为每个候选币种准备完整的原始数据序列
|
||||
|
||||
**↓**
|
||||
|
||||
### 步骤5: 🧠 AI综合决策
|
||||
- 查看历史反馈(胜率、盈亏比、最佳/最差币种)
|
||||
- 接收所有原始序列数据(K线、指标、持仓量)
|
||||
- Chain of Thought 思维链分析
|
||||
- 输出决策:平仓/开仓/持有/观望
|
||||
- 包含杠杆、仓位、止损、止盈参数
|
||||
- 📌 **新增 (v2.0.2)**: AI可自由分析原始序列,不受预定义指标限制
|
||||
|
||||
**↓**
|
||||
|
||||
### 步骤6: ⚡ 执行交易
|
||||
- 优先级排序:先平仓,再开仓
|
||||
- 精度自动适配(LOT_SIZE规则)
|
||||
- 防止仓位叠加(同币种同方向拒绝开仓)
|
||||
- 平仓后自动取消所有挂单
|
||||
- 记录开仓时间用于持仓时长追踪
|
||||
- 📌 追踪持仓开仓时间
|
||||
|
||||
**↓**
|
||||
|
||||
### 步骤7: 📝 记录日志
|
||||
- 保存完整决策记录到 `decision_logs/`
|
||||
- 包含思维链、决策JSON、账户快照、执行结果
|
||||
- 存储完整持仓数据(数量、杠杆、开/平仓时间)
|
||||
- 使用 `symbol_side` 键值防止多空冲突
|
||||
- 📌 **新增 (v2.0.2)**: 防止多空持仓冲突,考虑数量+杠杆
|
||||
|
||||
**↓**
|
||||
|
||||
**🔄 (每3-5分钟重复一次)**
|
||||
|
||||
### v2.0.2的核心改进
|
||||
|
||||
@@ -1074,7 +1072,7 @@ GET /api/statistics?trader_id=xxx # 统计信息
|
||||
### 系统接口
|
||||
|
||||
```bash
|
||||
GET /health # 健康检查
|
||||
GET /api/health # 健康检查
|
||||
GET /api/config # 系统配置
|
||||
```
|
||||
|
||||
@@ -1230,7 +1228,7 @@ sudo apt-get install libta-lib0-dev
|
||||
**解决**:
|
||||
- 币种池API是可选的
|
||||
- 如果API失败,系统会使用默认主流币种(BTC、ETH等)
|
||||
- 检查config.json中的API URL和auth参数
|
||||
- ~~检查config.json中的API URL和auth参数~~ *检查Web界面中的配置*
|
||||
|
||||
---
|
||||
|
||||
@@ -1246,77 +1244,19 @@ sudo apt-get install libta-lib0-dev
|
||||
|
||||
## 🔄 更新日志
|
||||
|
||||
### v2.0.2 (2025-10-29)
|
||||
📖 **详细的版本历史和更新,请查看:**
|
||||
|
||||
**关键Bug修复 - 交易历史记录与性能分析:**
|
||||
- **中文版:** [CHANGELOG.zh-CN.md](../../../CHANGELOG.zh-CN.md)
|
||||
- **English:** [CHANGELOG.md](../../../CHANGELOG.md)
|
||||
|
||||
本版本修复了历史交易记录和性能分析系统中的**严重计算错误**,这些错误严重影响了盈利统计的准确性。
|
||||
**最新版本:** v3.0.0 (2025-10-30) - 重大架构变革
|
||||
|
||||
**1. 盈亏计算 - 重大错误修复** (logger/decision_logger.go)
|
||||
- **问题**:之前只用百分比计算盈亏,完全忽略了仓位大小和杠杆倍数
|
||||
- 示例:100 USDT仓位赚5%和1000 USDT仓位赚5%都显示`5.0`作为盈利
|
||||
- 这导致性能分析完全不准确
|
||||
- **解决方案**:现在计算实际USDT盈亏金额
|
||||
```
|
||||
盈亏(USDT) = 仓位价值 × 价格变化% × 杠杆倍数
|
||||
示例: 1000 USDT × 5% × 20倍 = 1000 USDT实际盈利
|
||||
```
|
||||
- **影响**:胜率、盈亏比和夏普比率现在基于准确的USDT金额计算
|
||||
|
||||
**2. 持仓追踪 - 缺失关键数据**
|
||||
- **问题**:开仓记录只存储了价格和时间,缺少数量和杠杆
|
||||
- **解决方案**:现在存储完整交易数据:
|
||||
- `quantity`: 持仓数量(币数)
|
||||
- `leverage`: 杠杆倍数(如20倍)
|
||||
- 这些是准确计算盈亏的必要数据
|
||||
|
||||
**3. 持仓键值逻辑 - 多空冲突**
|
||||
- **问题**:使用`symbol`作为持仓键值,导致同时持有多空仓时数据冲突
|
||||
- 示例:BTCUSDT多头和BTCUSDT空头会互相覆盖
|
||||
- **解决方案**:改为`symbol_side`格式(如`BTCUSDT_long`、`BTCUSDT_short`)
|
||||
- 现在可以正确区分多空持仓
|
||||
|
||||
**4. 夏普比率计算 - 代码优化**
|
||||
- **问题**:使用自定义的牛顿迭代法计算平方根
|
||||
- **解决方案**:替换为标准库`math.Sqrt`
|
||||
- 更可靠、易维护且高效
|
||||
|
||||
**为什么这次更新很重要:**
|
||||
- ✅ 历史交易统计现在显示**真实的USDT盈亏**而不是无意义的百分比
|
||||
- ✅ 不同杠杆倍数的交易对比现在准确了
|
||||
- ✅ AI自我学习机制接收到正确的历史反馈
|
||||
- ✅ 盈亏比和夏普比率计算现在有意义了
|
||||
- ✅ 多持仓追踪(同时持有多空)现在正常工作
|
||||
|
||||
**建议**:如果您在此更新前运行过系统,您的历史统计数据是不准确的。更新到v2.0.2后,新的交易将被正确计算。
|
||||
|
||||
### v2.0.1 (2025-10-29)
|
||||
|
||||
**Bug修复:**
|
||||
- ✅ 修复ComparisonChart数据处理逻辑 - 从cycle_number分组改为timestamp分组
|
||||
- ✅ 解决后端重启导致cycle_number重置时图表冻结的问题
|
||||
- ✅ 改进图表数据显示 - 现在按时间顺序显示所有历史数据点
|
||||
- ✅ 增强调试日志,便于问题排查
|
||||
|
||||
### v2.0.0 (2025-10-28)
|
||||
|
||||
**重大更新:**
|
||||
- ✅ AI自我学习机制(历史反馈、表现分析)
|
||||
- ✅ 多Trader竞赛模式(Qwen vs DeepSeek)
|
||||
- ✅ Binance风格UI(完整模仿币安界面)
|
||||
- ✅ 性能对比图表(收益率实时对比)
|
||||
- ✅ 风险控制优化(单币种仓位上限调整)
|
||||
|
||||
**Bug修复:**
|
||||
- 修复初始余额硬编码问题
|
||||
- 修复多trader数据同步问题
|
||||
- 优化图表数据对齐(使用cycle_number)
|
||||
|
||||
### v1.0.0 (2025-10-27)
|
||||
- 初始版本发布
|
||||
- 基础AI交易功能
|
||||
- 决策日志系统
|
||||
- 简单Web界面
|
||||
**近期亮点:**
|
||||
- 🚀 完整系统重新设计,基于Web的配置平台
|
||||
- 🗄️ 数据库驱动架构(SQLite)
|
||||
- 🎨 无需编辑JSON - 全部通过Web界面配置
|
||||
- 🔧 AI模型与交易所任意组合
|
||||
- 📊 增强的API层,提供全面的端点
|
||||
|
||||
---
|
||||
|
||||
@@ -1352,7 +1292,7 @@ MIT License - 详见 [LICENSE](LICENSE) 文件
|
||||
|
||||
- [Binance API](https://binance-docs.github.io/apidocs/futures/cn/) - 币安合约API
|
||||
- [DeepSeek](https://platform.deepseek.com/) - DeepSeek AI API
|
||||
- [Qwen](https://dashscope.aliyuncs.com/) - 阿里云通义千问
|
||||
- [Qwen](https://dashscope.console.aliyun.com/) - 阿里云通义千问
|
||||
- [TA-Lib](https://ta-lib.org/) - 技术指标库
|
||||
- [Recharts](https://recharts.org/) - React图表库
|
||||
|
||||
398
docs/maintainers/PROJECT_MANAGEMENT.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# 📊 Project Management Guide
|
||||
|
||||
**Language:** [English](PROJECT_MANAGEMENT.md) | [中文](PROJECT_MANAGEMENT.zh-CN.md)
|
||||
|
||||
This guide explains how we manage the NOFX project, track progress, and prioritize work.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Project Structure
|
||||
|
||||
### GitHub Projects
|
||||
|
||||
We use **GitHub Projects (Beta)** with these boards:
|
||||
|
||||
#### 1. **NOFX Development Board**
|
||||
|
||||
**Columns:**
|
||||
```
|
||||
Backlog → Triaged → In Progress → In Review → Done
|
||||
```
|
||||
|
||||
**Views:**
|
||||
- 📋 **All Issues** - Kanban view of all work items
|
||||
- 🏃 **Sprint** - Current sprint items (2-week sprints)
|
||||
- 🗺️ **Roadmap** - Timeline view by roadmap phase
|
||||
- 🏷️ **By Area** - Grouped by area labels
|
||||
- 🔥 **Priority** - Sorted by priority (critical/high/medium/low)
|
||||
- 👥 **By Assignee** - Grouped by assigned maintainer
|
||||
|
||||
#### 2. **Bounty Program Board**
|
||||
|
||||
**Columns:**
|
||||
```
|
||||
Available → Claimed → In Progress → Under Review → Paid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Sprint Planning (Bi-weekly)
|
||||
|
||||
### Sprint Schedule
|
||||
|
||||
**Sprint Duration:** 2 weeks
|
||||
**Sprint Planning:** Every other Monday
|
||||
**Sprint Review:** Every other Friday
|
||||
|
||||
### Planning Process
|
||||
|
||||
**Monday - Sprint Planning (1 hour):**
|
||||
|
||||
1. **Review previous sprint** (15 min)
|
||||
- What was completed?
|
||||
- What was not completed and why?
|
||||
- Metrics review
|
||||
|
||||
2. **Prioritize backlog** (20 min)
|
||||
- Review new issues/PRs
|
||||
- Update priorities based on roadmap
|
||||
- Assign labels
|
||||
|
||||
3. **Plan next sprint** (25 min)
|
||||
- Select items for next sprint
|
||||
- Assign to maintainers
|
||||
- Set clear acceptance criteria
|
||||
- Estimate effort (S/M/L)
|
||||
|
||||
**Friday - Sprint Review (30 min):**
|
||||
|
||||
1. **Demo completed work** (15 min)
|
||||
- Show merged PRs
|
||||
- Demonstrate new features
|
||||
|
||||
2. **Retrospective** (15 min)
|
||||
- What went well?
|
||||
- What can improve?
|
||||
- Action items for next sprint
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Issue Triage Process
|
||||
|
||||
### Daily Triage (Mon-Fri, 15 min)
|
||||
|
||||
Review new issues and PRs:
|
||||
|
||||
1. **Verify completeness**
|
||||
- Template filled properly?
|
||||
- Reproduction steps clear (for bugs)?
|
||||
- Use case explained (for features)?
|
||||
|
||||
2. **Apply labels**
|
||||
```yaml
|
||||
Priority:
|
||||
- priority: critical # Security, data loss, production down
|
||||
- priority: high # Major bugs, high-value features
|
||||
- priority: medium # Regular bugs, standard features
|
||||
- priority: low # Nice-to-have, minor improvements
|
||||
|
||||
Type:
|
||||
- type: bug
|
||||
- type: feature
|
||||
- type: enhancement
|
||||
- type: documentation
|
||||
- type: security
|
||||
|
||||
Area:
|
||||
- area: exchange
|
||||
- area: ai
|
||||
- area: frontend
|
||||
- area: backend
|
||||
- area: security
|
||||
- area: ui/ux
|
||||
|
||||
Roadmap:
|
||||
- roadmap: phase-1 # Core Infrastructure
|
||||
- roadmap: phase-2 # Testing & Stability
|
||||
- roadmap: phase-3 # Universal Markets
|
||||
```
|
||||
|
||||
3. **Assign or tag for discussion**
|
||||
- Can handle immediately? Assign to maintainer
|
||||
- Needs discussion? Tag for next planning session
|
||||
- Needs more info? Request from author
|
||||
|
||||
4. **Close if needed**
|
||||
- Duplicate? Close with link to original
|
||||
- Invalid? Close with explanation
|
||||
- Out of scope? Close politely with reasoning
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Priority Decision Matrix
|
||||
|
||||
Use this matrix to decide priority:
|
||||
|
||||
| Impact / Urgency | High Urgency | Medium Urgency | Low Urgency |
|
||||
|------------------|--------------|----------------|-------------|
|
||||
| **High Impact** | 🔴 Critical | 🔴 Critical | 🟡 High |
|
||||
| **Medium Impact** | 🔴 Critical | 🟡 High | 🟢 Medium |
|
||||
| **Low Impact** | 🟡 High | 🟢 Medium | ⚪ Low |
|
||||
|
||||
**Impact:**
|
||||
- High: Affects core functionality, security, or many users
|
||||
- Medium: Affects specific features or moderate users
|
||||
- Low: Nice-to-have, minor improvements
|
||||
|
||||
**Urgency:**
|
||||
- High: Needs immediate attention
|
||||
- Medium: Should be addressed soon
|
||||
- Low: Can wait for natural inclusion
|
||||
|
||||
---
|
||||
|
||||
## 📊 Roadmap Alignment
|
||||
|
||||
All work should align with our [roadmap](../roadmap/README.md):
|
||||
|
||||
### Phase 1: Core Infrastructure (Current Focus)
|
||||
|
||||
**Must Accept:**
|
||||
- Security enhancements
|
||||
- AI model integrations
|
||||
- Exchange integrations (OKX, Bybit, Lighter, EdgeX)
|
||||
- Project structure refactoring
|
||||
- UI/UX improvements
|
||||
|
||||
**Can Accept:**
|
||||
- Related bug fixes
|
||||
- Documentation improvements
|
||||
- Performance optimizations
|
||||
|
||||
**Should Defer:**
|
||||
- Universal market expansion (stocks, futures)
|
||||
- Advanced AI features (RL, multi-agent)
|
||||
- Enterprise features
|
||||
|
||||
### Phase 2-5: Future Work
|
||||
|
||||
Mark with appropriate `roadmap: phase-X` label and add to backlog.
|
||||
|
||||
---
|
||||
|
||||
## 🎫 Issue Templates
|
||||
|
||||
We have these issue templates:
|
||||
|
||||
### 1. Bug Report
|
||||
- Use for bugs and errors
|
||||
- Must include reproduction steps
|
||||
- Label: `type: bug`
|
||||
|
||||
### 2. Feature Request
|
||||
- Use for new features
|
||||
- Must include use case and benefits
|
||||
- Label: `type: feature`
|
||||
|
||||
### 3. Bounty Claim
|
||||
- Use when claiming a bounty
|
||||
- Must reference bounty issue
|
||||
- Label: `bounty: claimed`
|
||||
|
||||
### 4. Security Vulnerability
|
||||
- Use for security issues (private)
|
||||
- Follow responsible disclosure
|
||||
- Label: `type: security`
|
||||
|
||||
**Missing a template?**
|
||||
- Use blank issue
|
||||
- Maintainers will convert to appropriate template
|
||||
|
||||
---
|
||||
|
||||
## 📈 Metrics We Track
|
||||
|
||||
### Weekly Metrics
|
||||
|
||||
- **PR Metrics:**
|
||||
- Number of PRs opened
|
||||
- Number of PRs merged
|
||||
- Average time to first review
|
||||
- Average time to merge
|
||||
|
||||
- **Issue Metrics:**
|
||||
- Number of issues opened
|
||||
- Number of issues closed
|
||||
- Issue backlog size
|
||||
- Issues by priority/type/area
|
||||
|
||||
- **Community Metrics:**
|
||||
- New contributors
|
||||
- Active contributors
|
||||
- Community engagement (comments, reactions)
|
||||
|
||||
### Monthly Metrics
|
||||
|
||||
- **Roadmap Progress:**
|
||||
- % completion per phase
|
||||
- Items completed vs planned
|
||||
- Blockers and risks
|
||||
|
||||
- **Code Quality:**
|
||||
- Test coverage
|
||||
- Code review comments per PR
|
||||
- Bug fix vs feature ratio
|
||||
|
||||
- **Bounty Program:**
|
||||
- Bounties created
|
||||
- Bounties claimed
|
||||
- Bounties paid
|
||||
- Average completion time
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Automation
|
||||
|
||||
We use GitHub Actions for automation:
|
||||
|
||||
### PR Automation
|
||||
|
||||
- **Automatic labeling** based on files changed
|
||||
- **PR size labeling** (small/medium/large)
|
||||
- **CI checks** (tests, linting, build)
|
||||
- **Security scans** (Trivy, Gitleaks)
|
||||
- **Conventional commit validation**
|
||||
|
||||
### Issue Automation
|
||||
|
||||
- **Stale issue detection** (closes after 30 days inactive)
|
||||
- **Automatic bounty labeling** when "bounty" keyword used
|
||||
- **Duplicate detection** using issue similarity
|
||||
|
||||
### Release Automation
|
||||
|
||||
- **Changelog generation** from conventional commits
|
||||
- **Version bumping** based on commit types
|
||||
- **Release notes** auto-generated
|
||||
- **Deployment** to staging/production
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Regular Tasks
|
||||
|
||||
### Daily
|
||||
- ✅ Triage new issues/PRs
|
||||
- ✅ Review urgent PRs
|
||||
- ✅ Respond to community questions
|
||||
|
||||
### Weekly
|
||||
- ✅ Sprint planning (Monday)
|
||||
- ✅ Sprint review (Friday)
|
||||
- ✅ Review metrics dashboard
|
||||
- ✅ Update project boards
|
||||
|
||||
### Monthly
|
||||
- ✅ Roadmap progress review
|
||||
- ✅ Community update post
|
||||
- ✅ Bounty program review
|
||||
- ✅ Dependency updates
|
||||
- ✅ Security audit
|
||||
|
||||
### Quarterly
|
||||
- ✅ Roadmap update
|
||||
- ✅ Major release planning
|
||||
- ✅ Contributor recognition
|
||||
- ✅ Documentation audit
|
||||
|
||||
---
|
||||
|
||||
## 📞 Communication Channels
|
||||
|
||||
### Internal (Maintainers)
|
||||
|
||||
- **GitHub Discussions:** Architecture decisions, RFC
|
||||
- **Private channel:** Sensitive discussions, bounty payments
|
||||
- **Weekly sync:** Sprint planning and review
|
||||
|
||||
### External (Community)
|
||||
|
||||
- **Telegram:** [@nofx_dev_community](https://t.me/nofx_dev_community)
|
||||
- **GitHub Issues:** Bug reports, feature requests
|
||||
- **GitHub Discussions:** General questions, ideas
|
||||
- **Twitter:** [@nofx_ai](https://x.com/nofx_ai) - Announcements
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Onboarding New Maintainers
|
||||
|
||||
### Checklist for New Maintainers
|
||||
|
||||
- [ ] Add to GitHub organization
|
||||
- [ ] Grant write access to repository
|
||||
- [ ] Add to private maintainer channel
|
||||
- [ ] Introduce to the team
|
||||
- [ ] Read all docs in `/docs/maintainers/`
|
||||
- [ ] Shadow experienced maintainer for 1 sprint
|
||||
- [ ] First solo PR review (with backup reviewer)
|
||||
- [ ] First solo issue triage
|
||||
- [ ] First sprint planning participation
|
||||
|
||||
### Expectations
|
||||
|
||||
**Time Commitment:**
|
||||
- ~5-10 hours per week
|
||||
- Participate in sprint planning/review
|
||||
- Respond to assigned issues/PRs within SLA
|
||||
|
||||
**Responsibilities:**
|
||||
- Code review
|
||||
- Issue triage
|
||||
- Community support
|
||||
- Documentation maintenance
|
||||
|
||||
---
|
||||
|
||||
## 🏆 Contributor Recognition
|
||||
|
||||
### Monthly Recognition
|
||||
|
||||
**Spotlight in Community Update:**
|
||||
- Top contributor
|
||||
- Best PR of the month
|
||||
- Most helpful community member
|
||||
|
||||
### Quarterly Recognition
|
||||
|
||||
**Contributor Tier System:**
|
||||
- 🥇 **Core Contributor** - 20+ merged PRs
|
||||
- 🥈 **Active Contributor** - 10+ merged PRs
|
||||
- 🥉 **Contributor** - 5+ merged PRs
|
||||
- ⭐ **First Timer** - 1+ merged PR
|
||||
|
||||
**Benefits:**
|
||||
- Recognition in README
|
||||
- Invitation to private Discord
|
||||
- Early access to features
|
||||
- Swag (for Core Contributors)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
### Internal Docs
|
||||
- [PR Review Guide](PR_REVIEW_GUIDE.md)
|
||||
- [Security Policy](../../SECURITY.md)
|
||||
- [Code of Conduct](../../CODE_OF_CONDUCT.md)
|
||||
|
||||
### External Resources
|
||||
- [GitHub Project Management](https://docs.github.com/en/issues/planning-and-tracking-with-projects)
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
- [Semantic Versioning](https://semver.org/)
|
||||
|
||||
---
|
||||
|
||||
## 🤔 Questions?
|
||||
|
||||
Reach out in the maintainer channel or open a discussion.
|
||||
|
||||
**Let's build something amazing together! 🚀**
|
||||
398
docs/maintainers/PROJECT_MANAGEMENT.zh-CN.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# 📊 项目管理指南
|
||||
|
||||
**语言:** [English](PROJECT_MANAGEMENT.md) | [中文](PROJECT_MANAGEMENT.zh-CN.md)
|
||||
|
||||
本指南解释了我们如何管理 NOFX 项目、跟踪进度和优先级排序。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 项目结构
|
||||
|
||||
### GitHub Projects
|
||||
|
||||
我们使用 **GitHub Projects (Beta)** 和以下看板:
|
||||
|
||||
#### 1. **NOFX 开发看板**
|
||||
|
||||
**列:**
|
||||
```
|
||||
Backlog → Triaged → In Progress → In Review → Done
|
||||
```
|
||||
|
||||
**视图:**
|
||||
- 📋 **所有 Issue** - 所有工作项的看板视图
|
||||
- 🏃 **Sprint** - 当前 Sprint 项(2 周 Sprint)
|
||||
- 🗺️ **路线图** - 按路线图阶段的时间轴视图
|
||||
- 🏷️ **按区域** - 按区域标签分组
|
||||
- 🔥 **优先级** - 按优先级排序(critical/high/medium/low)
|
||||
- 👥 **按分配人** - 按分配的维护者分组
|
||||
|
||||
#### 2. **悬赏计划看板**
|
||||
|
||||
**列:**
|
||||
```
|
||||
Available → Claimed → In Progress → Under Review → Paid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Sprint 计划(双周)
|
||||
|
||||
### Sprint 时间表
|
||||
|
||||
**Sprint 周期:** 2 周
|
||||
**Sprint 计划:** 每隔一周的星期一
|
||||
**Sprint 回顾:** 每隔一周的星期五
|
||||
|
||||
### 计划流程
|
||||
|
||||
**星期一 - Sprint 计划(1小时):**
|
||||
|
||||
1. **回顾上一个 Sprint**(15分钟)
|
||||
- 完成了什么?
|
||||
- 什么没有完成?为什么?
|
||||
- 指标回顾
|
||||
|
||||
2. **优先级排序 Backlog**(20分钟)
|
||||
- 审查新的 issue/PR
|
||||
- 基于路线图更新优先级
|
||||
- 分配标签
|
||||
|
||||
3. **计划下一个 Sprint**(25分钟)
|
||||
- 选择下一个 Sprint 的项目
|
||||
- 分配给维护者
|
||||
- 设定清晰的验收标准
|
||||
- 估算工作量(S/M/L)
|
||||
|
||||
**星期五 - Sprint 回顾(30分钟):**
|
||||
|
||||
1. **演示已完成的工作**(15分钟)
|
||||
- 展示已合并的 PR
|
||||
- 演示新功能
|
||||
|
||||
2. **复盘**(15分钟)
|
||||
- 什么做得好?
|
||||
- 什么可以改进?
|
||||
- 下一个 Sprint 的行动项
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Issue 分类流程
|
||||
|
||||
### 每日分类(周一至周五,15分钟)
|
||||
|
||||
审查新的 issue 和 PR:
|
||||
|
||||
1. **验证完整性**
|
||||
- 模板是否正确填写?
|
||||
- 重现步骤清晰吗(对于 bug)?
|
||||
- 使用场景解释清楚吗(对于功能)?
|
||||
|
||||
2. **应用标签**
|
||||
```yaml
|
||||
优先级:
|
||||
- priority: critical # 安全问题、数据丢失、生产环境宕机
|
||||
- priority: high # 主要 bug、高价值功能
|
||||
- priority: medium # 常规 bug、标准功能
|
||||
- priority: low # 可选功能、次要改进
|
||||
|
||||
类型:
|
||||
- type: bug
|
||||
- type: feature
|
||||
- type: enhancement
|
||||
- type: documentation
|
||||
- type: security
|
||||
|
||||
区域:
|
||||
- area: exchange
|
||||
- area: ai
|
||||
- area: frontend
|
||||
- area: backend
|
||||
- area: security
|
||||
- area: ui/ux
|
||||
|
||||
路线图:
|
||||
- roadmap: phase-1 # 核心基础设施
|
||||
- roadmap: phase-2 # 测试与稳定性
|
||||
- roadmap: phase-3 # 通用市场
|
||||
```
|
||||
|
||||
3. **分配或标记讨论**
|
||||
- 可以立即处理?分配给维护者
|
||||
- 需要讨论?标记在下次计划会议
|
||||
- 需要更多信息?从作者处请求
|
||||
|
||||
4. **必要时关闭**
|
||||
- 重复?关闭并链接到原始 issue
|
||||
- 无效?关闭并说明原因
|
||||
- 超出范围?礼貌关闭并说明理由
|
||||
|
||||
---
|
||||
|
||||
## 🎯 优先级决策矩阵
|
||||
|
||||
使用此矩阵决定优先级:
|
||||
|
||||
| 影响/紧急程度 | 高紧急 | 中等紧急 | 低紧急 |
|
||||
|------------------|--------------|----------------|-------------|
|
||||
| **高影响** | 🔴 Critical | 🔴 Critical | 🟡 High |
|
||||
| **中等影响** | 🔴 Critical | 🟡 High | 🟢 Medium |
|
||||
| **低影响** | 🟡 High | 🟢 Medium | ⚪ Low |
|
||||
|
||||
**影响:**
|
||||
- 高:影响核心功能、安全性或许多用户
|
||||
- 中:影响特定功能或中等数量用户
|
||||
- 低:可选功能、次要改进
|
||||
|
||||
**紧急程度:**
|
||||
- 高:需要立即关注
|
||||
- 中:应该尽快处理
|
||||
- 低:可以等待自然包含
|
||||
|
||||
---
|
||||
|
||||
## 📊 路线图对齐
|
||||
|
||||
所有工作应与我们的[路线图](../roadmap/README.zh-CN.md)对齐:
|
||||
|
||||
### Phase 1:核心基础设施(当前重点)
|
||||
|
||||
**必须接受:**
|
||||
- 安全增强
|
||||
- AI 模型集成
|
||||
- 交易所集成(OKX、Bybit、Lighter、EdgeX)
|
||||
- 项目结构重构
|
||||
- UI/UX 改进
|
||||
|
||||
**可以接受:**
|
||||
- 相关 bug 修复
|
||||
- 文档改进
|
||||
- 性能优化
|
||||
|
||||
**应该推迟:**
|
||||
- 通用市场扩展(股票、期货)
|
||||
- 高级 AI 功能(RL、多智能体)
|
||||
- 企业功能
|
||||
|
||||
### Phase 2-5:未来工作
|
||||
|
||||
使用适当的 `roadmap: phase-X` 标签标记并添加到 backlog。
|
||||
|
||||
---
|
||||
|
||||
## 🎫 Issue 模板
|
||||
|
||||
我们有这些 issue 模板:
|
||||
|
||||
### 1. Bug 报告
|
||||
- 用于 bug 和错误
|
||||
- 必须包含重现步骤
|
||||
- 标签:`type: bug`
|
||||
|
||||
### 2. 功能请求
|
||||
- 用于新功能
|
||||
- 必须包含使用场景和好处
|
||||
- 标签:`type: feature`
|
||||
|
||||
### 3. 悬赏认领
|
||||
- 认领悬赏时使用
|
||||
- 必须引用悬赏 issue
|
||||
- 标签:`bounty: claimed`
|
||||
|
||||
### 4. 安全漏洞
|
||||
- 用于安全问题(私密)
|
||||
- 遵循负责任的披露
|
||||
- 标签:`type: security`
|
||||
|
||||
**缺少模板?**
|
||||
- 使用空白 issue
|
||||
- 维护者将转换为适当的模板
|
||||
|
||||
---
|
||||
|
||||
## 📈 我们跟踪的指标
|
||||
|
||||
### 每周指标
|
||||
|
||||
- **PR 指标:**
|
||||
- 打开的 PR 数量
|
||||
- 合并的 PR 数量
|
||||
- 平均首次审核时间
|
||||
- 平均合并时间
|
||||
|
||||
- **Issue 指标:**
|
||||
- 打开的 issue 数量
|
||||
- 关闭的 issue 数量
|
||||
- Issue backlog 大小
|
||||
- 按优先级/类型/区域分类的 issue
|
||||
|
||||
- **社区指标:**
|
||||
- 新贡献者
|
||||
- 活跃贡献者
|
||||
- 社区参与度(评论、反应)
|
||||
|
||||
### 每月指标
|
||||
|
||||
- **路线图进度:**
|
||||
- 每个阶段的完成百分比
|
||||
- 已完成 vs 计划项目
|
||||
- 阻塞因素和风险
|
||||
|
||||
- **代码质量:**
|
||||
- 测试覆盖率
|
||||
- 每个 PR 的代码审核评论数
|
||||
- Bug 修复 vs 功能比率
|
||||
|
||||
- **悬赏计划:**
|
||||
- 创建的悬赏
|
||||
- 认领的悬赏
|
||||
- 支付的悬赏
|
||||
- 平均完成时间
|
||||
|
||||
---
|
||||
|
||||
## 🤖 自动化
|
||||
|
||||
我们使用 GitHub Actions 进行自动化:
|
||||
|
||||
### PR 自动化
|
||||
|
||||
- **基于文件变更的自动标签**
|
||||
- **PR 大小标签**(small/medium/large)
|
||||
- **CI 检查**(测试、linting、构建)
|
||||
- **安全扫描**(Trivy、Gitleaks)
|
||||
- **Conventional commit 验证**
|
||||
|
||||
### Issue 自动化
|
||||
|
||||
- **过期 issue 检测**(30天不活动后关闭)
|
||||
- **使用 "bounty" 关键字时自动悬赏标签**
|
||||
- **使用 issue 相似性的重复检测**
|
||||
|
||||
### 发布自动化
|
||||
|
||||
- **从 conventional commits 生成 Changelog**
|
||||
- **基于 commit 类型的版本升级**
|
||||
- **自动生成发布说明**
|
||||
- **部署到 staging/production**
|
||||
|
||||
---
|
||||
|
||||
## 🔄 定期任务
|
||||
|
||||
### 每日
|
||||
- ✅ 分类新的 issue/PR
|
||||
- ✅ 审查紧急 PR
|
||||
- ✅ 回应社区问题
|
||||
|
||||
### 每周
|
||||
- ✅ Sprint 计划(星期一)
|
||||
- ✅ Sprint 回顾(星期五)
|
||||
- ✅ 审查指标仪表板
|
||||
- ✅ 更新项目看板
|
||||
|
||||
### 每月
|
||||
- ✅ 路线图进度回顾
|
||||
- ✅ 社区更新帖子
|
||||
- ✅ 悬赏计划回顾
|
||||
- ✅ 依赖更新
|
||||
- ✅ 安全审计
|
||||
|
||||
### 每季度
|
||||
- ✅ 路线图更新
|
||||
- ✅ 主要版本规划
|
||||
- ✅ 贡献者表彰
|
||||
- ✅ 文档审计
|
||||
|
||||
---
|
||||
|
||||
## 📞 沟通渠道
|
||||
|
||||
### 内部(维护者)
|
||||
|
||||
- **GitHub Discussions:** 架构决策、RFC
|
||||
- **私人频道:** 敏感讨论、悬赏支付
|
||||
- **每周同步:** Sprint 计划和回顾
|
||||
|
||||
### 外部(社区)
|
||||
|
||||
- **Telegram:** [@nofx_dev_community](https://t.me/nofx_dev_community)
|
||||
- **GitHub Issues:** Bug 报告、功能请求
|
||||
- **GitHub Discussions:** 一般问题、想法
|
||||
- **Twitter:** [@nofx_ai](https://x.com/nofx_ai) - 公告
|
||||
|
||||
---
|
||||
|
||||
## 🎓 新维护者入职
|
||||
|
||||
### 新维护者检查清单
|
||||
|
||||
- [ ] 添加到 GitHub 组织
|
||||
- [ ] 授予仓库写入权限
|
||||
- [ ] 添加到私人维护者频道
|
||||
- [ ] 介绍给团队
|
||||
- [ ] 阅读 `/docs/maintainers/` 中的所有文档
|
||||
- [ ] 跟随有经验的维护者 1 个 Sprint
|
||||
- [ ] 首次单独 PR 审核(有备份审核者)
|
||||
- [ ] 首次单独 issue 分类
|
||||
- [ ] 首次参与 Sprint 计划
|
||||
|
||||
### 期望
|
||||
|
||||
**时间投入:**
|
||||
- 每周约 5-10 小时
|
||||
- 参与 Sprint 计划/回顾
|
||||
- 在 SLA 内回应分配的 issue/PR
|
||||
|
||||
**职责:**
|
||||
- 代码审核
|
||||
- Issue 分类
|
||||
- 社区支持
|
||||
- 文档维护
|
||||
|
||||
---
|
||||
|
||||
## 🏆 贡献者表彰
|
||||
|
||||
### 每月表彰
|
||||
|
||||
**在社区更新中聚焦:**
|
||||
- 顶级贡献者
|
||||
- 本月最佳 PR
|
||||
- 最有帮助的社区成员
|
||||
|
||||
### 每季度表彰
|
||||
|
||||
**贡献者等级系统:**
|
||||
- 🥇 **核心贡献者** - 20+ 个已合并 PR
|
||||
- 🥈 **活跃贡献者** - 10+ 个已合并 PR
|
||||
- 🥉 **贡献者** - 5+ 个已合并 PR
|
||||
- ⭐ **首次贡献者** - 1+ 个已合并 PR
|
||||
|
||||
**福利:**
|
||||
- 在 README 中表彰
|
||||
- 邀请加入私人 Discord
|
||||
- 早期访问功能
|
||||
- 周边商品(核心贡献者)
|
||||
|
||||
---
|
||||
|
||||
## 📚 资源
|
||||
|
||||
### 内部文档
|
||||
- [PR 审核指南](PR_REVIEW_GUIDE.zh-CN.md)
|
||||
- [安全政策](../../SECURITY.md)
|
||||
- [行为准则](../../CODE_OF_CONDUCT.md)
|
||||
|
||||
### 外部资源
|
||||
- [GitHub 项目管理](https://docs.github.com/en/issues/planning-and-tracking-with-projects)
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
- [语义化版本](https://semver.org/)
|
||||
|
||||
---
|
||||
|
||||
## 🤔 问题?
|
||||
|
||||
在维护者频道联系我们或开启讨论。
|
||||
|
||||
**让我们一起构建令人惊叹的产品!🚀**
|
||||
458
docs/maintainers/PR_REVIEW_GUIDE.md
Normal file
@@ -0,0 +1,458 @@
|
||||
# 🔍 PR Review Guide for Maintainers
|
||||
|
||||
**Language:** [English](PR_REVIEW_GUIDE.md) | [中文](PR_REVIEW_GUIDE.zh-CN.md)
|
||||
|
||||
This guide is for NOFX maintainers reviewing pull requests.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Review Checklist
|
||||
|
||||
### 1. Initial Triage (Within 24 hours)
|
||||
|
||||
- [ ] **Check PR alignment with roadmap**
|
||||
- Does it fit into our current priorities?
|
||||
- Is it in the [roadmap](../roadmap/README.md)?
|
||||
- If not, should we accept it anyway?
|
||||
|
||||
- [ ] **Verify PR completeness**
|
||||
- All sections of PR template filled?
|
||||
- Clear description of changes?
|
||||
- Related issues linked?
|
||||
- Screenshots/demo for UI changes?
|
||||
|
||||
- [ ] **Apply appropriate labels**
|
||||
- Priority: critical/high/medium/low
|
||||
- Type: bug/feature/enhancement/docs
|
||||
- Area: frontend/backend/exchange/ai/security
|
||||
- Status: needs review/needs changes
|
||||
|
||||
- [ ] **Assign reviewers**
|
||||
- Assign based on area of expertise
|
||||
- At least 1 maintainer review required
|
||||
|
||||
### 2. Code Review
|
||||
|
||||
#### A. Functionality Review
|
||||
|
||||
```markdown
|
||||
✅ **Questions to Ask:**
|
||||
|
||||
- Does it solve the stated problem?
|
||||
- Are edge cases handled?
|
||||
- Will this break existing functionality?
|
||||
- Is the approach correct for our architecture?
|
||||
- Are there better alternatives?
|
||||
```
|
||||
|
||||
**Testing:**
|
||||
- [ ] All CI checks passed?
|
||||
- [ ] Manual testing performed by contributor?
|
||||
- [ ] Test coverage adequate?
|
||||
- [ ] Tests are meaningful (not just for coverage)?
|
||||
|
||||
#### B. Code Quality Review
|
||||
|
||||
**Go Backend Code:**
|
||||
|
||||
```go
|
||||
// ❌ Bad - Reject
|
||||
func GetData(a, b string) interface{} {
|
||||
d := doSomething(a, b)
|
||||
return d
|
||||
}
|
||||
|
||||
// ✅ Good - Approve
|
||||
func GetAccountBalance(apiKey, secretKey string) (*Balance, error) {
|
||||
if apiKey == "" || secretKey == "" {
|
||||
return nil, fmt.Errorf("API credentials required")
|
||||
}
|
||||
|
||||
balance, err := client.FetchBalance(apiKey, secretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch balance: %w", err)
|
||||
}
|
||||
|
||||
return balance, nil
|
||||
}
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- [ ] Meaningful variable/function names
|
||||
- [ ] Proper error handling (no ignored errors)
|
||||
- [ ] Comments for complex logic
|
||||
- [ ] No hardcoded values (use constants/config)
|
||||
- [ ] Follows Go idioms and conventions
|
||||
- [ ] No unnecessary complexity
|
||||
|
||||
**TypeScript/React Frontend Code:**
|
||||
|
||||
```typescript
|
||||
// ❌ Bad - Reject
|
||||
const getData = (data: any) => {
|
||||
return data.map(d => <div>{d.name}</div>)
|
||||
}
|
||||
|
||||
// ✅ Good - Approve
|
||||
interface Trader {
|
||||
id: string;
|
||||
name: string;
|
||||
status: 'running' | 'stopped';
|
||||
}
|
||||
|
||||
const TraderList: React.FC<{ traders: Trader[] }> = ({ traders }) => {
|
||||
return (
|
||||
<div className="trader-list">
|
||||
{traders.map(trader => (
|
||||
<TraderCard key={trader.id} trader={trader} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- [ ] Type safety (no `any` unless absolutely necessary)
|
||||
- [ ] Proper React patterns (hooks, functional components)
|
||||
- [ ] Component reusability
|
||||
- [ ] Accessibility (a11y) considerations
|
||||
- [ ] Performance optimizations (memoization where needed)
|
||||
|
||||
#### C. Security Review
|
||||
|
||||
**Critical Checks:**
|
||||
|
||||
```go
|
||||
// 🚨 REJECT - Security Issue
|
||||
func Login(username, password string) {
|
||||
query := "SELECT * FROM users WHERE username='" + username + "'" // SQL Injection!
|
||||
db.Query(query)
|
||||
}
|
||||
|
||||
// ✅ APPROVE - Secure
|
||||
func Login(username, password string) error {
|
||||
query := "SELECT * FROM users WHERE username = ?"
|
||||
row := db.QueryRow(query, username) // Parameterized query
|
||||
// ... proper password verification with bcrypt
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] No SQL injection vulnerabilities
|
||||
- [ ] No XSS vulnerabilities in frontend
|
||||
- [ ] API keys/secrets not hardcoded
|
||||
- [ ] User inputs properly validated
|
||||
- [ ] Authentication/authorization properly handled
|
||||
- [ ] No sensitive data in logs
|
||||
- [ ] Dependencies have no known vulnerabilities
|
||||
|
||||
#### D. Performance Review
|
||||
|
||||
- [ ] No obvious performance issues
|
||||
- [ ] Database queries optimized (indexes, no N+1 queries)
|
||||
- [ ] No unnecessary API calls
|
||||
- [ ] Proper caching where applicable
|
||||
- [ ] No memory leaks
|
||||
|
||||
### 3. Documentation Review
|
||||
|
||||
- [ ] Code comments for complex logic
|
||||
- [ ] README updated if needed
|
||||
- [ ] API documentation updated (if API changes)
|
||||
- [ ] Migration guide for breaking changes
|
||||
- [ ] Changelog entry (for significant changes)
|
||||
|
||||
### 4. Testing Review
|
||||
|
||||
- [ ] Unit tests for new functions
|
||||
- [ ] Integration tests for new features
|
||||
- [ ] Tests actually test the functionality (not just coverage)
|
||||
- [ ] Test names are descriptive
|
||||
- [ ] Mock data is realistic
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Label Management
|
||||
|
||||
### Priority Assignment
|
||||
|
||||
Use these criteria to assign priority:
|
||||
|
||||
**Critical:**
|
||||
- Security vulnerabilities
|
||||
- Production-breaking bugs
|
||||
- Data loss issues
|
||||
|
||||
**High:**
|
||||
- Major bugs affecting many users
|
||||
- High-priority roadmap features
|
||||
- Performance issues
|
||||
|
||||
**Medium:**
|
||||
- Regular bug fixes
|
||||
- Standard feature requests
|
||||
- Refactoring
|
||||
|
||||
**Low:**
|
||||
- Minor improvements
|
||||
- Code style changes
|
||||
- Non-urgent documentation
|
||||
|
||||
### Status Workflow
|
||||
|
||||
```
|
||||
needs review → in review → needs changes → needs review → approved → merged
|
||||
↓
|
||||
on hold
|
||||
```
|
||||
|
||||
**Status Labels:**
|
||||
- `status: needs review` - Ready for initial review
|
||||
- `status: in progress` - Being actively reviewed
|
||||
- `status: needs changes` - Reviewer requested changes
|
||||
- `status: on hold` - Waiting for discussion/decision
|
||||
- `status: blocked` - Blocked by another PR/issue
|
||||
|
||||
---
|
||||
|
||||
## 💬 Providing Feedback
|
||||
|
||||
### Writing Good Review Comments
|
||||
|
||||
**❌ Bad Comments:**
|
||||
```
|
||||
This is wrong.
|
||||
Change this.
|
||||
Why did you do this?
|
||||
```
|
||||
|
||||
**✅ Good Comments:**
|
||||
```
|
||||
This approach might cause issues with concurrent requests.
|
||||
Consider using a mutex or atomic operations here.
|
||||
|
||||
Suggestion: Extract this logic into a separate function for better testability:
|
||||
```go
|
||||
func validateTraderConfig(config *TraderConfig) error {
|
||||
// validation logic
|
||||
}
|
||||
```
|
||||
|
||||
Question: Have you considered using the existing `ExchangeClient` interface
|
||||
instead of creating a new one? This would maintain consistency with the rest
|
||||
of the codebase.
|
||||
```
|
||||
|
||||
### Comment Types
|
||||
|
||||
**🔴 Blocking (must be addressed):**
|
||||
```markdown
|
||||
**BLOCKING:** This introduces a SQL injection vulnerability.
|
||||
Please use parameterized queries instead.
|
||||
```
|
||||
|
||||
**🟡 Non-blocking (suggestions):**
|
||||
```markdown
|
||||
**Suggestion:** Consider using `strings.Builder` here for better performance
|
||||
when concatenating many strings.
|
||||
```
|
||||
|
||||
**🟢 Praise (encourage good practices):**
|
||||
```markdown
|
||||
**Nice!** Great use of context for timeout handling. This is exactly what
|
||||
we want to see.
|
||||
```
|
||||
|
||||
### Questions vs Directives
|
||||
|
||||
**❌ Directive (can feel demanding):**
|
||||
```
|
||||
Change this to use the factory pattern.
|
||||
Add tests for this function.
|
||||
```
|
||||
|
||||
**✅ Question (more collaborative):**
|
||||
```
|
||||
Would the factory pattern be a better fit here? It might make testing easier.
|
||||
Could you add a test case for the error path? I want to make sure we handle
|
||||
failures gracefully.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ Response Time Guidelines
|
||||
|
||||
| PR Type | Initial Review | Follow-up | Merge Decision |
|
||||
|---------|---------------|-----------|----------------|
|
||||
| **Critical Bug** | 4 hours | 2 hours | Same day |
|
||||
| **Bounty PR** | 24 hours | 12 hours | 2-3 days |
|
||||
| **Feature** | 2-3 days | 1-2 days | 3-5 days |
|
||||
| **Documentation** | 2-3 days | 1-2 days | 3-5 days |
|
||||
| **Large PR** | 3-5 days | 2-3 days | 5-7 days |
|
||||
|
||||
---
|
||||
|
||||
## ✅ Approval Criteria
|
||||
|
||||
A PR should be approved when:
|
||||
|
||||
1. **Functionality**
|
||||
- ✅ Solves the stated problem
|
||||
- ✅ No regression in existing features
|
||||
- ✅ Edge cases handled
|
||||
|
||||
2. **Quality**
|
||||
- ✅ Follows code standards
|
||||
- ✅ Well-structured and readable
|
||||
- ✅ Adequate test coverage
|
||||
|
||||
3. **Security**
|
||||
- ✅ No security vulnerabilities
|
||||
- ✅ Inputs validated
|
||||
- ✅ Secrets properly managed
|
||||
|
||||
4. **Documentation**
|
||||
- ✅ Code commented where needed
|
||||
- ✅ Docs updated if applicable
|
||||
|
||||
5. **Process**
|
||||
- ✅ All CI checks pass
|
||||
- ✅ All review comments addressed
|
||||
- ✅ Rebased on latest dev branch
|
||||
|
||||
---
|
||||
|
||||
## 🚫 Rejection Criteria
|
||||
|
||||
Reject a PR if:
|
||||
|
||||
**Immediate Rejection:**
|
||||
- 🔴 Introduces security vulnerabilities
|
||||
- 🔴 Contains malicious code
|
||||
- 🔴 Violates Code of Conduct
|
||||
- 🔴 Contains plagiarized code
|
||||
- 🔴 Hardcoded API keys or secrets
|
||||
|
||||
**Request Changes:**
|
||||
- 🟡 Poor code quality (after feedback ignored)
|
||||
- 🟡 No tests for new features
|
||||
- 🟡 Breaking changes without migration path
|
||||
- 🟡 Doesn't align with roadmap (without prior discussion)
|
||||
- 🟡 Incomplete (missing critical parts)
|
||||
|
||||
**Close with Explanation:**
|
||||
- 🟠 Duplicate functionality
|
||||
- 🟠 Out of scope for project
|
||||
- 🟠 Better alternative already exists
|
||||
- 🟠 Contributor unresponsive for >2 weeks
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Special Case Reviews
|
||||
|
||||
### Bounty PRs
|
||||
|
||||
Extra care needed:
|
||||
|
||||
- [ ] All acceptance criteria met?
|
||||
- [ ] Demo video/screenshots provided?
|
||||
- [ ] Working as specified in bounty issue?
|
||||
- [ ] Payment info discussed privately?
|
||||
- [ ] Priority review (24h turnaround)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [ ] Migration guide provided?
|
||||
- [ ] Deprecation warnings added?
|
||||
- [ ] Version bump planned?
|
||||
- [ ] Backward compatibility considered?
|
||||
- [ ] RFC (Request for Comments) created for major changes?
|
||||
|
||||
### Security PRs
|
||||
|
||||
- [ ] Verified by security-focused reviewer?
|
||||
- [ ] No public disclosure of vulnerability?
|
||||
- [ ] Coordinated disclosure if needed?
|
||||
- [ ] Security advisory prepared?
|
||||
- [ ] Patch release planned?
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Merge Guidelines
|
||||
|
||||
### When to Merge
|
||||
|
||||
Merge when:
|
||||
- ✅ At least 1 approval from maintainer
|
||||
- ✅ All CI checks passing
|
||||
- ✅ All conversations resolved
|
||||
- ✅ No requested changes pending
|
||||
- ✅ Rebased on latest target branch
|
||||
|
||||
### Merge Strategy
|
||||
|
||||
**Squash Merge** (default for most PRs):
|
||||
- Small bug fixes
|
||||
- Single-feature PRs
|
||||
- Documentation updates
|
||||
- Keeps git history clean
|
||||
|
||||
**Merge Commit** (for complex PRs):
|
||||
- Multi-commit features with logical commits
|
||||
- Preserve commit history
|
||||
- Large refactoring with atomic commits
|
||||
|
||||
**Rebase and Merge** (rarely):
|
||||
- When linear history is important
|
||||
- Commits are already well-structured
|
||||
|
||||
### Merge Commit Message
|
||||
|
||||
Format:
|
||||
```
|
||||
<type>(<scope>): <PR title> (#123)
|
||||
|
||||
Brief description of changes.
|
||||
|
||||
- Key change 1
|
||||
- Key change 2
|
||||
|
||||
Co-authored-by: Contributor Name <email@example.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Review Metrics to Track
|
||||
|
||||
Monitor these metrics monthly:
|
||||
|
||||
- Average time to first review
|
||||
- Average time to merge
|
||||
- PR acceptance rate
|
||||
- Number of PRs by type (bug/feature/docs)
|
||||
- Number of PRs by area (frontend/backend/exchange)
|
||||
- Contributor retention rate
|
||||
|
||||
---
|
||||
|
||||
## 🙋 Questions?
|
||||
|
||||
If unsure about a PR:
|
||||
|
||||
1. **Ask other maintainers** in private channel
|
||||
2. **Request more context** from contributor
|
||||
3. **Mark as "on hold"** and add to next maintainer sync
|
||||
4. **When in doubt, be conservative** - better to ask than approve something risky
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Resources
|
||||
|
||||
- [Contributing Guide](../../CONTRIBUTING.md)
|
||||
- [Code of Conduct](../../CODE_OF_CONDUCT.md)
|
||||
- [Security Policy](../../SECURITY.md)
|
||||
- [Project Roadmap](../roadmap/README.md)
|
||||
|
||||
---
|
||||
|
||||
**Remember:** Reviews should be **respectful**, **constructive**, and **educational**.
|
||||
We're building a community, not just code. 🚀
|
||||
457
docs/maintainers/PR_REVIEW_GUIDE.zh-CN.md
Normal file
@@ -0,0 +1,457 @@
|
||||
# 🔍 维护者 PR 审核指南
|
||||
|
||||
**语言:** [English](PR_REVIEW_GUIDE.md) | [中文](PR_REVIEW_GUIDE.zh-CN.md)
|
||||
|
||||
本指南适用于审核 pull request 的 NOFX 维护者。
|
||||
|
||||
---
|
||||
|
||||
## 📋 审核清单
|
||||
|
||||
### 1. 初步分类(24小时内)
|
||||
|
||||
- [ ] **检查 PR 与路线图的一致性**
|
||||
- 是否符合我们当前的优先级?
|
||||
- 是否在[路线图](../roadmap/README.zh-CN.md)中?
|
||||
- 如果不在,我们是否应该接受它?
|
||||
|
||||
- [ ] **验证 PR 完整性**
|
||||
- PR 模板的所有部分都已填写?
|
||||
- 变更描述清晰?
|
||||
- 相关 issue 已链接?
|
||||
- UI 变更有截图/演示?
|
||||
|
||||
- [ ] **应用适当的标签**
|
||||
- 优先级:critical/high/medium/low
|
||||
- 类型:bug/feature/enhancement/docs
|
||||
- 区域:frontend/backend/exchange/ai/security
|
||||
- 状态:needs review/needs changes
|
||||
|
||||
- [ ] **分配审核者**
|
||||
- 根据专业领域分配
|
||||
- 至少需要 1 个维护者审核
|
||||
|
||||
### 2. 代码审核
|
||||
|
||||
#### A. 功能审核
|
||||
|
||||
```markdown
|
||||
✅ **要问的问题:**
|
||||
|
||||
- 是否解决了所述问题?
|
||||
- 边界情况是否处理?
|
||||
- 是否会破坏现有功能?
|
||||
- 方法是否适合我们的架构?
|
||||
- 是否有更好的替代方案?
|
||||
```
|
||||
|
||||
**测试:**
|
||||
- [ ] 所有 CI 检查都通过?
|
||||
- [ ] 贡献者进行了手动测试?
|
||||
- [ ] 测试覆盖率足够?
|
||||
- [ ] 测试有意义(不只是为了覆盖率)?
|
||||
|
||||
#### B. 代码质量审核
|
||||
|
||||
**Go 后端代码:**
|
||||
|
||||
```go
|
||||
// ❌ 差 - 拒绝
|
||||
func GetData(a, b string) interface{} {
|
||||
d := doSomething(a, b)
|
||||
return d
|
||||
}
|
||||
|
||||
// ✅ 好 - 批准
|
||||
func GetAccountBalance(apiKey, secretKey string) (*Balance, error) {
|
||||
if apiKey == "" || secretKey == "" {
|
||||
return nil, fmt.Errorf("API credentials required")
|
||||
}
|
||||
|
||||
balance, err := client.FetchBalance(apiKey, secretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch balance: %w", err)
|
||||
}
|
||||
|
||||
return balance, nil
|
||||
}
|
||||
```
|
||||
|
||||
**检查项:**
|
||||
- [ ] 有意义的变量/函数名
|
||||
- [ ] 正确的错误处理(没有忽略错误)
|
||||
- [ ] 复杂逻辑有注释
|
||||
- [ ] 没有硬编码值(使用常量/配置)
|
||||
- [ ] 遵循 Go 习惯用法和约定
|
||||
- [ ] 没有不必要的复杂性
|
||||
|
||||
**TypeScript/React 前端代码:**
|
||||
|
||||
```typescript
|
||||
// ❌ 差 - 拒绝
|
||||
const getData = (data: any) => {
|
||||
return data.map(d => <div>{d.name}</div>)
|
||||
}
|
||||
|
||||
// ✅ 好 - 批准
|
||||
interface Trader {
|
||||
id: string;
|
||||
name: string;
|
||||
status: 'running' | 'stopped';
|
||||
}
|
||||
|
||||
const TraderList: React.FC<{ traders: Trader[] }> = ({ traders }) => {
|
||||
return (
|
||||
<div className="trader-list">
|
||||
{traders.map(trader => (
|
||||
<TraderCard key={trader.id} trader={trader} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**检查项:**
|
||||
- [ ] 类型安全(除非绝对必要,否则不使用 `any`)
|
||||
- [ ] 正确的 React 模式(hooks、函数式组件)
|
||||
- [ ] 组件可重用性
|
||||
- [ ] 可访问性(a11y)考虑
|
||||
- [ ] 性能优化(需要时使用 memoization)
|
||||
|
||||
#### C. 安全审核
|
||||
|
||||
**关键检查:**
|
||||
|
||||
```go
|
||||
// 🚨 拒绝 - 安全问题
|
||||
func Login(username, password string) {
|
||||
query := "SELECT * FROM users WHERE username='" + username + "'" // SQL 注入!
|
||||
db.Query(query)
|
||||
}
|
||||
|
||||
// ✅ 批准 - 安全
|
||||
func Login(username, password string) error {
|
||||
query := "SELECT * FROM users WHERE username = ?"
|
||||
row := db.QueryRow(query, username) // 参数化查询
|
||||
// ... 使用 bcrypt 进行正确的密码验证
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] 没有 SQL 注入漏洞
|
||||
- [ ] 前端没有 XSS 漏洞
|
||||
- [ ] API 密钥/密码没有硬编码
|
||||
- [ ] 用户输入已正确验证
|
||||
- [ ] 认证/授权正确处理
|
||||
- [ ] 日志中没有敏感数据
|
||||
- [ ] 依赖项没有已知漏洞
|
||||
|
||||
#### D. 性能审核
|
||||
|
||||
- [ ] 没有明显的性能问题
|
||||
- [ ] 数据库查询已优化(索引、没有 N+1 查询)
|
||||
- [ ] 没有不必要的 API 调用
|
||||
- [ ] 适当的缓存
|
||||
- [ ] 没有内存泄漏
|
||||
|
||||
### 3. 文档审核
|
||||
|
||||
- [ ] 复杂逻辑有代码注释
|
||||
- [ ] 如果需要,README 已更新
|
||||
- [ ] API 文档已更新(如有 API 变更)
|
||||
- [ ] 破坏性变更有迁移指南
|
||||
- [ ] Changelog 条目(对于重大变更)
|
||||
|
||||
### 4. 测试审核
|
||||
|
||||
- [ ] 新函数有单元测试
|
||||
- [ ] 新功能有集成测试
|
||||
- [ ] 测试确实测试了功能(不只是覆盖率)
|
||||
- [ ] 测试名称具有描述性
|
||||
- [ ] 模拟数据真实
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ 标签管理
|
||||
|
||||
### 优先级分配
|
||||
|
||||
使用这些标准来分配优先级:
|
||||
|
||||
**Critical(严重):**
|
||||
- 安全漏洞
|
||||
- 生产环境破坏性 bug
|
||||
- 数据丢失问题
|
||||
|
||||
**High(高):**
|
||||
- 影响许多用户的重大 bug
|
||||
- 高优先级路线图功能
|
||||
- 性能问题
|
||||
|
||||
**Medium(中):**
|
||||
- 常规 bug 修复
|
||||
- 标准功能请求
|
||||
- 重构
|
||||
|
||||
**Low(低):**
|
||||
- 次要改进
|
||||
- 代码风格变更
|
||||
- 非紧急文档
|
||||
|
||||
### 状态工作流
|
||||
|
||||
```
|
||||
needs review → in review → needs changes → needs review → approved → merged
|
||||
↓
|
||||
on hold
|
||||
```
|
||||
|
||||
**状态标签:**
|
||||
- `status: needs review` - 准备初次审核
|
||||
- `status: in progress` - 正在积极审核
|
||||
- `status: needs changes` - 审核者请求更改
|
||||
- `status: on hold` - 等待讨论/决定
|
||||
- `status: blocked` - 被另一个 PR/issue 阻塞
|
||||
|
||||
---
|
||||
|
||||
## 💬 提供反馈
|
||||
|
||||
### 编写好的审核评论
|
||||
|
||||
**❌ 差的评论:**
|
||||
```
|
||||
这是错的。
|
||||
改这个。
|
||||
你为什么这样做?
|
||||
```
|
||||
|
||||
**✅ 好的评论:**
|
||||
```
|
||||
这种方法可能会导致并发请求的问题。
|
||||
考虑在这里使用互斥锁或原子操作。
|
||||
|
||||
建议:将此逻辑提取到单独的函数中以提高可测试性:
|
||||
```go
|
||||
func validateTraderConfig(config *TraderConfig) error {
|
||||
// 验证逻辑
|
||||
}
|
||||
```
|
||||
|
||||
问题:你是否考虑过使用现有的 `ExchangeClient` 接口
|
||||
而不是创建新接口?这将与代码库的其余部分保持一致。
|
||||
```
|
||||
|
||||
### 评论类型
|
||||
|
||||
**🔴 阻塞性(必须解决):**
|
||||
```markdown
|
||||
**阻塞性:** 这引入了 SQL 注入漏洞。
|
||||
请改用参数化查询。
|
||||
```
|
||||
|
||||
**🟡 非阻塞性(建议):**
|
||||
```markdown
|
||||
**建议:** 考虑在这里使用 `strings.Builder` 以提高
|
||||
连接多个字符串时的性能。
|
||||
```
|
||||
|
||||
**🟢 赞扬(鼓励好的做法):**
|
||||
```markdown
|
||||
**很好!** 很好地使用 context 进行超时处理。这正是
|
||||
我们想看到的。
|
||||
```
|
||||
|
||||
### 问题 vs 指令
|
||||
|
||||
**❌ 指令(可能感觉强硬):**
|
||||
```
|
||||
改用工厂模式。
|
||||
为这个函数添加测试。
|
||||
```
|
||||
|
||||
**✅ 问题(更协作):**
|
||||
```
|
||||
工厂模式在这里会更合适吗?它可能会使测试更容易。
|
||||
你能为错误路径添加一个测试用例吗?我想确保我们
|
||||
优雅地处理失败。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ 响应时间指南
|
||||
|
||||
| PR 类型 | 初次审核 | 后续审核 | 合并决定 |
|
||||
|---------|----------|----------|----------|
|
||||
| **严重 Bug** | 4 小时 | 2 小时 | 当天 |
|
||||
| **悬赏 PR** | 24 小时 | 12 小时 | 2-3 天 |
|
||||
| **功能** | 2-3 天 | 1-2 天 | 3-5 天 |
|
||||
| **文档** | 2-3 天 | 1-2 天 | 3-5 天 |
|
||||
| **大型 PR** | 3-5 天 | 2-3 天 | 5-7 天 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 批准标准
|
||||
|
||||
PR 应在以下情况下批准:
|
||||
|
||||
1. **功能性**
|
||||
- ✅ 解决了所述问题
|
||||
- ✅ 现有功能没有退化
|
||||
- ✅ 边界情况已处理
|
||||
|
||||
2. **质量**
|
||||
- ✅ 遵循代码标准
|
||||
- ✅ 结构良好且可读
|
||||
- ✅ 测试覆盖率足够
|
||||
|
||||
3. **安全性**
|
||||
- ✅ 没有安全漏洞
|
||||
- ✅ 输入已验证
|
||||
- ✅ 密钥管理正确
|
||||
|
||||
4. **文档**
|
||||
- ✅ 需要的地方有代码注释
|
||||
- ✅ 文档已更新(如适用)
|
||||
|
||||
5. **流程**
|
||||
- ✅ 所有 CI 检查通过
|
||||
- ✅ 所有审核评论已处理
|
||||
- ✅ 已基于最新 dev 分支 rebase
|
||||
|
||||
---
|
||||
|
||||
## 🚫 拒绝标准
|
||||
|
||||
在以下情况下拒绝 PR:
|
||||
|
||||
**立即拒绝:**
|
||||
- 🔴 引入安全漏洞
|
||||
- 🔴 包含恶意代码
|
||||
- 🔴 违反行为准则
|
||||
- 🔴 包含抄袭代码
|
||||
- 🔴 硬编码 API 密钥或密码
|
||||
|
||||
**请求更改:**
|
||||
- 🟡 代码质量差(反馈被忽略后)
|
||||
- 🟡 新功能没有测试
|
||||
- 🟡 没有迁移路径的破坏性变更
|
||||
- 🟡 与路线图不一致(未经事先讨论)
|
||||
- 🟡 不完整(缺少关键部分)
|
||||
|
||||
**关闭并说明:**
|
||||
- 🟠 重复功能
|
||||
- 🟠 超出项目范围
|
||||
- 🟠 已存在更好的替代方案
|
||||
- 🟠 贡献者 >2 周无响应
|
||||
|
||||
---
|
||||
|
||||
## 🎯 特殊情况审核
|
||||
|
||||
### 悬赏 PR
|
||||
|
||||
需要额外注意:
|
||||
|
||||
- [ ] 所有验收标准都满足?
|
||||
- [ ] 提供了演示视频/截图?
|
||||
- [ ] 按悬赏 issue 中的规定工作?
|
||||
- [ ] 私下讨论了付款信息?
|
||||
- [ ] 优先审核(24小时周转)
|
||||
|
||||
### 破坏性变更
|
||||
|
||||
- [ ] 提供了迁移指南?
|
||||
- [ ] 添加了弃用警告?
|
||||
- [ ] 计划了版本升级?
|
||||
- [ ] 考虑了向后兼容性?
|
||||
- [ ] 为重大变更创建了 RFC?
|
||||
|
||||
### 安全 PR
|
||||
|
||||
- [ ] 由专注于安全的审核者验证?
|
||||
- [ ] 没有公开披露漏洞?
|
||||
- [ ] 如需要,协调披露?
|
||||
- [ ] 准备了安全公告?
|
||||
- [ ] 计划了补丁发布?
|
||||
|
||||
---
|
||||
|
||||
## 🔄 合并指南
|
||||
|
||||
### 何时合并
|
||||
|
||||
满足以下条件时合并:
|
||||
- ✅ 至少 1 个维护者批准
|
||||
- ✅ 所有 CI 检查通过
|
||||
- ✅ 所有对话已解决
|
||||
- ✅ 没有待处理的请求更改
|
||||
- ✅ 已基于最新目标分支 rebase
|
||||
|
||||
### 合并策略
|
||||
|
||||
**Squash Merge**(大多数 PR 的默认策略):
|
||||
- 小型 bug 修复
|
||||
- 单功能 PR
|
||||
- 文档更新
|
||||
- 保持 git 历史清洁
|
||||
|
||||
**Merge Commit**(复杂 PR):
|
||||
- 具有逻辑提交的多提交功能
|
||||
- 保留提交历史
|
||||
- 具有原子提交的大型重构
|
||||
|
||||
**Rebase and Merge**(很少使用):
|
||||
- 线性历史很重要时
|
||||
- 提交已经结构良好
|
||||
|
||||
### 合并提交信息
|
||||
|
||||
格式:
|
||||
```
|
||||
<type>(<scope>): <PR 标题> (#123)
|
||||
|
||||
变更的简要描述。
|
||||
|
||||
- 关键变更 1
|
||||
- 关键变更 2
|
||||
|
||||
Co-authored-by: 贡献者姓名 <email@example.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 要跟踪的审核指标
|
||||
|
||||
每月监控这些指标:
|
||||
|
||||
- 平均首次审核时间
|
||||
- 平均合并时间
|
||||
- PR 接受率
|
||||
- 按类型分类的 PR 数量(bug/feature/docs)
|
||||
- 按区域分类的 PR 数量(frontend/backend/exchange)
|
||||
- 贡献者留存率
|
||||
|
||||
---
|
||||
|
||||
## 🙋 问题?
|
||||
|
||||
如果对 PR 不确定:
|
||||
|
||||
1. **询问其他维护者**在私人频道
|
||||
2. **向贡献者请求更多上下文**
|
||||
3. **标记为"on hold"**并添加到下次维护者同步
|
||||
4. **如有疑问,保守一点** - 问比批准有风险的东西更好
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关资源
|
||||
|
||||
- [贡献指南](../../CONTRIBUTING.md)
|
||||
- [行为准则](../../CODE_OF_CONDUCT.md)
|
||||
- [安全政策](../../SECURITY.md)
|
||||
- [项目路线图](../roadmap/README.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
**记住:** 审核应该是**尊重的**、**建设性的**和**教育性的**。
|
||||
我们在构建社区,而不仅仅是代码。🚀
|
||||
51
docs/maintainers/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 📚 Maintainer Documentation
|
||||
|
||||
**Language:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
This directory contains documentation for NOFX project maintainers and contributors who want to understand our processes.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [PR_REVIEW_GUIDE.md](PR_REVIEW_GUIDE.md) | Guide for reviewing pull requests |
|
||||
| [PROJECT_MANAGEMENT.md](PROJECT_MANAGEMENT.md) | Project management workflow and processes |
|
||||
| [SETUP_GUIDE.md](SETUP_GUIDE.md) | Setup guide for the PR management system |
|
||||
|
||||
**Available in:** 🇬🇧 English | 🇨🇳 中文
|
||||
|
||||
---
|
||||
|
||||
## 🎯 For New Maintainers
|
||||
|
||||
If you're a new maintainer, start here:
|
||||
|
||||
1. **Read the documentation** (listed above) to understand the review process
|
||||
2. **Shadow an experienced maintainer** for 1-2 weeks
|
||||
3. **Start with simple reviews** before handling complex PRs
|
||||
4. **Ask questions** in the maintainer channel
|
||||
|
||||
---
|
||||
|
||||
## 🤝 For Contributors
|
||||
|
||||
These documents are also helpful for contributors who want to:
|
||||
- Understand our review standards
|
||||
- Learn our project management workflow
|
||||
- See how we prioritize work
|
||||
|
||||
Everything here is transparent and designed to help you contribute successfully!
|
||||
|
||||
---
|
||||
|
||||
## 📞 Questions?
|
||||
|
||||
- **Public questions:** Use [GitHub Discussions](https://github.com/tinkle-community/nofx/discussions)
|
||||
- **Maintainer questions:** Use the maintainer channel
|
||||
- **Migration questions:** See [Migration Announcement](../community/MIGRATION_ANNOUNCEMENT.md)
|
||||
|
||||
---
|
||||
|
||||
**Remember:** We're building an open, welcoming community. Documentation should empower contributors while maintaining project quality. 🚀
|
||||
51
docs/maintainers/README.zh-CN.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 📚 维护者文档
|
||||
|
||||
**语言:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
此目录包含 NOFX 项目维护者和想要了解我们流程的贡献者的文档。
|
||||
|
||||
---
|
||||
|
||||
## 📖 文档
|
||||
|
||||
| 文档 | 描述 |
|
||||
|------|------|
|
||||
| [PR_REVIEW_GUIDE.md](PR_REVIEW_GUIDE.md) | PR 审核指南 |
|
||||
| [PROJECT_MANAGEMENT.md](PROJECT_MANAGEMENT.md) | 项目管理工作流程和流程 |
|
||||
| [SETUP_GUIDE.md](SETUP_GUIDE.md) | PR 管理系统设置指南 |
|
||||
|
||||
**可用语言:** 🇬🇧 English | 🇨🇳 中文
|
||||
|
||||
---
|
||||
|
||||
## 🎯 对于新维护者
|
||||
|
||||
如果你是新维护者,从这里开始:
|
||||
|
||||
1. **阅读文档**(上面列出的)以了解审核流程
|
||||
2. **跟随有经验的维护者** 1-2 周
|
||||
3. **从简单的审核开始**,然后再处理复杂的 PR
|
||||
4. **在维护者频道提问**
|
||||
|
||||
---
|
||||
|
||||
## 🤝 对于贡献者
|
||||
|
||||
这些文档对想要以下内容的贡献者也很有帮助:
|
||||
- 了解我们的审核标准
|
||||
- 学习我们的项目管理工作流程
|
||||
- 了解我们如何排定工作优先级
|
||||
|
||||
这里的一切都是透明的,旨在帮助你成功贡献!
|
||||
|
||||
---
|
||||
|
||||
## 📞 问题?
|
||||
|
||||
- **公开问题:** 使用 [GitHub Discussions](https://github.com/tinkle-community/nofx/discussions)
|
||||
- **维护者问题:** 使用维护者频道
|
||||
- **迁移问题:** 查看[迁移公告](../community/MIGRATION_ANNOUNCEMENT.zh-CN.md)
|
||||
|
||||
---
|
||||
|
||||
**记住:** 我们正在建立一个开放、热情的社区。文档应该赋能贡献者,同时保持项目质量。🚀
|
||||
381
docs/maintainers/SETUP_GUIDE.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# 🚀 PR Management System Setup Guide
|
||||
|
||||
**Language:** [English](SETUP_GUIDE.md) | [中文](SETUP_GUIDE.zh-CN.md)
|
||||
|
||||
This guide will help you set up and activate the complete PR management system for NOFX.
|
||||
|
||||
---
|
||||
|
||||
## 📦 What's Included
|
||||
|
||||
The PR management system includes:
|
||||
|
||||
### 1. **Documentation**
|
||||
- ✅ `CONTRIBUTING.md` - Contributor guidelines
|
||||
- ✅ `docs/maintainers/PR_REVIEW_GUIDE.md` - Reviewer guidelines
|
||||
- ✅ `docs/maintainers/PROJECT_MANAGEMENT.md` - Project management workflow
|
||||
- ✅ `docs/maintainers/SETUP_GUIDE.md` - This file
|
||||
|
||||
### 2. **GitHub Configuration**
|
||||
- ✅ `.github/PULL_REQUEST_TEMPLATE.md` - PR template (already exists)
|
||||
- ✅ `.github/labels.yml` - Label definitions
|
||||
- ✅ `.github/labeler.yml` - Auto-labeling rules
|
||||
- ✅ `.github/workflows/pr-checks.yml` - Automated PR checks
|
||||
|
||||
### 3. **Automation**
|
||||
- ✅ Automatic PR labeling
|
||||
- ✅ PR size checking
|
||||
- ✅ CI/CD tests
|
||||
- ✅ Security scanning
|
||||
- ✅ Commit message validation
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Setup Steps
|
||||
|
||||
### Step 1: Sync GitHub Labels
|
||||
|
||||
Create the labels defined in `.github/labels.yml`:
|
||||
|
||||
```bash
|
||||
# Option 1: Using gh CLI (recommended)
|
||||
gh label list # See current labels
|
||||
gh label delete <label-name> # Remove old labels if needed
|
||||
gh label create "priority: critical" --color "d73a4a" --description "Critical priority"
|
||||
# ... repeat for all labels in labels.yml
|
||||
|
||||
# Option 2: Use GitHub Labeler Action (automated)
|
||||
# The workflow will sync labels automatically on push
|
||||
```
|
||||
|
||||
**Or use the GitHub Labeler Action** (add to `.github/workflows/sync-labels.yml`):
|
||||
|
||||
```yaml
|
||||
name: Sync Labels
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- '.github/labels.yml'
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: crazy-max/ghaction-github-labeler@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
yaml-file: .github/labels.yml
|
||||
```
|
||||
|
||||
### Step 2: Enable GitHub Actions
|
||||
|
||||
1. Go to **Settings → Actions → General**
|
||||
2. Enable **"Allow all actions and reusable workflows"**
|
||||
3. Set **Workflow permissions** to **"Read and write permissions"**
|
||||
4. Check **"Allow GitHub Actions to create and approve pull requests"**
|
||||
|
||||
### Step 3: Set Up Branch Protection Rules
|
||||
|
||||
**For `main` branch:**
|
||||
|
||||
1. Go to **Settings → Branches → Add rule**
|
||||
2. Branch name pattern: `main`
|
||||
3. Configure:
|
||||
- ✅ Require a pull request before merging
|
||||
- ✅ Require approvals: **1**
|
||||
- ✅ Require status checks to pass before merging
|
||||
- Select: `Backend Tests (Go)`
|
||||
- Select: `Frontend Tests (React/TypeScript)`
|
||||
- Select: `Security Scan`
|
||||
- ✅ Require conversation resolution before merging
|
||||
- ✅ Do not allow bypassing the above settings
|
||||
- ❌ Allow force pushes (disabled)
|
||||
- ❌ Allow deletions (disabled)
|
||||
|
||||
**For `dev` branch:**
|
||||
|
||||
1. Same as above, but with:
|
||||
- Require approvals: **1**
|
||||
- Less strict (allow maintainers to bypass if needed)
|
||||
|
||||
### Step 4: Create GitHub Projects
|
||||
|
||||
1. Go to **Projects → New project**
|
||||
2. Create **"NOFX Development"** board
|
||||
- Template: Board
|
||||
- Add columns: `Backlog`, `Triaged`, `In Progress`, `In Review`, `Done`
|
||||
- Add views: Sprint, Roadmap, By Area, Priority
|
||||
|
||||
3. Create **"Bounty Program"** board
|
||||
- Template: Board
|
||||
- Add columns: `Available`, `Claimed`, `In Progress`, `Under Review`, `Paid`
|
||||
|
||||
### Step 5: Enable Discussions (Optional but Recommended)
|
||||
|
||||
1. Go to **Settings → General → Features**
|
||||
2. Enable **"Discussions"**
|
||||
3. Create categories:
|
||||
- 💬 **General** - General discussions
|
||||
- 💡 **Ideas** - Feature ideas and suggestions
|
||||
- 🙏 **Q&A** - Questions and answers
|
||||
- 📢 **Announcements** - Important updates
|
||||
- 🗳️ **Polls** - Community polls
|
||||
|
||||
### Step 6: Configure Issue Templates
|
||||
|
||||
The templates already exist in `.github/ISSUE_TEMPLATE/`. Verify they're working:
|
||||
|
||||
1. Go to **Issues → New issue**
|
||||
2. You should see:
|
||||
- 🐛 Bug Report
|
||||
- ✨ Feature Request
|
||||
- 💰 Bounty Claim
|
||||
|
||||
If not showing, check files are properly formatted YAML with frontmatter.
|
||||
|
||||
### Step 7: Set Up Code Owners (Optional)
|
||||
|
||||
Create `.github/CODEOWNERS`:
|
||||
|
||||
```
|
||||
# Global owners
|
||||
* @tinkle @zack
|
||||
|
||||
# Frontend
|
||||
/web/ @frontend-lead
|
||||
|
||||
# Exchange integrations
|
||||
/internal/exchange/ @exchange-lead
|
||||
|
||||
# AI components
|
||||
/internal/ai/ @ai-lead
|
||||
|
||||
# Documentation
|
||||
/docs/ @tinkle @zack
|
||||
*.md @tinkle @zack
|
||||
```
|
||||
|
||||
### Step 8: Configure Notifications
|
||||
|
||||
**For Maintainers:**
|
||||
|
||||
1. Go to **Settings → Notifications**
|
||||
2. Enable:
|
||||
- ✅ Pull request reviews
|
||||
- ✅ Pull request pushes
|
||||
- ✅ Comments on issues and PRs
|
||||
- ✅ New issues
|
||||
- ✅ Security alerts
|
||||
|
||||
3. Set up email filters to organize notifications
|
||||
|
||||
**For Repository:**
|
||||
|
||||
1. Go to **Settings → Webhooks** (if integrating with Slack/Discord)
|
||||
2. Add webhook for notifications
|
||||
|
||||
---
|
||||
|
||||
## 📋 Post-Setup Checklist
|
||||
|
||||
After setup, verify:
|
||||
|
||||
- [ ] Labels are created and visible
|
||||
- [ ] Branch protection rules are active
|
||||
- [ ] GitHub Actions workflows run on new PR
|
||||
- [ ] Auto-labeling works (create a test PR)
|
||||
- [ ] PR template shows when creating PR
|
||||
- [ ] Issue templates show when creating issue
|
||||
- [ ] Projects boards are accessible
|
||||
- [ ] CONTRIBUTING.md is linked in README
|
||||
|
||||
---
|
||||
|
||||
## 🎯 How to Use the System
|
||||
|
||||
### For Contributors
|
||||
|
||||
1. **Read** [CONTRIBUTING.md](../../../CONTRIBUTING.md)
|
||||
2. **Check** [Roadmap](../../roadmap/README.md) for priorities
|
||||
3. **Open issue** or find existing one
|
||||
4. **Create PR** using the template
|
||||
5. **Address review feedback**
|
||||
6. **Celebrate** when merged! 🎉
|
||||
|
||||
### For Maintainers
|
||||
|
||||
1. **Daily:** Triage new issues/PRs (15 min)
|
||||
2. **Daily:** Review assigned PRs
|
||||
3. **Weekly:** Sprint planning (Monday) and review (Friday)
|
||||
4. **Follow:** [PR Review Guide](PR_REVIEW_GUIDE.md)
|
||||
5. **Follow:** [Project Management Guide](PROJECT_MANAGEMENT.md)
|
||||
|
||||
### For Bounty Hunters
|
||||
|
||||
1. **Check** bounty issues with `bounty` label
|
||||
2. **Claim** by commenting on issue
|
||||
3. **Complete** within deadline
|
||||
4. **Submit PR** with bounty claim section filled
|
||||
5. **Get paid** after merge
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Testing the System
|
||||
|
||||
### Test 1: Create a Test PR
|
||||
|
||||
```bash
|
||||
# Create a test branch
|
||||
git checkout -b test/pr-system-check
|
||||
|
||||
# Make a small change
|
||||
echo "# Test" >> TEST.md
|
||||
|
||||
# Commit and push
|
||||
git add TEST.md
|
||||
git commit -m "test: verify PR automation system"
|
||||
git push origin test/pr-system-check
|
||||
|
||||
# Create PR on GitHub
|
||||
# Verify:
|
||||
# - PR template loads
|
||||
# - Auto-labels are applied
|
||||
# - CI checks run
|
||||
# - Size label is added
|
||||
```
|
||||
|
||||
### Test 2: Create a Test Issue
|
||||
|
||||
1. Go to **Issues → New issue**
|
||||
2. Select **Bug Report**
|
||||
3. Fill in template
|
||||
4. Submit
|
||||
5. Verify:
|
||||
- Template renders correctly
|
||||
- Issue can be labeled
|
||||
- Issue appears in project board
|
||||
|
||||
### Test 3: Test Auto-Labeling
|
||||
|
||||
Create PRs that change files in different areas:
|
||||
|
||||
```bash
|
||||
# Test 1: Frontend changes
|
||||
git checkout -b test/frontend-label
|
||||
touch web/src/test.tsx
|
||||
git add . && git commit -m "test: frontend labeling"
|
||||
git push origin test/frontend-label
|
||||
# Should get "area: frontend" label
|
||||
|
||||
# Test 2: Backend changes
|
||||
git checkout -b test/backend-label
|
||||
touch internal/test.go
|
||||
git add . && git commit -m "test: backend labeling"
|
||||
git push origin test/backend-label
|
||||
# Should get "area: backend" label
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Issue: Labels not syncing
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Delete all existing labels first
|
||||
gh label list --json name --jq '.[].name' | xargs -I {} gh label delete "{}" --yes
|
||||
|
||||
# Then create from labels.yml manually or via action
|
||||
```
|
||||
|
||||
### Issue: GitHub Actions not running
|
||||
|
||||
**Check:**
|
||||
1. Actions are enabled in repository settings
|
||||
2. Workflow files are in `.github/workflows/`
|
||||
3. YAML syntax is valid
|
||||
4. Permissions are set correctly
|
||||
|
||||
**Debug:**
|
||||
```bash
|
||||
# Validate workflow locally
|
||||
act pull_request # Using 'act' tool
|
||||
```
|
||||
|
||||
### Issue: Branch protection blocking PRs
|
||||
|
||||
**Check:**
|
||||
1. Required checks are defined in workflow
|
||||
2. Check names match exactly
|
||||
3. Checks are completing (not stuck)
|
||||
|
||||
**Temporary fix:**
|
||||
- Maintainers can bypass if urgent
|
||||
- Adjust protection rules if too strict
|
||||
|
||||
### Issue: Auto-labeler not working
|
||||
|
||||
**Check:**
|
||||
1. `.github/labeler.yml` exists and valid YAML
|
||||
2. Labels defined in labeler.yml exist in repository
|
||||
3. Workflow has `pull-requests: write` permission
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring and Maintenance
|
||||
|
||||
### Weekly Review
|
||||
|
||||
Check these metrics every week:
|
||||
|
||||
```bash
|
||||
# Using gh CLI
|
||||
gh pr list --state all --json number,createdAt,closedAt
|
||||
gh issue list --state all --json number,createdAt,closedAt
|
||||
|
||||
# Or use GitHub Insights
|
||||
# Repository → Insights → Pulse, Contributors, Traffic
|
||||
```
|
||||
|
||||
### Monthly Maintenance
|
||||
|
||||
- [ ] Review and update labels if needed
|
||||
- [ ] Check for outdated dependencies in workflows
|
||||
- [ ] Update CONTRIBUTING.md if processes change
|
||||
- [ ] Review automation effectiveness
|
||||
- [ ] Gather community feedback
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Training Resources
|
||||
|
||||
### For New Contributors
|
||||
|
||||
- [First Contributions Guide](https://github.com/firstcontributions/first-contributions)
|
||||
- [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/)
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
|
||||
### For Maintainers
|
||||
|
||||
- [The Art of Code Review](https://google.github.io/eng-practices/review/)
|
||||
- [GitHub Project Management](https://docs.github.com/en/issues/planning-and-tracking-with-projects)
|
||||
- [Maintainer Community](https://maintainers.github.com/)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 You're All Set!
|
||||
|
||||
The PR management system is now ready to:
|
||||
|
||||
✅ Guide contributors with clear guidelines
|
||||
✅ Automate repetitive tasks
|
||||
✅ Maintain code quality
|
||||
✅ Track progress systematically
|
||||
✅ Scale the community
|
||||
|
||||
**Questions?** Reach out in the maintainer channel or open a discussion.
|
||||
|
||||
**Let's build an amazing community! 🚀**
|
||||
381
docs/maintainers/SETUP_GUIDE.zh-CN.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# 🚀 PR 管理系统设置指南
|
||||
|
||||
**语言:** [English](SETUP_GUIDE.md) | [中文](SETUP_GUIDE.zh-CN.md)
|
||||
|
||||
本指南将帮助你为 NOFX 设置和激活完整的 PR 管理系统。
|
||||
|
||||
---
|
||||
|
||||
## 📦 包含内容
|
||||
|
||||
PR 管理系统包括:
|
||||
|
||||
### 1. **文档**
|
||||
- ✅ `CONTRIBUTING.md` - 贡献者指南
|
||||
- ✅ `docs/maintainers/PR_REVIEW_GUIDE.md` - 审核者指南
|
||||
- ✅ `docs/maintainers/PROJECT_MANAGEMENT.md` - 项目管理工作流程
|
||||
- ✅ `docs/maintainers/SETUP_GUIDE.md` - 本文件
|
||||
|
||||
### 2. **GitHub 配置**
|
||||
- ✅ `.github/PULL_REQUEST_TEMPLATE.md` - PR 模板(已存在)
|
||||
- ✅ `.github/labels.yml` - 标签定义
|
||||
- ✅ `.github/labeler.yml` - 自动标签规则
|
||||
- ✅ `.github/workflows/pr-checks.yml` - 自动化 PR 检查
|
||||
|
||||
### 3. **自动化**
|
||||
- ✅ 自动 PR 标签
|
||||
- ✅ PR 大小检查
|
||||
- ✅ CI/CD 测试
|
||||
- ✅ 安全扫描
|
||||
- ✅ Commit 信息验证
|
||||
|
||||
---
|
||||
|
||||
## 🔧 设置步骤
|
||||
|
||||
### 步骤 1:同步 GitHub 标签
|
||||
|
||||
创建 `.github/labels.yml` 中定义的标签:
|
||||
|
||||
```bash
|
||||
# 选项 1:使用 gh CLI(推荐)
|
||||
gh label list # 查看当前标签
|
||||
gh label delete <label-name> # 如需要,删除旧标签
|
||||
gh label create "priority: critical" --color "d73a4a" --description "Critical priority"
|
||||
# ... 为 labels.yml 中的所有标签重复
|
||||
|
||||
# 选项 2:使用 GitHub Labeler Action(自动化)
|
||||
# 工作流将在推送时自动同步标签
|
||||
```
|
||||
|
||||
**或使用 GitHub Labeler Action**(添加到 `.github/workflows/sync-labels.yml`):
|
||||
|
||||
```yaml
|
||||
name: Sync Labels
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- '.github/labels.yml'
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: crazy-max/ghaction-github-labeler@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
yaml-file: .github/labels.yml
|
||||
```
|
||||
|
||||
### 步骤 2:启用 GitHub Actions
|
||||
|
||||
1. 前往 **Settings → Actions → General**
|
||||
2. 启用 **"Allow all actions and reusable workflows"**
|
||||
3. 设置 **Workflow permissions** 为 **"Read and write permissions"**
|
||||
4. 勾选 **"Allow GitHub Actions to create and approve pull requests"**
|
||||
|
||||
### 步骤 3:设置分支保护规则
|
||||
|
||||
**对于 `main` 分支:**
|
||||
|
||||
1. 前往 **Settings → Branches → Add rule**
|
||||
2. 分支名称模式:`main`
|
||||
3. 配置:
|
||||
- ✅ Require a pull request before merging
|
||||
- ✅ Require approvals: **1**
|
||||
- ✅ Require status checks to pass before merging
|
||||
- 选择:`Backend Tests (Go)`
|
||||
- 选择:`Frontend Tests (React/TypeScript)`
|
||||
- 选择:`Security Scan`
|
||||
- ✅ Require conversation resolution before merging
|
||||
- ✅ Do not allow bypassing the above settings
|
||||
- ❌ Allow force pushes(禁用)
|
||||
- ❌ Allow deletions(禁用)
|
||||
|
||||
**对于 `dev` 分支:**
|
||||
|
||||
1. 与上面相同,但:
|
||||
- Require approvals: **1**
|
||||
- 宽松一些(如需要允许维护者绕过)
|
||||
|
||||
### 步骤 4:创建 GitHub Projects
|
||||
|
||||
1. 前往 **Projects → New project**
|
||||
2. 创建 **"NOFX Development"** 看板
|
||||
- 模板:Board
|
||||
- 添加列:`Backlog`、`Triaged`、`In Progress`、`In Review`、`Done`
|
||||
- 添加视图:Sprint、Roadmap、By Area、Priority
|
||||
|
||||
3. 创建 **"Bounty Program"** 看板
|
||||
- 模板:Board
|
||||
- 添加列:`Available`、`Claimed`、`In Progress`、`Under Review`、`Paid`
|
||||
|
||||
### 步骤 5:启用 Discussions(可选但推荐)
|
||||
|
||||
1. 前往 **Settings → General → Features**
|
||||
2. 启用 **"Discussions"**
|
||||
3. 创建分类:
|
||||
- 💬 **General** - 一般讨论
|
||||
- 💡 **Ideas** - 功能想法和建议
|
||||
- 🙏 **Q&A** - 问答
|
||||
- 📢 **Announcements** - 重要更新
|
||||
- 🗳️ **Polls** - 社区投票
|
||||
|
||||
### 步骤 6:配置 Issue 模板
|
||||
|
||||
模板已存在于 `.github/ISSUE_TEMPLATE/` 中。验证它们是否正常工作:
|
||||
|
||||
1. 前往 **Issues → New issue**
|
||||
2. 你应该看到:
|
||||
- 🐛 Bug Report
|
||||
- ✨ Feature Request
|
||||
- 💰 Bounty Claim
|
||||
|
||||
如果没有显示,检查文件是否为正确格式的 YAML 和 frontmatter。
|
||||
|
||||
### 步骤 7:设置 Code Owners(可选)
|
||||
|
||||
创建 `.github/CODEOWNERS`:
|
||||
|
||||
```
|
||||
# 全局所有者
|
||||
* @tinkle @zack
|
||||
|
||||
# 前端
|
||||
/web/ @frontend-lead
|
||||
|
||||
# 交易所集成
|
||||
/internal/exchange/ @exchange-lead
|
||||
|
||||
# AI 组件
|
||||
/internal/ai/ @ai-lead
|
||||
|
||||
# 文档
|
||||
/docs/ @tinkle @zack
|
||||
*.md @tinkle @zack
|
||||
```
|
||||
|
||||
### 步骤 8:配置通知
|
||||
|
||||
**对于维护者:**
|
||||
|
||||
1. 前往 **Settings → Notifications**
|
||||
2. 启用:
|
||||
- ✅ Pull request reviews
|
||||
- ✅ Pull request pushes
|
||||
- ✅ Comments on issues and PRs
|
||||
- ✅ New issues
|
||||
- ✅ Security alerts
|
||||
|
||||
3. 设置电子邮件过滤器来组织通知
|
||||
|
||||
**对于仓库:**
|
||||
|
||||
1. 前往 **Settings → Webhooks**(如果与 Slack/Discord 集成)
|
||||
2. 添加通知 webhook
|
||||
|
||||
---
|
||||
|
||||
## 📋 设置后检查清单
|
||||
|
||||
设置后,验证:
|
||||
|
||||
- [ ] 标签已创建并可见
|
||||
- [ ] 分支保护规则已激活
|
||||
- [ ] GitHub Actions 工作流在新 PR 上运行
|
||||
- [ ] 自动标签工作(创建测试 PR)
|
||||
- [ ] 创建 PR 时显示 PR 模板
|
||||
- [ ] 创建 issue 时显示 issue 模板
|
||||
- [ ] Projects 看板可访问
|
||||
- [ ] CONTRIBUTING.md 在 README 中链接
|
||||
|
||||
---
|
||||
|
||||
## 🎯 如何使用系统
|
||||
|
||||
### 对于贡献者
|
||||
|
||||
1. **阅读** [CONTRIBUTING.md](../../../CONTRIBUTING.md)
|
||||
2. **查看** [路线图](../../roadmap/README.zh-CN.md)了解优先级
|
||||
3. **开启 issue** 或找到现有的
|
||||
4. **使用模板创建 PR**
|
||||
5. **处理审核反馈**
|
||||
6. **庆祝** 当合并时!🎉
|
||||
|
||||
### 对于维护者
|
||||
|
||||
1. **每日:** 分类新 issue/PR(15分钟)
|
||||
2. **每日:** 审查分配的 PR
|
||||
3. **每周:** Sprint 计划(周一)和回顾(周五)
|
||||
4. **遵循:** [PR 审核指南](PR_REVIEW_GUIDE.zh-CN.md)
|
||||
5. **遵循:** [项目管理指南](PROJECT_MANAGEMENT.zh-CN.md)
|
||||
|
||||
### 对于悬赏猎人
|
||||
|
||||
1. **查看** 带有 `bounty` 标签的悬赏 issue
|
||||
2. **通过评论认领** issue
|
||||
3. **在截止日期前完成**
|
||||
4. **提交 PR** 并填写悬赏认领部分
|
||||
5. **合并后获得报酬**
|
||||
|
||||
---
|
||||
|
||||
## 🔍 测试系统
|
||||
|
||||
### 测试 1:创建测试 PR
|
||||
|
||||
```bash
|
||||
# 创建测试分支
|
||||
git checkout -b test/pr-system-check
|
||||
|
||||
# 进行小改动
|
||||
echo "# Test" >> TEST.md
|
||||
|
||||
# 提交并推送
|
||||
git add TEST.md
|
||||
git commit -m "test: verify PR automation system"
|
||||
git push origin test/pr-system-check
|
||||
|
||||
# 在 GitHub 上创建 PR
|
||||
# 验证:
|
||||
# - PR 模板加载
|
||||
# - 应用了自动标签
|
||||
# - CI 检查运行
|
||||
# - 添加了大小标签
|
||||
```
|
||||
|
||||
### 测试 2:创建测试 Issue
|
||||
|
||||
1. 前往 **Issues → New issue**
|
||||
2. 选择 **Bug Report**
|
||||
3. 填写模板
|
||||
4. 提交
|
||||
5. 验证:
|
||||
- 模板正确渲染
|
||||
- Issue 可以被标签
|
||||
- Issue 出现在项目看板中
|
||||
|
||||
### 测试 3:测试自动标签
|
||||
|
||||
创建改动不同区域文件的 PR:
|
||||
|
||||
```bash
|
||||
# 测试 1:前端变更
|
||||
git checkout -b test/frontend-label
|
||||
touch web/src/test.tsx
|
||||
git add . && git commit -m "test: frontend labeling"
|
||||
git push origin test/frontend-label
|
||||
# 应该得到 "area: frontend" 标签
|
||||
|
||||
# 测试 2:后端变更
|
||||
git checkout -b test/backend-label
|
||||
touch internal/test.go
|
||||
git add . && git commit -m "test: backend labeling"
|
||||
git push origin test/backend-label
|
||||
# 应该得到 "area: backend" 标签
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 故障排除
|
||||
|
||||
### 问题:标签未同步
|
||||
|
||||
**解决方案:**
|
||||
```bash
|
||||
# 首先删除所有现有标签
|
||||
gh label list --json name --jq '.[].name' | xargs -I {} gh label delete "{}" --yes
|
||||
|
||||
# 然后从 labels.yml 手动创建或通过 action 创建
|
||||
```
|
||||
|
||||
### 问题:GitHub Actions 未运行
|
||||
|
||||
**检查:**
|
||||
1. 仓库设置中启用了 Actions
|
||||
2. 工作流文件在 `.github/workflows/` 中
|
||||
3. YAML 语法有效
|
||||
4. 权限设置正确
|
||||
|
||||
**调试:**
|
||||
```bash
|
||||
# 本地验证工作流
|
||||
act pull_request # 使用 'act' 工具
|
||||
```
|
||||
|
||||
### 问题:分支保护阻止 PR
|
||||
|
||||
**检查:**
|
||||
1. 必需的检查在工作流中定义
|
||||
2. 检查名称完全匹配
|
||||
3. 检查正在完成(没有卡住)
|
||||
|
||||
**临时修复:**
|
||||
- 维护者可以在紧急情况下绕过
|
||||
- 如果太严格,调整保护规则
|
||||
|
||||
### 问题:自动标签器不工作
|
||||
|
||||
**检查:**
|
||||
1. `.github/labeler.yml` 存在且为有效 YAML
|
||||
2. labeler.yml 中定义的标签在仓库中存在
|
||||
3. 工作流有 `pull-requests: write` 权限
|
||||
|
||||
---
|
||||
|
||||
## 📊 监控和维护
|
||||
|
||||
### 每周回顾
|
||||
|
||||
每周检查这些指标:
|
||||
|
||||
```bash
|
||||
# 使用 gh CLI
|
||||
gh pr list --state all --json number,createdAt,closedAt
|
||||
gh issue list --state all --json number,createdAt,closedAt
|
||||
|
||||
# 或使用 GitHub Insights
|
||||
# Repository → Insights → Pulse, Contributors, Traffic
|
||||
```
|
||||
|
||||
### 每月维护
|
||||
|
||||
- [ ] 如需要审查和更新标签
|
||||
- [ ] 检查工作流中的过期依赖
|
||||
- [ ] 如果流程变更更新 CONTRIBUTING.md
|
||||
- [ ] 审查自动化效果
|
||||
- [ ] 收集社区反馈
|
||||
|
||||
---
|
||||
|
||||
## 🎓 培训资源
|
||||
|
||||
### 对于新贡献者
|
||||
|
||||
- [首次贡献指南](https://github.com/firstcontributions/first-contributions)
|
||||
- [如何写 Git Commit 信息](https://chris.beams.io/posts/git-commit/)
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
|
||||
### 对于维护者
|
||||
|
||||
- [代码审核的艺术](https://google.github.io/eng-practices/review/)
|
||||
- [GitHub 项目管理](https://docs.github.com/en/issues/planning-and-tracking-with-projects)
|
||||
- [维护者社区](https://maintainers.github.com/)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 一切就绪!
|
||||
|
||||
PR 管理系统现在已准备好:
|
||||
|
||||
✅ 用清晰的指南引导贡献者
|
||||
✅ 自动化重复任务
|
||||
✅ 保持代码质量
|
||||
✅ 系统性地跟踪进度
|
||||
✅ 扩展社区
|
||||
|
||||
**有问题?** 在维护者频道联系我们或开启讨论。
|
||||
|
||||
**让我们构建令人惊叹的社区!🚀**
|
||||
292
docs/roadmap/README.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# 🗺️ NOFX Roadmap
|
||||
|
||||
**Language:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
Strategic plan for NOFX development and universal market expansion.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
NOFX is on a mission to become the **Universal AI Trading Operating System** for all financial markets. Our proven infrastructure on crypto markets is being extended to stocks, futures, options, forex, and beyond.
|
||||
|
||||
**Vision:** Same architecture. Same agent framework. All markets.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Short-Term Roadmap
|
||||
|
||||
### Phase 1: Core Infrastructure Enhancement
|
||||
|
||||
#### 1.1 Security Enhancements
|
||||
**Goal:** Protect sensitive data and reduce security vulnerabilities
|
||||
|
||||
- **Credential Management**
|
||||
- [ ] Implement AES-256 encryption for API keys in database
|
||||
- [ ] Add encryption for private keys (Hyperliquid, Aster)
|
||||
- [ ] Use hardware security module (HSM) support for production
|
||||
- [ ] Implement key rotation mechanism
|
||||
- [ ] Add audit logging for all credential access
|
||||
|
||||
- **Application Security**
|
||||
- [ ] Input validation and sanitization (prevent SQL injection, XSS)
|
||||
- [ ] Rate limiting for API endpoints
|
||||
- [ ] CORS policy configuration
|
||||
- [ ] JWT token expiration and refresh mechanism
|
||||
- [ ] Implement RBAC (Role-Based Access Control) for multi-user support
|
||||
- [ ] Add IP whitelisting for API access
|
||||
- [ ] Security headers (CSP, HSTS, X-Frame-Options)
|
||||
|
||||
- **Operational Security**
|
||||
- [ ] Secure password hashing (bcrypt with salt)
|
||||
- [ ] 2FA enhancement (backup codes, multiple TOTP devices)
|
||||
- [ ] Session management (auto-logout, concurrent session limits)
|
||||
- [ ] Secrets management (environment variables, vault integration)
|
||||
- [ ] Regular dependency vulnerability scanning
|
||||
|
||||
#### 1.2 Enhanced AI Capabilities
|
||||
**Goal:** Richer prompts, flexible configuration, support for more AI models
|
||||
|
||||
- **Prompt System Overhaul**
|
||||
- [ ] Template engine for dynamic prompt generation
|
||||
- [ ] Multi-language prompt support (chain-of-thought, few-shot, zero-shot)
|
||||
- [ ] Market condition-based prompt switching (bull, bear, sideways)
|
||||
- [ ] Historical performance feedback integration in prompts
|
||||
- [ ] Prompt versioning and A/B testing framework
|
||||
- [ ] User-customizable prompt templates via web interface
|
||||
|
||||
- **AI Model Integration**
|
||||
- [ ] OpenAI GPT-4/GPT-4 Turbo support
|
||||
- [ ] Anthropic Claude 3 (Opus, Sonnet, Haiku) integration
|
||||
- [ ] Google Gemini Pro support
|
||||
- [ ] Local LLM support (Llama, Mistral via Ollama)
|
||||
- [ ] Multi-model ensemble (voting, weighted average)
|
||||
- [ ] Model performance tracking and auto-selection
|
||||
- [ ] Fallback mechanism when primary model fails
|
||||
|
||||
- **AI Decision Engine**
|
||||
- [ ] Confidence scoring for each decision
|
||||
- [ ] Explanation generation (why this trade?)
|
||||
- [ ] Risk assessment integration in AI reasoning
|
||||
- [ ] Market regime detection (trend, mean-reversion, high volatility)
|
||||
- [ ] Cross-validation with technical indicators
|
||||
|
||||
#### 1.3 Exchange Integration Expansion
|
||||
**Goal:** Support more CEX and popular perp-DEX, both spot and futures
|
||||
|
||||
- **Centralized Exchanges (CEX)**
|
||||
- [ ] **OKX** - Futures + Spot trading
|
||||
- [ ] **Bybit** - Futures + Spot trading
|
||||
- [ ] **Bitget** - Futures + Spot trading
|
||||
- [ ] **Gate.io** - Futures + Spot trading
|
||||
- [ ] **KuCoin** - Futures + Spot trading
|
||||
- [ ] Unified CEX interface for easy addition of new exchanges
|
||||
|
||||
- **Decentralized Perpetual Exchanges (Perp-DEX)**
|
||||
- [x] **Hyperliquid** (Ethereum L1) - High-performance orderbook DEX (✅ Supported)
|
||||
- [x] **Aster** (Multi-chain) - Binance-compatible API DEX (✅ Supported)
|
||||
- [ ] **Lighter** (Arbitrum) - Gasless orderbook DEX with off-chain matching
|
||||
- [ ] **EdgeX** (Multi-chain) - Professional derivatives DEX
|
||||
- [ ] Unified DEX interface for consistent integration
|
||||
- [ ] Enhanced Hyperliquid integration (testnet support, advanced order types)
|
||||
- [ ] Enhanced Aster integration (cross-chain support, wallet management)
|
||||
|
||||
- **Spot + Futures Support**
|
||||
- [ ] Dual-mode trading (spot arbitrage, futures hedging)
|
||||
- [ ] Cross-exchange arbitrage detection
|
||||
- [ ] Unified position tracking across spot and futures
|
||||
- [ ] Auto-conversion between spot and perpetual strategies
|
||||
|
||||
- **Exchange Infrastructure**
|
||||
- [ ] **Trading Data Analysis API Integration** (In-house developed)
|
||||
- [ ] AI500 integration - In-house AI-powered coin selection model
|
||||
- [ ] OI (Open Interest) Analysis - Real-time open interest tracking and anomaly detection
|
||||
- [ ] NetFlow Analysis - On-chain fund flow analysis for market sentiment
|
||||
- [ ] Market sentiment aggregator - Combine multiple data sources for enhanced AI decision making
|
||||
- [ ] Custom indicator API - Support for proprietary technical indicators
|
||||
- [ ] Automatic precision handling (quantity, price decimals)
|
||||
- [ ] Order type abstraction (market, limit, stop-loss, take-profit)
|
||||
- [ ] Unified error handling and retry logic
|
||||
- [ ] WebSocket support for real-time data
|
||||
- [ ] Rate limit management per exchange
|
||||
|
||||
#### 1.4 Project Structure Refactoring
|
||||
**Goal:** Clear hierarchy, high cohesion, low coupling, easy to extend and maintain
|
||||
|
||||
- **Architecture Redesign**
|
||||
- [ ] Implement layered architecture (Presentation → Business Logic → Data Access)
|
||||
- [ ] Apply SOLID principles (especially Liskov Substitution Principle for exchange adapters)
|
||||
- [ ] Extract common interfaces for all exchange implementations
|
||||
- [ ] Separate concerns: trading logic, data fetching, decision making, execution
|
||||
- [ ] Implement dependency injection for better testability
|
||||
|
||||
- **Code Organization**
|
||||
- [ ] Refactor monolithic modules into smaller, focused packages
|
||||
- [ ] Create abstract base classes for traders, exchanges, AI models
|
||||
- [ ] Implement factory pattern for exchange/AI model creation
|
||||
- [ ] Standardize error handling and logging across all modules
|
||||
- [ ] Remove circular dependencies and improve import structure
|
||||
|
||||
- **Configuration Management**
|
||||
- [ ] Centralize all configuration in structured config files
|
||||
- [ ] Implement hot-reload for non-critical configuration changes
|
||||
- [ ] Validate configurations at startup with clear error messages
|
||||
- [ ] Support environment-specific configs (dev/staging/production)
|
||||
|
||||
#### 1.5 User Experience Improvements
|
||||
**Goal:** Enhanced web interface, better monitoring, and alerting system
|
||||
|
||||
- **Web Interface Enhancements**
|
||||
- [ ] Mobile-responsive design (tablet and phone support)
|
||||
- [ ] Dark/Light theme toggle with user preference saving
|
||||
- [ ] Advanced charting with TradingView widget integration
|
||||
- [ ] Real-time WebSocket updates (replace polling for positions/orders)
|
||||
- [ ] Drag-and-drop dashboard customization
|
||||
- [ ] Multi-language support (EN, CN, RU, UK)
|
||||
|
||||
- **Configuration Interface**
|
||||
- [ ] Visual strategy builder (no-code flow diagram)
|
||||
- [ ] Live configuration preview before saving
|
||||
- [ ] Configuration templates for common strategies
|
||||
- [ ] Bulk trader management (start/stop multiple traders)
|
||||
- [ ] Exchange credential testing (verify before saving)
|
||||
- [ ] AI model testing interface (test prompts before deployment)
|
||||
|
||||
- **Monitoring & Analytics**
|
||||
- [ ] Real-time performance dashboard with key metrics
|
||||
- [ ] Equity curve visualization (per trader, per exchange, overall)
|
||||
- [ ] Drawdown analysis and risk metrics
|
||||
- [ ] Trade history with filtering and search
|
||||
- [ ] P&L breakdown by symbol, time period, strategy
|
||||
- [ ] Comparison view (multiple traders side-by-side)
|
||||
- [ ] Export functionality (CSV, JSON, PDF reports)
|
||||
|
||||
- **Alert & Notification System**
|
||||
- [ ] Multi-channel alerts (Email, Telegram, Discord, Webhook)
|
||||
- [ ] Configurable alert rules (profit threshold, loss limit, error detection)
|
||||
- [ ] Alert priority levels (critical, warning, info)
|
||||
- [ ] Alert history and acknowledgment tracking
|
||||
- [ ] Daily/Weekly performance summary emails
|
||||
- [ ] System health monitoring (API connectivity, database status)
|
||||
|
||||
### Phase 2: Testing & Stability
|
||||
|
||||
#### 2.1 Quality Assurance
|
||||
- [ ] Comprehensive unit test coverage (>80%)
|
||||
- [ ] Integration tests for all exchange adapters
|
||||
- [ ] Load testing (100+ concurrent traders)
|
||||
- [ ] Security audit (API key encryption, SQL injection prevention)
|
||||
|
||||
#### 2.2 Documentation
|
||||
- [ ] Complete API reference documentation
|
||||
- [ ] Video tutorials for beginners
|
||||
- [ ] Strategy development guide
|
||||
- [ ] Troubleshooting playbook
|
||||
|
||||
#### 2.3 Community Features
|
||||
- [ ] Public strategy marketplace (share/sell strategies)
|
||||
- [ ] Leaderboard with verified performance
|
||||
- [ ] Community forum integration
|
||||
- [ ] Bug bounty program
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Long-Term Roadmap
|
||||
|
||||
### Phase 3: Universal Market Expansion
|
||||
|
||||
**Goal:** Extend the proven crypto trading infrastructure to all major financial markets.
|
||||
|
||||
#### 3.1 Stock Markets
|
||||
- [ ] US Equities (Interactive Brokers, Alpaca Markets)
|
||||
- [ ] Asian Markets (A-shares, Hong Kong, Japan)
|
||||
- [ ] Fundamental analysis integration (earnings, P/E, dividends)
|
||||
- [ ] AI-powered stock screening
|
||||
|
||||
#### 3.2 Futures Markets
|
||||
- [ ] Commodity Futures (Energy, Metals, Agriculture)
|
||||
- [ ] Index Futures (S&P 500, NASDAQ, Dow Jones, VIX)
|
||||
- [ ] Rollover management and spread trading
|
||||
|
||||
#### 3.3 Options Trading
|
||||
- [ ] Options chain data and Greeks calculation
|
||||
- [ ] Equity, Index, and Crypto options
|
||||
- [ ] Options strategy builder
|
||||
|
||||
#### 3.4 Forex Markets
|
||||
- [ ] Major currency pairs and exotic pairs
|
||||
- [ ] Interest rate analysis and carry trade support
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Advanced AI & Automation
|
||||
|
||||
**Goal:** Implement cutting-edge AI technologies for autonomous trading.
|
||||
|
||||
- [ ] Multi-Agent orchestration (specialized agents with dynamic coordination)
|
||||
- [ ] Reinforcement Learning (DQN, PPO, transfer learning)
|
||||
- [ ] Alternative data integration (social sentiment, news, on-chain analytics)
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Enterprise & Scaling
|
||||
|
||||
**Goal:** Scale infrastructure for institutional use and high-volume trading.
|
||||
|
||||
- [ ] Database migration (PostgreSQL/MySQL, Redis, TimescaleDB)
|
||||
- [ ] Microservices architecture with Kubernetes deployment
|
||||
- [ ] Multi-user RBAC and white-label solutions
|
||||
- [ ] Advanced analytics and compliance reporting
|
||||
|
||||
---
|
||||
|
||||
## 📊 Key Metrics & Milestones
|
||||
|
||||
### Short-Term Targets
|
||||
- [ ] **100+** supported trading pairs across all exchanges
|
||||
- [ ] **10,000+** active trader instances
|
||||
- [ ] **5+** new exchange integrations
|
||||
- [ ] **80%+** test coverage
|
||||
- [ ] **99.9%** uptime
|
||||
|
||||
### Long-Term Targets
|
||||
- [ ] **All major asset classes** supported (crypto, stocks, futures, options, forex)
|
||||
- [ ] **50,000+** active users
|
||||
- [ ] **Enterprise tier** launched
|
||||
- [ ] **Institutional partnerships** established
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Community Involvement
|
||||
|
||||
We welcome community contributions to accelerate our roadmap:
|
||||
|
||||
- **Vote on Features**: Join our [Telegram community](https://t.me/nofx_dev_community) to vote on priority features
|
||||
- **Contribute Code**: Check our [Contributing Guide](../../CONTRIBUTING.md)
|
||||
- **Bug Bounties**: Report issues and earn rewards
|
||||
- **Strategy Sharing**: Share your successful strategies
|
||||
|
||||
---
|
||||
|
||||
## 📝 Roadmap Updates
|
||||
|
||||
This roadmap is reviewed and updated quarterly based on:
|
||||
- Community feedback
|
||||
- Market demands
|
||||
- Technical feasibility
|
||||
- Resource availability
|
||||
|
||||
**Last Updated:** 2025-11-01
|
||||
|
||||
---
|
||||
|
||||
## 📚 Related Documentation
|
||||
|
||||
- [Architecture Documentation](../architecture/README.md) - Technical architecture details
|
||||
- [Getting Started](../getting-started/README.md) - Setup and deployment
|
||||
- [Contributing Guide](../../CONTRIBUTING.md) - How to contribute
|
||||
- [Changelog](../../CHANGELOG.md) - Version history
|
||||
|
||||
---
|
||||
|
||||
[← Back to Documentation Home](../README.md)
|
||||
292
docs/roadmap/README.zh-CN.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# 🗺️ NOFX 路线图
|
||||
|
||||
**语言:** [English](README.md) | [中文](README.zh-CN.md)
|
||||
|
||||
NOFX 发展和通用市场扩展的战略规划。
|
||||
|
||||
---
|
||||
|
||||
## 📋 概述
|
||||
|
||||
NOFX 的使命是成为所有金融市场的**通用 AI 交易操作系统**。我们在加密货币市场上经过验证的基础设施正在扩展到股票、期货、期权、外汇等领域。
|
||||
|
||||
**愿景:** 相同架构。相同智能体框架。所有市场。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 短期路线图
|
||||
|
||||
### 阶段1: 核心基础设施增强
|
||||
|
||||
#### 1.1 安全性增强
|
||||
**目标:** 保护敏感数据,减少安全漏洞
|
||||
|
||||
- **凭证管理**
|
||||
- [ ] 为数据库中的API密钥实现AES-256加密
|
||||
- [ ] 为私钥(Hyperliquid、Aster)添加加密
|
||||
- [ ] 为生产环境支持硬件安全模块(HSM)
|
||||
- [ ] 实现密钥轮换机制
|
||||
- [ ] 为所有凭证访问添加审计日志
|
||||
|
||||
- **应用安全**
|
||||
- [ ] 输入验证和清理(防止SQL注入、XSS攻击)
|
||||
- [ ] API端点的速率限制
|
||||
- [ ] CORS策略配置
|
||||
- [ ] JWT令牌过期和刷新机制
|
||||
- [ ] 实现RBAC(基于角色的访问控制)支持多用户
|
||||
- [ ] 添加API访问的IP白名单
|
||||
- [ ] 安全头部(CSP、HSTS、X-Frame-Options)
|
||||
|
||||
- **运营安全**
|
||||
- [ ] 安全密码哈希(bcrypt加盐)
|
||||
- [ ] 2FA增强(备份码、多个TOTP设备)
|
||||
- [ ] 会话管理(自动登出、并发会话限制)
|
||||
- [ ] 密钥管理(环境变量、vault集成)
|
||||
- [ ] 定期依赖项漏洞扫描
|
||||
|
||||
#### 1.2 增强AI能力
|
||||
**目标:** 更丰富的prompts、灵活配置、支持更多AI模型
|
||||
|
||||
- **Prompt系统全面改造**
|
||||
- [ ] 动态prompt生成的模板引擎
|
||||
- [ ] 多语言prompt支持(思维链、few-shot、zero-shot)
|
||||
- [ ] 基于市场状况的prompt切换(牛市、熊市、震荡)
|
||||
- [ ] 在prompts中集成历史绩效反馈
|
||||
- [ ] Prompt版本控制和A/B测试框架
|
||||
- [ ] 通过Web界面自定义prompt模板
|
||||
|
||||
- **AI模型集成**
|
||||
- [ ] OpenAI GPT-4/GPT-4 Turbo支持
|
||||
- [ ] Anthropic Claude 3(Opus、Sonnet、Haiku)集成
|
||||
- [ ] Google Gemini Pro支持
|
||||
- [ ] 本地LLM支持(通过Ollama的Llama、Mistral)
|
||||
- [ ] 多模型集成(投票、加权平均)
|
||||
- [ ] 模型性能跟踪和自动选择
|
||||
- [ ] 主模型失败时的降级机制
|
||||
|
||||
- **AI决策引擎**
|
||||
- [ ] 每个决策的置信度评分
|
||||
- [ ] 解释生成(为什么做这笔交易?)
|
||||
- [ ] AI推理中的风险评估集成
|
||||
- [ ] 市场状态检测(趋势、均值回归、高波动)
|
||||
- [ ] 与技术指标的交叉验证
|
||||
|
||||
#### 1.3 交易所集成扩展
|
||||
**目标:** 支持更多CEX和流行的perp-DEX,现货和合约
|
||||
|
||||
- **中心化交易所(CEX)**
|
||||
- [ ] **OKX** - 合约 + 现货交易
|
||||
- [ ] **Bybit** - 合约 + 现货交易
|
||||
- [ ] **Bitget** - 合约 + 现货交易
|
||||
- [ ] **Gate.io** - 合约 + 现货交易
|
||||
- [ ] **KuCoin** - 合约 + 现货交易
|
||||
- [ ] 统一的CEX接口,便于添加新交易所
|
||||
|
||||
- **去中心化永续交易所(Perp-DEX)**
|
||||
- [x] **Hyperliquid**(Ethereum L1)- 高性能订单簿DEX(✅ 已支持)
|
||||
- [x] **Aster**(多链)- Binance兼容API的DEX(✅ 已支持)
|
||||
- [ ] **Lighter**(Arbitrum)- 无Gas订单簿DEX,链下撮合
|
||||
- [ ] **EdgeX**(多链)- 专业衍生品DEX
|
||||
- [ ] 统一的DEX接口,保证集成一致性
|
||||
- [ ] 增强Hyperliquid集成(测试网支持、高级订单类型)
|
||||
- [ ] 增强Aster集成(跨链支持、钱包管理)
|
||||
|
||||
- **现货 + 合约支持**
|
||||
- [ ] 双模式交易(现货套利、合约对冲)
|
||||
- [ ] 跨交易所套利检测
|
||||
- [ ] 现货和合约的统一持仓跟踪
|
||||
- [ ] 现货和永续策略之间的自动转换
|
||||
|
||||
- **交易所基础设施**
|
||||
- [ ] **交易数据分析API集成**(自研)
|
||||
- [ ] AI500集成 - 自研AI选币模型
|
||||
- [ ] OI(持仓量)分析 - 实时持仓量跟踪和异常检测
|
||||
- [ ] NetFlow分析 - 链上资金流向分析,用于市场情绪判断
|
||||
- [ ] 市场情绪聚合器 - 整合多个数据源,增强AI决策能力
|
||||
- [ ] 自定义指标API - 支持专有技术指标
|
||||
- [ ] 自动精度处理(数量、价格小数位)
|
||||
- [ ] 订单类型抽象(市价、限价、止损、止盈)
|
||||
- [ ] 统一的错误处理和重试逻辑
|
||||
- [ ] 实时数据的WebSocket支持
|
||||
- [ ] 每个交易所的速率限制管理
|
||||
|
||||
#### 1.4 项目结构重构
|
||||
**目标:** 清晰层次、高内聚低耦合、易于扩展和维护
|
||||
|
||||
- **架构重新设计**
|
||||
- [ ] 实现分层架构(表现层 → 业务逻辑层 → 数据访问层)
|
||||
- [ ] 应用SOLID原则(特别是里氏替换原则用于交易所适配器)
|
||||
- [ ] 为所有交易所实现提取通用接口
|
||||
- [ ] 分离关注点:交易逻辑、数据获取、决策制定、执行
|
||||
- [ ] 实现依赖注入以提高可测试性
|
||||
|
||||
- **代码组织**
|
||||
- [ ] 将单体模块重构为更小、更专注的包
|
||||
- [ ] 为traders、exchanges、AI模型创建抽象基类
|
||||
- [ ] 实现工厂模式用于交易所/AI模型的创建
|
||||
- [ ] 标准化所有模块的错误处理和日志记录
|
||||
- [ ] 消除循环依赖并改进导入结构
|
||||
|
||||
- **配置管理**
|
||||
- [ ] 将所有配置集中到结构化配置文件中
|
||||
- [ ] 实现非关键配置的热重载
|
||||
- [ ] 启动时验证配置并提供清晰的错误消息
|
||||
- [ ] 支持环境特定配置(dev/staging/production)
|
||||
|
||||
#### 1.5 用户体验改进
|
||||
**目标:** 增强Web界面、更好的监控和告警系统
|
||||
|
||||
- **Web界面增强**
|
||||
- [ ] 移动端响应式设计(平板和手机支持)
|
||||
- [ ] 深色/浅色主题切换并保存用户偏好
|
||||
- [ ] TradingView小部件集成的高级图表
|
||||
- [ ] 实时WebSocket更新(替代持仓/订单的轮询)
|
||||
- [ ] 拖拽式仪表板自定义
|
||||
- [ ] 多语言支持(EN、CN、RU、UK)
|
||||
|
||||
- **配置界面**
|
||||
- [ ] 可视化策略构建器(无代码流程图)
|
||||
- [ ] 保存前的实时配置预览
|
||||
- [ ] 常用策略的配置模板
|
||||
- [ ] 批量trader管理(启动/停止多个traders)
|
||||
- [ ] 交易所凭证测试(保存前验证)
|
||||
- [ ] AI模型测试界面(部署前测试prompts)
|
||||
|
||||
- **监控与分析**
|
||||
- [ ] 实时性能仪表板和关键指标
|
||||
- [ ] 权益曲线可视化(每个trader、每个交易所、总体)
|
||||
- [ ] 回撤分析和风险指标
|
||||
- [ ] 带过滤和搜索的交易历史
|
||||
- [ ] 按币种、时间段、策略的盈亏分解
|
||||
- [ ] 比较视图(多个traders并排)
|
||||
- [ ] 导出功能(CSV、JSON、PDF报告)
|
||||
|
||||
- **告警与通知系统**
|
||||
- [ ] 多渠道告警(Email、Telegram、Discord、Webhook)
|
||||
- [ ] 可配置的告警规则(利润阈值、亏损限制、错误检测)
|
||||
- [ ] 告警优先级(严重、警告、信息)
|
||||
- [ ] 告警历史和确认跟踪
|
||||
- [ ] 每日/每周性能摘要邮件
|
||||
- [ ] 系统健康监控(API连接、数据库状态)
|
||||
|
||||
### 阶段2: 测试与稳定性
|
||||
|
||||
#### 2.1 质量保证
|
||||
- [ ] 全面的单元测试覆盖率(>80%)
|
||||
- [ ] 所有交易所适配器的集成测试
|
||||
- [ ] 负载测试(100+并发交易者)
|
||||
- [ ] 安全审计(API密钥加密、SQL注入防护)
|
||||
|
||||
#### 2.2 文档
|
||||
- [ ] 完整的API参考文档
|
||||
- [ ] 新手视频教程
|
||||
- [ ] 策略开发指南
|
||||
- [ ] 故障排查手册
|
||||
|
||||
#### 2.3 社区功能
|
||||
- [ ] 公开策略市场(分享/出售策略)
|
||||
- [ ] 经过验证的绩效排行榜
|
||||
- [ ] 社区论坛集成
|
||||
- [ ] 漏洞赏金计划
|
||||
|
||||
---
|
||||
|
||||
## 🚀 长期路线图
|
||||
|
||||
### 阶段3: 通用市场扩展
|
||||
|
||||
**目标:** 将经过验证的加密货币交易基础设施扩展到所有主要金融市场。
|
||||
|
||||
#### 3.1 股票市场
|
||||
- [ ] 美股(Interactive Brokers、Alpaca Markets)
|
||||
- [ ] 亚洲市场(A股、香港、日本)
|
||||
- [ ] 基本面分析集成(财报、市盈率、股息)
|
||||
- [ ] AI驱动的股票筛选
|
||||
|
||||
#### 3.2 期货市场
|
||||
- [ ] 商品期货(能源、金属、农产品)
|
||||
- [ ] 指数期货(标普500、纳斯达克、道琼斯、VIX)
|
||||
- [ ] 展期管理和价差交易
|
||||
|
||||
#### 3.3 期权交易
|
||||
- [ ] 期权链数据和Greeks计算
|
||||
- [ ] 股票、指数和加密期权
|
||||
- [ ] 期权策略构建器
|
||||
|
||||
#### 3.4 外汇市场
|
||||
- [ ] 主要货币对和稀有货币对
|
||||
- [ ] 利率分析和套息交易支持
|
||||
|
||||
---
|
||||
|
||||
### 阶段4: 高级AI与自动化
|
||||
|
||||
**目标:** 实现前沿AI技术用于自主交易。
|
||||
|
||||
- [ ] 多智能体编排(专业化智能体与动态协调)
|
||||
- [ ] 强化学习(DQN、PPO、迁移学习)
|
||||
- [ ] 替代数据集成(社交情绪、新闻、链上分析)
|
||||
|
||||
---
|
||||
|
||||
### 阶段5: 企业级与扩展
|
||||
|
||||
**目标:** 扩展基础设施以支持机构使用和高频交易。
|
||||
|
||||
- [ ] 数据库迁移(PostgreSQL/MySQL、Redis、TimescaleDB)
|
||||
- [ ] 微服务架构与Kubernetes部署
|
||||
- [ ] 多用户RBAC和白标解决方案
|
||||
- [ ] 高级分析和合规报告
|
||||
|
||||
---
|
||||
|
||||
## 📊 关键指标与里程碑
|
||||
|
||||
### 短期目标
|
||||
- [ ] 所有交易所支持**100+**交易对
|
||||
- [ ] **10,000+**活跃交易者实例
|
||||
- [ ] **5+**新交易所集成
|
||||
- [ ] **80%+**测试覆盖率
|
||||
- [ ] **99.9%**正常运行时间
|
||||
|
||||
### 长期目标
|
||||
- [ ] 支持**所有主要资产类别**(加密、股票、期货、期权、外汇)
|
||||
- [ ] **50,000+**活跃用户
|
||||
- [ ] **企业级**版本发布
|
||||
- [ ] 建立**机构合作伙伴关系**
|
||||
|
||||
---
|
||||
|
||||
## 🤝 社区参与
|
||||
|
||||
我们欢迎社区贡献来加速我们的路线图:
|
||||
|
||||
- **功能投票**: 加入我们的[Telegram社区](https://t.me/nofx_dev_community)投票优先功能
|
||||
- **贡献代码**: 查看我们的[贡献指南](../../CONTRIBUTING.md)
|
||||
- **漏洞赏金**: 报告问题并获得奖励
|
||||
- **策略分享**: 分享你的成功策略
|
||||
|
||||
---
|
||||
|
||||
## 📝 路线图更新
|
||||
|
||||
本路线图根据以下因素每季度审查和更新:
|
||||
- 社区反馈
|
||||
- 市场需求
|
||||
- 技术可行性
|
||||
- 资源可用性
|
||||
|
||||
**最后更新:** 2025-11-01
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [架构文档](../architecture/README.zh-CN.md) - 技术架构详情
|
||||
- [快速开始](../getting-started/README.zh-CN.md) - 设置和部署
|
||||
- [贡献指南](../../CONTRIBUTING.md) - 如何贡献
|
||||
- [更新日志](../../CHANGELOG.zh-CN.md) - 版本历史
|
||||
|
||||
---
|
||||
|
||||
[← 返回文档主页](../README.md)
|
||||
16
go.mod
@@ -6,13 +6,20 @@ require (
|
||||
github.com/adshao/go-binance/v2 v2.8.7
|
||||
github.com/ethereum/go-ethereum v1.16.5
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/sonirico/go-hyperliquid v0.17.0
|
||||
golang.org/x/crypto v0.42.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.0 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
@@ -20,6 +27,7 @@ require (
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elastic/go-sysinfo v1.15.4 // indirect
|
||||
github.com/elastic/go-windows v1.0.2 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
|
||||
@@ -31,8 +39,6 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/holiman/uint256 v1.3.2 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -50,6 +56,7 @@ require (
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sonirico/vago v0.9.0 // indirect
|
||||
@@ -65,7 +72,6 @@ require (
|
||||
go.elastic.co/fastjson v1.5.1 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
@@ -74,4 +80,8 @@ require (
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
howett.net/plist v1.0.1 // indirect
|
||||
modernc.org/libc v1.37.6 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/sqlite v1.28.0 // indirect
|
||||
)
|
||||
|
||||
8
go.sum
@@ -10,6 +10,8 @@ github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJiz
|
||||
github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
@@ -67,6 +69,8 @@ github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7Lk
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
@@ -114,6 +118,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
@@ -130,6 +136,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
type DecisionRecord struct {
|
||||
Timestamp time.Time `json:"timestamp"` // 决策时间
|
||||
CycleNumber int `json:"cycle_number"` // 周期编号
|
||||
SystemPrompt string `json:"system_prompt"` // 系统提示词(发送给AI的系统prompt)
|
||||
InputPrompt string `json:"input_prompt"` // 发送给AI的输入prompt
|
||||
CoTTrace string `json:"cot_trace"` // AI思维链(输出)
|
||||
DecisionJSON string `json:"decision_json"` // 决策JSON
|
||||
|
||||
260
main.go
@@ -1,107 +1,242 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"nofx/api"
|
||||
"nofx/auth"
|
||||
"nofx/config"
|
||||
"nofx/manager"
|
||||
"nofx/market"
|
||||
"nofx/pool"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// LeverageConfig 杠杆配置
|
||||
type LeverageConfig struct {
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||
}
|
||||
|
||||
// ConfigFile 配置文件结构,只包含需要同步到数据库的字段
|
||||
type ConfigFile struct {
|
||||
AdminMode bool `json:"admin_mode"`
|
||||
APIServerPort int `json:"api_server_port"`
|
||||
UseDefaultCoins bool `json:"use_default_coins"`
|
||||
DefaultCoins []string `json:"default_coins"`
|
||||
CoinPoolAPIURL string `json:"coin_pool_api_url"`
|
||||
OITopAPIURL string `json:"oi_top_api_url"`
|
||||
InsideCoins bool `json:"inside_coins"`
|
||||
MaxDailyLoss float64 `json:"max_daily_loss"`
|
||||
MaxDrawdown float64 `json:"max_drawdown"`
|
||||
StopTradingMinutes int `json:"stop_trading_minutes"`
|
||||
Leverage LeverageConfig `json:"leverage"`
|
||||
JWTSecret string `json:"jwt_secret"`
|
||||
DataKLineTime string `json:"data_k_line_time"`
|
||||
}
|
||||
|
||||
// syncConfigToDatabase 从config.json读取配置并同步到数据库
|
||||
func syncConfigToDatabase(database *config.Database) error {
|
||||
// 检查config.json是否存在
|
||||
if _, err := os.Stat("config.json"); os.IsNotExist(err) {
|
||||
log.Printf("📄 config.json不存在,跳过同步")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 读取config.json
|
||||
data, err := os.ReadFile("config.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取config.json失败: %w", err)
|
||||
}
|
||||
|
||||
// 解析JSON
|
||||
var configFile ConfigFile
|
||||
if err := json.Unmarshal(data, &configFile); err != nil {
|
||||
return fmt.Errorf("解析config.json失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("🔄 开始同步config.json到数据库...")
|
||||
|
||||
// 同步各配置项到数据库
|
||||
configs := map[string]string{
|
||||
"admin_mode": fmt.Sprintf("%t", configFile.AdminMode),
|
||||
"api_server_port": strconv.Itoa(configFile.APIServerPort),
|
||||
"use_default_coins": fmt.Sprintf("%t", configFile.UseDefaultCoins),
|
||||
"coin_pool_api_url": configFile.CoinPoolAPIURL,
|
||||
"oi_top_api_url": configFile.OITopAPIURL,
|
||||
"inside_coins": fmt.Sprintf("%t", configFile.InsideCoins),
|
||||
"max_daily_loss": fmt.Sprintf("%.1f", configFile.MaxDailyLoss),
|
||||
"max_drawdown": fmt.Sprintf("%.1f", configFile.MaxDrawdown),
|
||||
"stop_trading_minutes": strconv.Itoa(configFile.StopTradingMinutes),
|
||||
}
|
||||
|
||||
// 同步default_coins(转换为JSON字符串存储)
|
||||
if len(configFile.DefaultCoins) > 0 {
|
||||
defaultCoinsJSON, err := json.Marshal(configFile.DefaultCoins)
|
||||
if err == nil {
|
||||
configs["default_coins"] = string(defaultCoinsJSON)
|
||||
}
|
||||
}
|
||||
|
||||
// 同步杠杆配置
|
||||
if configFile.Leverage.BTCETHLeverage > 0 {
|
||||
configs["btc_eth_leverage"] = strconv.Itoa(configFile.Leverage.BTCETHLeverage)
|
||||
}
|
||||
if configFile.Leverage.AltcoinLeverage > 0 {
|
||||
configs["altcoin_leverage"] = strconv.Itoa(configFile.Leverage.AltcoinLeverage)
|
||||
}
|
||||
|
||||
// 如果JWT密钥不为空,也同步
|
||||
if configFile.JWTSecret != "" {
|
||||
configs["jwt_secret"] = configFile.JWTSecret
|
||||
}
|
||||
|
||||
// 更新数据库配置
|
||||
for key, value := range configs {
|
||||
if err := database.SetSystemConfig(key, value); err != nil {
|
||||
log.Printf("⚠️ 更新配置 %s 失败: %v", key, err)
|
||||
} else {
|
||||
log.Printf("✓ 同步配置: %s = %s", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("✅ config.json同步完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("╔════════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ 🏆 AI模型交易竞赛系统 - Qwen vs DeepSeek ║")
|
||||
fmt.Println("║ 🤖 AI多模型交易系统 - 支持 DeepSeek & Qwen ║")
|
||||
fmt.Println("╚════════════════════════════════════════════════════════════╝")
|
||||
fmt.Println()
|
||||
|
||||
// 加载配置文件
|
||||
configFile := "config.json"
|
||||
// 初始化数据库配置
|
||||
dbPath := "config.db"
|
||||
if len(os.Args) > 1 {
|
||||
configFile = os.Args[1]
|
||||
dbPath = os.Args[1]
|
||||
}
|
||||
|
||||
log.Printf("📋 加载配置文件: %s", configFile)
|
||||
cfg, err := config.LoadConfig(configFile)
|
||||
log.Printf("📋 初始化配置数据库: %s", dbPath)
|
||||
database, err := config.NewDatabase(dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 加载配置失败: %v", err)
|
||||
log.Fatalf("❌ 初始化数据库失败: %v", err)
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
// 同步config.json到数据库
|
||||
if err := syncConfigToDatabase(database); err != nil {
|
||||
log.Printf("⚠️ 同步config.json到数据库失败: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("✓ 配置加载成功,共%d个trader参赛", len(cfg.Traders))
|
||||
// 获取系统配置
|
||||
useDefaultCoinsStr, _ := database.GetSystemConfig("use_default_coins")
|
||||
useDefaultCoins := useDefaultCoinsStr == "true"
|
||||
apiPortStr, _ := database.GetSystemConfig("api_server_port")
|
||||
|
||||
// 获取管理员模式配置
|
||||
adminModeStr, _ := database.GetSystemConfig("admin_mode")
|
||||
adminMode := adminModeStr != "false" // 默认为true
|
||||
|
||||
// 设置JWT密钥
|
||||
jwtSecret, _ := database.GetSystemConfig("jwt_secret")
|
||||
if jwtSecret == "" {
|
||||
jwtSecret = "your-jwt-secret-key-change-in-production-make-it-long-and-random"
|
||||
log.Printf("⚠️ 使用默认JWT密钥,建议在生产环境中配置")
|
||||
}
|
||||
auth.SetJWTSecret(jwtSecret)
|
||||
|
||||
// 在管理员模式下,确保admin用户存在
|
||||
if adminMode {
|
||||
err := database.EnsureAdminUser()
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 创建admin用户失败: %v", err)
|
||||
} else {
|
||||
log.Printf("✓ 管理员模式已启用,无需登录")
|
||||
}
|
||||
auth.SetAdminMode(true)
|
||||
}
|
||||
|
||||
log.Printf("✓ 配置数据库初始化成功")
|
||||
fmt.Println()
|
||||
|
||||
// 设置默认主流币种列表
|
||||
pool.SetDefaultCoins(cfg.DefaultCoins)
|
||||
// 从数据库读取默认主流币种列表
|
||||
defaultCoinsJSON, _ := database.GetSystemConfig("default_coins")
|
||||
var defaultCoins []string
|
||||
|
||||
if defaultCoinsJSON != "" {
|
||||
// 尝试从JSON解析
|
||||
if err := json.Unmarshal([]byte(defaultCoinsJSON), &defaultCoins); err != nil {
|
||||
log.Printf("⚠️ 解析default_coins配置失败: %v,使用硬编码默认值", err)
|
||||
defaultCoins = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "HYPEUSDT"}
|
||||
} else {
|
||||
log.Printf("✓ 从数据库加载默认币种列表(共%d个): %v", len(defaultCoins), defaultCoins)
|
||||
}
|
||||
} else {
|
||||
// 如果数据库中没有配置,使用硬编码默认值
|
||||
defaultCoins = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "HYPEUSDT"}
|
||||
log.Printf("⚠️ 数据库中未配置default_coins,使用硬编码默认值")
|
||||
}
|
||||
|
||||
pool.SetDefaultCoins(defaultCoins)
|
||||
// 设置是否使用默认主流币种
|
||||
pool.SetUseDefaultCoins(cfg.UseDefaultCoins)
|
||||
if cfg.UseDefaultCoins {
|
||||
log.Printf("✓ 已启用默认主流币种列表(共%d个币种): %v", len(cfg.DefaultCoins), cfg.DefaultCoins)
|
||||
pool.SetUseDefaultCoins(useDefaultCoins)
|
||||
if useDefaultCoins {
|
||||
log.Printf("✓ 已启用默认主流币种列表")
|
||||
}
|
||||
|
||||
// 设置币种池API URL
|
||||
if cfg.CoinPoolAPIURL != "" {
|
||||
pool.SetCoinPoolAPI(cfg.CoinPoolAPIURL)
|
||||
coinPoolAPIURL, _ := database.GetSystemConfig("coin_pool_api_url")
|
||||
if coinPoolAPIURL != "" {
|
||||
pool.SetCoinPoolAPI(coinPoolAPIURL)
|
||||
log.Printf("✓ 已配置AI500币种池API")
|
||||
}
|
||||
if cfg.OITopAPIURL != "" {
|
||||
pool.SetOITopAPI(cfg.OITopAPIURL)
|
||||
|
||||
oiTopAPIURL, _ := database.GetSystemConfig("oi_top_api_url")
|
||||
if oiTopAPIURL != "" {
|
||||
pool.SetOITopAPI(oiTopAPIURL)
|
||||
log.Printf("✓ 已配置OI Top API")
|
||||
}
|
||||
|
||||
// 创建TraderManager
|
||||
traderManager := manager.NewTraderManager()
|
||||
|
||||
// 添加所有启用的trader
|
||||
enabledCount := 0
|
||||
for i, traderCfg := range cfg.Traders {
|
||||
// 跳过未启用的trader
|
||||
if !traderCfg.Enabled {
|
||||
log.Printf("⏭️ [%d/%d] 跳过未启用的 %s", i+1, len(cfg.Traders), traderCfg.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
enabledCount++
|
||||
log.Printf("📦 [%d/%d] 初始化 %s (%s模型)...",
|
||||
i+1, len(cfg.Traders), traderCfg.Name, strings.ToUpper(traderCfg.AIModel))
|
||||
|
||||
err := traderManager.AddTrader(
|
||||
traderCfg,
|
||||
cfg.CoinPoolAPIURL,
|
||||
cfg.MaxDailyLoss,
|
||||
cfg.MaxDrawdown,
|
||||
cfg.StopTradingMinutes,
|
||||
cfg.Leverage, // 传递杠杆配置
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 初始化trader失败: %v", err)
|
||||
}
|
||||
// 从数据库加载所有交易员到内存
|
||||
err = traderManager.LoadTradersFromDatabase(database)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 加载交易员失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查是否至少有一个启用的trader
|
||||
if enabledCount == 0 {
|
||||
log.Fatalf("❌ 没有启用的trader,请在config.json中设置至少一个trader的enabled=true")
|
||||
// 获取数据库中的所有交易员配置(用于显示,使用default用户)
|
||||
traders, err := database.GetTraders("default")
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 获取交易员列表失败: %v", err)
|
||||
}
|
||||
|
||||
// 显示加载的交易员信息
|
||||
fmt.Println()
|
||||
fmt.Println("🏁 竞赛参赛者:")
|
||||
for _, traderCfg := range cfg.Traders {
|
||||
// 只显示启用的trader
|
||||
if !traderCfg.Enabled {
|
||||
continue
|
||||
fmt.Println("🤖 数据库中的AI交易员配置:")
|
||||
if len(traders) == 0 {
|
||||
fmt.Println(" • 暂无配置的交易员,请通过Web界面创建")
|
||||
} else {
|
||||
for _, trader := range traders {
|
||||
status := "停止"
|
||||
if trader.IsRunning {
|
||||
status = "运行中"
|
||||
}
|
||||
fmt.Printf(" • %s (%s + %s) - 初始资金: %.0f USDT [%s]\n",
|
||||
trader.Name, strings.ToUpper(trader.AIModelID), strings.ToUpper(trader.ExchangeID),
|
||||
trader.InitialBalance, status)
|
||||
}
|
||||
fmt.Printf(" • %s (%s) - 初始资金: %.0f USDT\n",
|
||||
traderCfg.Name, strings.ToUpper(traderCfg.AIModel), traderCfg.InitialBalance)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("🤖 AI全权决策模式:")
|
||||
fmt.Printf(" • AI将自主决定每笔交易的杠杆倍数(山寨币最高%d倍,BTC/ETH最高%d倍)\n",
|
||||
cfg.Leverage.AltcoinLeverage, cfg.Leverage.BTCETHLeverage)
|
||||
fmt.Printf(" • AI将自主决定每笔交易的杠杆倍数(山寨币最高5倍,BTC/ETH最高5倍)\n")
|
||||
fmt.Println(" • AI将自主决定每笔交易的仓位大小")
|
||||
fmt.Println(" • AI将自主设置止损和止盈价格")
|
||||
fmt.Println(" • AI将基于市场数据、技术指标、账户状态做出全面分析")
|
||||
@@ -112,20 +247,31 @@ func main() {
|
||||
fmt.Println(strings.Repeat("=", 60))
|
||||
fmt.Println()
|
||||
|
||||
// 获取API服务器端口
|
||||
apiPort := 8080 // 默认端口
|
||||
if apiPortStr != "" {
|
||||
if port, err := strconv.Atoi(apiPortStr); err == nil {
|
||||
apiPort = port
|
||||
}
|
||||
}
|
||||
|
||||
// 创建并启动API服务器
|
||||
apiServer := api.NewServer(traderManager, cfg.APIServerPort)
|
||||
apiServer := api.NewServer(traderManager, database, apiPort)
|
||||
go func() {
|
||||
if err := apiServer.Start(); err != nil {
|
||||
log.Printf("❌ API服务器错误: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 启动流行情数据 - 默认使用所有交易员设置的币种 如果没有设置币种 则优先使用系统默认
|
||||
go market.NewWSMonitor(150).Start(database.GetCustomCoins())
|
||||
//go market.NewWSMonitor(150).Start([]string{}) //这里是一个使用方式 传入空的话 则使用market市场的所有币种
|
||||
// 设置优雅退出
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// 启动所有trader
|
||||
traderManager.StartAll()
|
||||
// TODO: 启动数据库中配置为运行状态的交易员
|
||||
// traderManager.StartAll()
|
||||
|
||||
// 等待退出信号
|
||||
<-sigChan
|
||||
@@ -135,5 +281,5 @@ func main() {
|
||||
traderManager.StopAll()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("👋 感谢使用AI交易竞赛系统!")
|
||||
fmt.Println("👋 感谢使用AI交易系统!")
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"nofx/config"
|
||||
"nofx/trader"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -22,43 +25,227 @@ func NewTraderManager() *TraderManager {
|
||||
}
|
||||
}
|
||||
|
||||
// AddTrader 添加一个trader
|
||||
func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, leverage config.LeverageConfig) error {
|
||||
// LoadTradersFromDatabase 从数据库加载所有交易员到内存
|
||||
func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) error {
|
||||
tm.mu.Lock()
|
||||
defer tm.mu.Unlock()
|
||||
|
||||
if _, exists := tm.traders[cfg.ID]; exists {
|
||||
return fmt.Errorf("trader ID '%s' 已存在", cfg.ID)
|
||||
// 获取所有用户
|
||||
userIDs, err := database.GetAllUsers()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取用户列表失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("📋 发现 %d 个用户,开始加载所有交易员配置...", len(userIDs))
|
||||
|
||||
var allTraders []*config.TraderRecord
|
||||
for _, userID := range userIDs {
|
||||
// 获取每个用户的交易员
|
||||
traders, err := database.GetTraders(userID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 获取用户 %s 的交易员失败: %v", userID, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("📋 用户 %s: %d 个交易员", userID, len(traders))
|
||||
allTraders = append(allTraders, traders...)
|
||||
}
|
||||
|
||||
log.Printf("📋 总共加载 %d 个交易员配置", len(allTraders))
|
||||
|
||||
// 获取系统配置(不包含信号源,信号源现在为用户级别)
|
||||
maxDailyLossStr, _ := database.GetSystemConfig("max_daily_loss")
|
||||
maxDrawdownStr, _ := database.GetSystemConfig("max_drawdown")
|
||||
stopTradingMinutesStr, _ := database.GetSystemConfig("stop_trading_minutes")
|
||||
defaultCoinsStr, _ := database.GetSystemConfig("default_coins")
|
||||
|
||||
// 解析配置
|
||||
maxDailyLoss := 10.0 // 默认值
|
||||
if val, err := strconv.ParseFloat(maxDailyLossStr, 64); err == nil {
|
||||
maxDailyLoss = val
|
||||
}
|
||||
|
||||
maxDrawdown := 20.0 // 默认值
|
||||
if val, err := strconv.ParseFloat(maxDrawdownStr, 64); err == nil {
|
||||
maxDrawdown = val
|
||||
}
|
||||
|
||||
stopTradingMinutes := 60 // 默认值
|
||||
if val, err := strconv.Atoi(stopTradingMinutesStr); err == nil {
|
||||
stopTradingMinutes = val
|
||||
}
|
||||
|
||||
// 解析默认币种列表
|
||||
var defaultCoins []string
|
||||
if defaultCoinsStr != "" {
|
||||
if err := json.Unmarshal([]byte(defaultCoinsStr), &defaultCoins); err != nil {
|
||||
log.Printf("⚠️ 解析默认币种配置失败: %v,使用空列表", err)
|
||||
defaultCoins = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个交易员获取AI模型和交易所配置
|
||||
for _, traderCfg := range allTraders {
|
||||
// 获取AI模型配置(使用交易员所属的用户ID)
|
||||
aiModels, err := database.GetAIModels(traderCfg.UserID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 获取AI模型配置失败: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var aiModelCfg *config.AIModelConfig
|
||||
// 优先精确匹配 model.ID(新版逻辑)
|
||||
for _, model := range aiModels {
|
||||
if model.ID == traderCfg.AIModelID {
|
||||
aiModelCfg = model
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果没有精确匹配,尝试匹配 provider(兼容旧数据)
|
||||
if aiModelCfg == nil {
|
||||
for _, model := range aiModels {
|
||||
if model.Provider == traderCfg.AIModelID {
|
||||
aiModelCfg = model
|
||||
log.Printf("⚠️ 交易员 %s 使用旧版 provider 匹配: %s -> %s", traderCfg.Name, traderCfg.AIModelID, model.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if aiModelCfg == nil {
|
||||
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
|
||||
continue
|
||||
}
|
||||
|
||||
if !aiModelCfg.Enabled {
|
||||
log.Printf("⚠️ 交易员 %s 的AI模型 %s 未启用,跳过", traderCfg.Name, traderCfg.AIModelID)
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取交易所配置(使用交易员所属的用户ID)
|
||||
exchanges, err := database.GetExchanges(traderCfg.UserID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 获取交易所配置失败: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var exchangeCfg *config.ExchangeConfig
|
||||
for _, exchange := range exchanges {
|
||||
if exchange.ID == traderCfg.ExchangeID {
|
||||
exchangeCfg = exchange
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if exchangeCfg == nil {
|
||||
log.Printf("⚠️ 交易员 %s 的交易所 %s 不存在,跳过", traderCfg.Name, traderCfg.ExchangeID)
|
||||
continue
|
||||
}
|
||||
|
||||
if !exchangeCfg.Enabled {
|
||||
log.Printf("⚠️ 交易员 %s 的交易所 %s 未启用,跳过", traderCfg.Name, traderCfg.ExchangeID)
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取用户信号源配置
|
||||
var coinPoolURL, oiTopURL string
|
||||
if userSignalSource, err := database.GetUserSignalSource(traderCfg.UserID); err == nil {
|
||||
coinPoolURL = userSignalSource.CoinPoolURL
|
||||
oiTopURL = userSignalSource.OITopURL
|
||||
} else {
|
||||
// 如果用户没有配置信号源,使用空字符串
|
||||
log.Printf("🔍 用户 %s 暂未配置信号源", traderCfg.UserID)
|
||||
}
|
||||
|
||||
// 添加到TraderManager
|
||||
err = tm.addTraderFromDB(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins)
|
||||
if err != nil {
|
||||
log.Printf("❌ 添加交易员 %s 失败: %v", traderCfg.Name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("✓ 成功加载 %d 个交易员到内存", len(tm.traders))
|
||||
return nil
|
||||
}
|
||||
|
||||
// addTraderFromConfig 内部方法:从配置添加交易员(不加锁,因为调用方已加锁)
|
||||
func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string) error {
|
||||
if _, exists := tm.traders[traderCfg.ID]; exists {
|
||||
return fmt.Errorf("trader ID '%s' 已存在", traderCfg.ID)
|
||||
}
|
||||
|
||||
// 处理交易币种列表
|
||||
var tradingCoins []string
|
||||
if traderCfg.TradingSymbols != "" {
|
||||
// 解析逗号分隔的交易币种列表
|
||||
symbols := strings.Split(traderCfg.TradingSymbols, ",")
|
||||
for _, symbol := range symbols {
|
||||
symbol = strings.TrimSpace(symbol)
|
||||
if symbol != "" {
|
||||
tradingCoins = append(tradingCoins, symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有指定交易币种,使用默认币种
|
||||
if len(tradingCoins) == 0 {
|
||||
tradingCoins = defaultCoins
|
||||
}
|
||||
|
||||
// 根据交易员配置决定是否使用信号源
|
||||
var effectiveCoinPoolURL string
|
||||
if traderCfg.UseCoinPool && coinPoolURL != "" {
|
||||
effectiveCoinPoolURL = coinPoolURL
|
||||
log.Printf("✓ 交易员 %s 启用 COIN POOL 信号源: %s", traderCfg.Name, coinPoolURL)
|
||||
}
|
||||
|
||||
// 构建AutoTraderConfig
|
||||
traderConfig := trader.AutoTraderConfig{
|
||||
ID: cfg.ID,
|
||||
Name: cfg.Name,
|
||||
AIModel: cfg.AIModel,
|
||||
Exchange: cfg.Exchange,
|
||||
BinanceAPIKey: cfg.BinanceAPIKey,
|
||||
BinanceSecretKey: cfg.BinanceSecretKey,
|
||||
HyperliquidPrivateKey: cfg.HyperliquidPrivateKey,
|
||||
HyperliquidWalletAddr: cfg.HyperliquidWalletAddr,
|
||||
HyperliquidTestnet: cfg.HyperliquidTestnet,
|
||||
AsterUser: cfg.AsterUser,
|
||||
AsterSigner: cfg.AsterSigner,
|
||||
AsterPrivateKey: cfg.AsterPrivateKey,
|
||||
CoinPoolAPIURL: coinPoolURL,
|
||||
UseQwen: cfg.AIModel == "qwen",
|
||||
DeepSeekKey: cfg.DeepSeekKey,
|
||||
QwenKey: cfg.QwenKey,
|
||||
CustomAPIURL: cfg.CustomAPIURL,
|
||||
CustomAPIKey: cfg.CustomAPIKey,
|
||||
CustomModelName: cfg.CustomModelName,
|
||||
ScanInterval: cfg.GetScanInterval(),
|
||||
InitialBalance: cfg.InitialBalance,
|
||||
BTCETHLeverage: leverage.BTCETHLeverage, // 使用配置的杠杆倍数
|
||||
AltcoinLeverage: leverage.AltcoinLeverage, // 使用配置的杠杆倍数
|
||||
traderConfig := trader.AutoTraderConfig{
|
||||
ID: traderCfg.ID,
|
||||
Name: traderCfg.Name,
|
||||
AIModel: aiModelCfg.Provider, // 使用provider作为模型标识
|
||||
Exchange: exchangeCfg.ID, // 使用exchange ID
|
||||
BinanceAPIKey: "",
|
||||
BinanceSecretKey: "",
|
||||
HyperliquidPrivateKey: "",
|
||||
HyperliquidTestnet: exchangeCfg.Testnet,
|
||||
CoinPoolAPIURL: effectiveCoinPoolURL,
|
||||
UseQwen: aiModelCfg.Provider == "qwen",
|
||||
DeepSeekKey: "",
|
||||
QwenKey: "",
|
||||
CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL
|
||||
CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称
|
||||
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
|
||||
InitialBalance: traderCfg.InitialBalance,
|
||||
BTCETHLeverage: traderCfg.BTCETHLeverage,
|
||||
AltcoinLeverage: traderCfg.AltcoinLeverage,
|
||||
MaxDailyLoss: maxDailyLoss,
|
||||
MaxDrawdown: maxDrawdown,
|
||||
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
||||
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||
DefaultCoins: defaultCoins,
|
||||
TradingCoins: tradingCoins,
|
||||
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
|
||||
}
|
||||
|
||||
// 根据交易所类型设置API密钥
|
||||
if exchangeCfg.ID == "binance" {
|
||||
traderConfig.BinanceAPIKey = exchangeCfg.APIKey
|
||||
traderConfig.BinanceSecretKey = exchangeCfg.SecretKey
|
||||
} else if exchangeCfg.ID == "hyperliquid" {
|
||||
traderConfig.HyperliquidPrivateKey = exchangeCfg.APIKey // hyperliquid用APIKey存储private key
|
||||
traderConfig.HyperliquidWalletAddr = exchangeCfg.HyperliquidWalletAddr
|
||||
} else if exchangeCfg.ID == "aster" {
|
||||
traderConfig.AsterUser = exchangeCfg.AsterUser
|
||||
traderConfig.AsterSigner = exchangeCfg.AsterSigner
|
||||
traderConfig.AsterPrivateKey = exchangeCfg.AsterPrivateKey
|
||||
}
|
||||
|
||||
// 根据AI模型设置API密钥
|
||||
if aiModelCfg.Provider == "qwen" {
|
||||
traderConfig.QwenKey = aiModelCfg.APIKey
|
||||
} else if aiModelCfg.Provider == "deepseek" {
|
||||
traderConfig.DeepSeekKey = aiModelCfg.APIKey
|
||||
}
|
||||
|
||||
// 创建trader实例
|
||||
@@ -66,9 +253,126 @@ func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string,
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建trader失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置自定义prompt(如果有)
|
||||
if traderCfg.CustomPrompt != "" {
|
||||
at.SetCustomPrompt(traderCfg.CustomPrompt)
|
||||
at.SetOverrideBasePrompt(traderCfg.OverrideBasePrompt)
|
||||
if traderCfg.OverrideBasePrompt {
|
||||
log.Printf("✓ 已设置自定义交易策略prompt (覆盖基础prompt)")
|
||||
} else {
|
||||
log.Printf("✓ 已设置自定义交易策略prompt (补充基础prompt)")
|
||||
}
|
||||
}
|
||||
|
||||
tm.traders[cfg.ID] = at
|
||||
log.Printf("✓ Trader '%s' (%s) 已添加", cfg.Name, cfg.AIModel)
|
||||
tm.traders[traderCfg.ID] = at
|
||||
log.Printf("✓ Trader '%s' (%s + %s) 已加载到内存", traderCfg.Name, aiModelCfg.Provider, exchangeCfg.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTrader 从数据库配置添加trader (移除旧版兼容性)
|
||||
|
||||
// AddTraderFromDB 从数据库配置添加trader
|
||||
func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string) error {
|
||||
tm.mu.Lock()
|
||||
defer tm.mu.Unlock()
|
||||
|
||||
if _, exists := tm.traders[traderCfg.ID]; exists {
|
||||
return fmt.Errorf("trader ID '%s' 已存在", traderCfg.ID)
|
||||
}
|
||||
|
||||
// 处理交易币种列表
|
||||
var tradingCoins []string
|
||||
if traderCfg.TradingSymbols != "" {
|
||||
// 解析逗号分隔的交易币种列表
|
||||
symbols := strings.Split(traderCfg.TradingSymbols, ",")
|
||||
for _, symbol := range symbols {
|
||||
symbol = strings.TrimSpace(symbol)
|
||||
if symbol != "" {
|
||||
tradingCoins = append(tradingCoins, symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有指定交易币种,使用默认币种
|
||||
if len(tradingCoins) == 0 {
|
||||
tradingCoins = defaultCoins
|
||||
}
|
||||
|
||||
// 根据交易员配置决定是否使用信号源
|
||||
var effectiveCoinPoolURL string
|
||||
if traderCfg.UseCoinPool && coinPoolURL != "" {
|
||||
effectiveCoinPoolURL = coinPoolURL
|
||||
log.Printf("✓ 交易员 %s 启用 COIN POOL 信号源: %s", traderCfg.Name, coinPoolURL)
|
||||
}
|
||||
|
||||
// 构建AutoTraderConfig
|
||||
traderConfig := trader.AutoTraderConfig{
|
||||
ID: traderCfg.ID,
|
||||
Name: traderCfg.Name,
|
||||
AIModel: aiModelCfg.Provider, // 使用provider作为模型标识
|
||||
Exchange: exchangeCfg.ID, // 使用exchange ID
|
||||
BinanceAPIKey: "",
|
||||
BinanceSecretKey: "",
|
||||
HyperliquidPrivateKey: "",
|
||||
HyperliquidTestnet: exchangeCfg.Testnet,
|
||||
CoinPoolAPIURL: effectiveCoinPoolURL,
|
||||
UseQwen: aiModelCfg.Provider == "qwen",
|
||||
DeepSeekKey: "",
|
||||
QwenKey: "",
|
||||
CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL
|
||||
CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称
|
||||
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
|
||||
InitialBalance: traderCfg.InitialBalance,
|
||||
BTCETHLeverage: traderCfg.BTCETHLeverage,
|
||||
AltcoinLeverage: traderCfg.AltcoinLeverage,
|
||||
MaxDailyLoss: maxDailyLoss,
|
||||
MaxDrawdown: maxDrawdown,
|
||||
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
||||
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||
DefaultCoins: defaultCoins,
|
||||
TradingCoins: tradingCoins,
|
||||
}
|
||||
|
||||
// 根据交易所类型设置API密钥
|
||||
if exchangeCfg.ID == "binance" {
|
||||
traderConfig.BinanceAPIKey = exchangeCfg.APIKey
|
||||
traderConfig.BinanceSecretKey = exchangeCfg.SecretKey
|
||||
} else if exchangeCfg.ID == "hyperliquid" {
|
||||
traderConfig.HyperliquidPrivateKey = exchangeCfg.APIKey // hyperliquid用APIKey存储private key
|
||||
traderConfig.HyperliquidWalletAddr = exchangeCfg.HyperliquidWalletAddr
|
||||
} else if exchangeCfg.ID == "aster" {
|
||||
traderConfig.AsterUser = exchangeCfg.AsterUser
|
||||
traderConfig.AsterSigner = exchangeCfg.AsterSigner
|
||||
traderConfig.AsterPrivateKey = exchangeCfg.AsterPrivateKey
|
||||
}
|
||||
|
||||
// 根据AI模型设置API密钥
|
||||
if aiModelCfg.Provider == "qwen" {
|
||||
traderConfig.QwenKey = aiModelCfg.APIKey
|
||||
} else if aiModelCfg.Provider == "deepseek" {
|
||||
traderConfig.DeepSeekKey = aiModelCfg.APIKey
|
||||
}
|
||||
|
||||
// 创建trader实例
|
||||
at, err := trader.NewAutoTrader(traderConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建trader失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置自定义prompt(如果有)
|
||||
if traderCfg.CustomPrompt != "" {
|
||||
at.SetCustomPrompt(traderCfg.CustomPrompt)
|
||||
at.SetOverrideBasePrompt(traderCfg.OverrideBasePrompt)
|
||||
if traderCfg.OverrideBasePrompt {
|
||||
log.Printf("✓ 已设置自定义交易策略prompt (覆盖基础prompt)")
|
||||
} else {
|
||||
log.Printf("✓ 已设置自定义交易策略prompt (补充基础prompt)")
|
||||
}
|
||||
}
|
||||
|
||||
tm.traders[traderCfg.ID] = at
|
||||
log.Printf("✓ Trader '%s' (%s + %s) 已添加", traderCfg.Name, aiModelCfg.Provider, exchangeCfg.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -155,6 +459,7 @@ func (tm *TraderManager) GetComparisonData() (map[string]interface{}, error) {
|
||||
"trader_id": t.GetID(),
|
||||
"trader_name": t.GetName(),
|
||||
"ai_model": t.GetAIModel(),
|
||||
"exchange": t.GetExchange(),
|
||||
"total_equity": account["total_equity"],
|
||||
"total_pnl": account["total_pnl"],
|
||||
"total_pnl_pct": account["total_pnl_pct"],
|
||||
@@ -170,3 +475,313 @@ func (tm *TraderManager) GetComparisonData() (map[string]interface{}, error) {
|
||||
|
||||
return comparison, nil
|
||||
}
|
||||
|
||||
// GetCompetitionData 获取竞赛数据(全平台所有交易员)
|
||||
func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) {
|
||||
tm.mu.RLock()
|
||||
defer tm.mu.RUnlock()
|
||||
|
||||
comparison := make(map[string]interface{})
|
||||
traders := make([]map[string]interface{}, 0)
|
||||
|
||||
// 获取全平台所有交易员
|
||||
for _, t := range tm.traders {
|
||||
account, err := t.GetAccountInfo()
|
||||
status := t.GetStatus()
|
||||
|
||||
var traderData map[string]interface{}
|
||||
|
||||
if err != nil {
|
||||
// 如果获取账户信息失败,使用默认值但仍然显示交易员
|
||||
log.Printf("⚠️ 获取交易员 %s 账户信息失败: %v", t.GetID(), err)
|
||||
traderData = map[string]interface{}{
|
||||
"trader_id": t.GetID(),
|
||||
"trader_name": t.GetName(),
|
||||
"ai_model": t.GetAIModel(),
|
||||
"exchange": t.GetExchange(),
|
||||
"total_equity": 0.0,
|
||||
"total_pnl": 0.0,
|
||||
"total_pnl_pct": 0.0,
|
||||
"position_count": 0,
|
||||
"margin_used_pct": 0.0,
|
||||
"is_running": status["is_running"],
|
||||
"error": "账户数据获取失败",
|
||||
}
|
||||
} else {
|
||||
// 正常情况下使用真实账户数据
|
||||
traderData = map[string]interface{}{
|
||||
"trader_id": t.GetID(),
|
||||
"trader_name": t.GetName(),
|
||||
"ai_model": t.GetAIModel(),
|
||||
"exchange": t.GetExchange(),
|
||||
"total_equity": account["total_equity"],
|
||||
"total_pnl": account["total_pnl"],
|
||||
"total_pnl_pct": account["total_pnl_pct"],
|
||||
"position_count": account["position_count"],
|
||||
"margin_used_pct": account["margin_used_pct"],
|
||||
"is_running": status["is_running"],
|
||||
}
|
||||
}
|
||||
|
||||
traders = append(traders, traderData)
|
||||
}
|
||||
comparison["traders"] = traders
|
||||
comparison["count"] = len(traders)
|
||||
|
||||
return comparison, nil
|
||||
}
|
||||
|
||||
// isUserTrader 检查trader是否属于指定用户
|
||||
func isUserTrader(traderID, userID string) bool {
|
||||
// trader ID格式: userID_traderName 或 randomUUID_modelName
|
||||
// 为了兼容性,我们检查前缀
|
||||
if len(traderID) >= len(userID) && traderID[:len(userID)] == userID {
|
||||
return true
|
||||
}
|
||||
// 对于老的default用户,所有没有明确用户前缀的都属于default
|
||||
if userID == "default" && !containsUserPrefix(traderID) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// containsUserPrefix 检查trader ID是否包含用户前缀
|
||||
func containsUserPrefix(traderID string) bool {
|
||||
// 检查是否包含邮箱格式的前缀(user@example.com_traderName)
|
||||
for i, ch := range traderID {
|
||||
if ch == '@' {
|
||||
// 找到@符号,说明可能是email前缀
|
||||
return true
|
||||
}
|
||||
if ch == '_' && i > 0 {
|
||||
// 找到下划线但前面没有@,可能是UUID或其他格式
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LoadUserTraders 为特定用户加载交易员到内存
|
||||
func (tm *TraderManager) LoadUserTraders(database *config.Database, userID string) error {
|
||||
tm.mu.Lock()
|
||||
defer tm.mu.Unlock()
|
||||
|
||||
// 获取指定用户的所有交易员
|
||||
traders, err := database.GetTraders(userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取用户 %s 的交易员列表失败: %w", userID, err)
|
||||
}
|
||||
|
||||
log.Printf("📋 为用户 %s 加载交易员配置: %d 个", userID, len(traders))
|
||||
|
||||
// 获取系统配置(不包含信号源,信号源现在为用户级别)
|
||||
maxDailyLossStr, _ := database.GetSystemConfig("max_daily_loss")
|
||||
maxDrawdownStr, _ := database.GetSystemConfig("max_drawdown")
|
||||
stopTradingMinutesStr, _ := database.GetSystemConfig("stop_trading_minutes")
|
||||
defaultCoinsStr, _ := database.GetSystemConfig("default_coins")
|
||||
|
||||
// 获取用户信号源配置
|
||||
var coinPoolURL, oiTopURL string
|
||||
if userSignalSource, err := database.GetUserSignalSource(userID); err == nil {
|
||||
coinPoolURL = userSignalSource.CoinPoolURL
|
||||
oiTopURL = userSignalSource.OITopURL
|
||||
log.Printf("📡 加载用户 %s 的信号源配置: COIN POOL=%s, OI TOP=%s", userID, coinPoolURL, oiTopURL)
|
||||
} else {
|
||||
log.Printf("🔍 用户 %s 暂未配置信号源", userID)
|
||||
}
|
||||
|
||||
// 解析配置
|
||||
maxDailyLoss := 10.0 // 默认值
|
||||
if val, err := strconv.ParseFloat(maxDailyLossStr, 64); err == nil {
|
||||
maxDailyLoss = val
|
||||
}
|
||||
|
||||
maxDrawdown := 20.0 // 默认值
|
||||
if val, err := strconv.ParseFloat(maxDrawdownStr, 64); err == nil {
|
||||
maxDrawdown = val
|
||||
}
|
||||
|
||||
stopTradingMinutes := 60 // 默认值
|
||||
if val, err := strconv.Atoi(stopTradingMinutesStr); err == nil {
|
||||
stopTradingMinutes = val
|
||||
}
|
||||
|
||||
// 解析默认币种列表
|
||||
var defaultCoins []string
|
||||
if defaultCoinsStr != "" {
|
||||
if err := json.Unmarshal([]byte(defaultCoinsStr), &defaultCoins); err != nil {
|
||||
log.Printf("⚠️ 解析默认币种配置失败: %v,使用空列表", err)
|
||||
defaultCoins = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个交易员获取AI模型和交易所配置
|
||||
for _, traderCfg := range traders {
|
||||
// 检查是否已经加载过这个交易员
|
||||
if _, exists := tm.traders[traderCfg.ID]; exists {
|
||||
log.Printf("⚠️ 交易员 %s 已经加载,跳过", traderCfg.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取AI模型配置(使用该用户的配置)
|
||||
aiModels, err := database.GetAIModels(userID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 获取用户 %s 的AI模型配置失败: %v", userID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var aiModelCfg *config.AIModelConfig
|
||||
// 优先精确匹配 model.ID(新版逻辑)
|
||||
for _, model := range aiModels {
|
||||
if model.ID == traderCfg.AIModelID {
|
||||
aiModelCfg = model
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果没有精确匹配,尝试匹配 provider(兼容旧数据)
|
||||
if aiModelCfg == nil {
|
||||
for _, model := range aiModels {
|
||||
if model.Provider == traderCfg.AIModelID {
|
||||
aiModelCfg = model
|
||||
log.Printf("⚠️ 交易员 %s 使用旧版 provider 匹配: %s -> %s", traderCfg.Name, traderCfg.AIModelID, model.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if aiModelCfg == nil {
|
||||
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
|
||||
continue
|
||||
}
|
||||
|
||||
if !aiModelCfg.Enabled {
|
||||
log.Printf("⚠️ 交易员 %s 的AI模型 %s 未启用,跳过", traderCfg.Name, traderCfg.AIModelID)
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取交易所配置(使用该用户的配置)
|
||||
exchanges, err := database.GetExchanges(userID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 获取用户 %s 的交易所配置失败: %v", userID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var exchangeCfg *config.ExchangeConfig
|
||||
for _, exchange := range exchanges {
|
||||
if exchange.ID == traderCfg.ExchangeID {
|
||||
exchangeCfg = exchange
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if exchangeCfg == nil {
|
||||
log.Printf("⚠️ 交易员 %s 的交易所 %s 不存在,跳过", traderCfg.Name, traderCfg.ExchangeID)
|
||||
continue
|
||||
}
|
||||
|
||||
if !exchangeCfg.Enabled {
|
||||
log.Printf("⚠️ 交易员 %s 的交易所 %s 未启用,跳过", traderCfg.Name, traderCfg.ExchangeID)
|
||||
continue
|
||||
}
|
||||
|
||||
// 使用现有的方法加载交易员
|
||||
err = tm.loadSingleTrader(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 加载交易员 %s 失败: %v", traderCfg.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadSingleTrader 加载单个交易员(从现有代码提取的公共逻辑)
|
||||
func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string) error {
|
||||
// 处理交易币种列表
|
||||
var tradingCoins []string
|
||||
if traderCfg.TradingSymbols != "" {
|
||||
// 解析逗号分隔的交易币种列表
|
||||
symbols := strings.Split(traderCfg.TradingSymbols, ",")
|
||||
for _, symbol := range symbols {
|
||||
symbol = strings.TrimSpace(symbol)
|
||||
if symbol != "" {
|
||||
tradingCoins = append(tradingCoins, symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有指定交易币种,使用默认币种
|
||||
if len(tradingCoins) == 0 {
|
||||
tradingCoins = defaultCoins
|
||||
}
|
||||
|
||||
// 根据交易员配置决定是否使用信号源
|
||||
var effectiveCoinPoolURL string
|
||||
if traderCfg.UseCoinPool && coinPoolURL != "" {
|
||||
effectiveCoinPoolURL = coinPoolURL
|
||||
log.Printf("✓ 交易员 %s 启用 COIN POOL 信号源: %s", traderCfg.Name, coinPoolURL)
|
||||
}
|
||||
|
||||
// 构建AutoTraderConfig
|
||||
traderConfig := trader.AutoTraderConfig{
|
||||
ID: traderCfg.ID,
|
||||
Name: traderCfg.Name,
|
||||
AIModel: aiModelCfg.Provider, // 使用provider作为模型标识
|
||||
Exchange: exchangeCfg.ID, // 使用exchange ID
|
||||
InitialBalance: traderCfg.InitialBalance,
|
||||
BTCETHLeverage: traderCfg.BTCETHLeverage,
|
||||
AltcoinLeverage: traderCfg.AltcoinLeverage,
|
||||
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
|
||||
CoinPoolAPIURL: effectiveCoinPoolURL,
|
||||
CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL
|
||||
CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称
|
||||
UseQwen: aiModelCfg.Provider == "qwen",
|
||||
MaxDailyLoss: maxDailyLoss,
|
||||
MaxDrawdown: maxDrawdown,
|
||||
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
||||
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||
DefaultCoins: defaultCoins,
|
||||
TradingCoins: tradingCoins,
|
||||
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
|
||||
}
|
||||
|
||||
// 根据交易所类型设置API密钥
|
||||
if exchangeCfg.ID == "binance" {
|
||||
traderConfig.BinanceAPIKey = exchangeCfg.APIKey
|
||||
traderConfig.BinanceSecretKey = exchangeCfg.SecretKey
|
||||
} else if exchangeCfg.ID == "hyperliquid" {
|
||||
traderConfig.HyperliquidPrivateKey = exchangeCfg.APIKey // hyperliquid用APIKey存储private key
|
||||
traderConfig.HyperliquidWalletAddr = exchangeCfg.HyperliquidWalletAddr
|
||||
} else if exchangeCfg.ID == "aster" {
|
||||
traderConfig.AsterUser = exchangeCfg.AsterUser
|
||||
traderConfig.AsterSigner = exchangeCfg.AsterSigner
|
||||
traderConfig.AsterPrivateKey = exchangeCfg.AsterPrivateKey
|
||||
}
|
||||
|
||||
// 根据AI模型设置API密钥
|
||||
if aiModelCfg.Provider == "qwen" {
|
||||
traderConfig.QwenKey = aiModelCfg.APIKey
|
||||
} else if aiModelCfg.Provider == "deepseek" {
|
||||
traderConfig.DeepSeekKey = aiModelCfg.APIKey
|
||||
}
|
||||
|
||||
// 创建trader实例
|
||||
at, err := trader.NewAutoTrader(traderConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建trader失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置自定义prompt(如果有)
|
||||
if traderCfg.CustomPrompt != "" {
|
||||
at.SetCustomPrompt(traderCfg.CustomPrompt)
|
||||
at.SetOverrideBasePrompt(traderCfg.OverrideBasePrompt)
|
||||
if traderCfg.OverrideBasePrompt {
|
||||
log.Printf("✓ 已设置自定义交易策略prompt (覆盖基础prompt)")
|
||||
} else {
|
||||
log.Printf("✓ 已设置自定义交易策略prompt (补充基础prompt)")
|
||||
}
|
||||
}
|
||||
|
||||
tm.traders[traderCfg.ID] = at
|
||||
log.Printf("✓ Trader '%s' (%s + %s) 已为用户加载到内存", traderCfg.Name, aiModelCfg.Provider, exchangeCfg.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
150
market/api_client.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package market
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://fapi.binance.com"
|
||||
)
|
||||
|
||||
type APIClient struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewAPIClient() *APIClient {
|
||||
return &APIClient{
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *APIClient) GetExchangeInfo() (*ExchangeInfo, error) {
|
||||
url := fmt.Sprintf("%s/fapi/v1/exchangeInfo", baseURL)
|
||||
resp, err := c.client.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var exchangeInfo ExchangeInfo
|
||||
err = json.Unmarshal(body, &exchangeInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &exchangeInfo, nil
|
||||
}
|
||||
|
||||
func (c *APIClient) GetKlines(symbol, interval string, limit int) ([]Kline, error) {
|
||||
url := fmt.Sprintf("%s/fapi/v1/klines", baseURL)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("symbol", symbol)
|
||||
q.Add("interval", interval)
|
||||
q.Add("limit", strconv.Itoa(limit))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var klineResponses []KlineResponse
|
||||
err = json.Unmarshal(body, &klineResponses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var klines []Kline
|
||||
for _, kr := range klineResponses {
|
||||
kline, err := parseKline(kr)
|
||||
if err != nil {
|
||||
log.Printf("解析K线数据失败: %v", err)
|
||||
continue
|
||||
}
|
||||
klines = append(klines, kline)
|
||||
}
|
||||
|
||||
return klines, nil
|
||||
}
|
||||
|
||||
func parseKline(kr KlineResponse) (Kline, error) {
|
||||
var kline Kline
|
||||
|
||||
if len(kr) < 11 {
|
||||
return kline, fmt.Errorf("invalid kline data")
|
||||
}
|
||||
|
||||
// 解析各个字段
|
||||
kline.OpenTime = int64(kr[0].(float64))
|
||||
kline.Open, _ = strconv.ParseFloat(kr[1].(string), 64)
|
||||
kline.High, _ = strconv.ParseFloat(kr[2].(string), 64)
|
||||
kline.Low, _ = strconv.ParseFloat(kr[3].(string), 64)
|
||||
kline.Close, _ = strconv.ParseFloat(kr[4].(string), 64)
|
||||
kline.Volume, _ = strconv.ParseFloat(kr[5].(string), 64)
|
||||
kline.CloseTime = int64(kr[6].(float64))
|
||||
kline.QuoteVolume, _ = strconv.ParseFloat(kr[7].(string), 64)
|
||||
kline.Trades = int(kr[8].(float64))
|
||||
kline.TakerBuyBaseVolume, _ = strconv.ParseFloat(kr[9].(string), 64)
|
||||
kline.TakerBuyQuoteVolume, _ = strconv.ParseFloat(kr[10].(string), 64)
|
||||
|
||||
return kline, nil
|
||||
}
|
||||
|
||||
func (c *APIClient) GetCurrentPrice(symbol string) (float64, error) {
|
||||
url := fmt.Sprintf("%s/fapi/v1/ticker/price", baseURL)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("symbol", symbol)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var ticker PriceTicker
|
||||
err = json.Unmarshal(body, &ticker)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
price, err := strconv.ParseFloat(ticker.Price, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return price, nil
|
||||
}
|
||||
202
market/combined_streams.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package market
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type CombinedStreamsClient struct {
|
||||
conn *websocket.Conn
|
||||
mu sync.RWMutex
|
||||
subscribers map[string]chan []byte
|
||||
reconnect bool
|
||||
done chan struct{}
|
||||
batchSize int // 每批订阅的流数量
|
||||
}
|
||||
|
||||
func NewCombinedStreamsClient(batchSize int) *CombinedStreamsClient {
|
||||
return &CombinedStreamsClient{
|
||||
subscribers: make(map[string]chan []byte),
|
||||
reconnect: true,
|
||||
done: make(chan struct{}),
|
||||
batchSize: batchSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CombinedStreamsClient) Connect() error {
|
||||
dialer := websocket.Dialer{
|
||||
HandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// 组合流使用不同的端点
|
||||
conn, _, err := dialer.Dial("wss://fstream.binance.com/stream", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("组合流WebSocket连接失败: %v", err)
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.conn = conn
|
||||
c.mu.Unlock()
|
||||
|
||||
log.Println("组合流WebSocket连接成功")
|
||||
go c.readMessages()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BatchSubscribeKlines 批量订阅K线
|
||||
func (c *CombinedStreamsClient) BatchSubscribeKlines(symbols []string, interval string) error {
|
||||
// 将symbols分批处理
|
||||
batches := c.splitIntoBatches(symbols, c.batchSize)
|
||||
|
||||
for i, batch := range batches {
|
||||
log.Printf("订阅第 %d 批, 数量: %d", i+1, len(batch))
|
||||
|
||||
streams := make([]string, len(batch))
|
||||
for j, symbol := range batch {
|
||||
streams[j] = fmt.Sprintf("%s@kline_%s", strings.ToLower(symbol), interval)
|
||||
}
|
||||
|
||||
if err := c.subscribeStreams(streams); err != nil {
|
||||
return fmt.Errorf("第 %d 批订阅失败: %v", i+1, err)
|
||||
}
|
||||
|
||||
// 批次间延迟,避免被限制
|
||||
if i < len(batches)-1 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// splitIntoBatches 将切片分成指定大小的批次
|
||||
func (c *CombinedStreamsClient) splitIntoBatches(symbols []string, batchSize int) [][]string {
|
||||
var batches [][]string
|
||||
|
||||
for i := 0; i < len(symbols); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(symbols) {
|
||||
end = len(symbols)
|
||||
}
|
||||
batches = append(batches, symbols[i:end])
|
||||
}
|
||||
|
||||
return batches
|
||||
}
|
||||
|
||||
// subscribeStreams 订阅多个流
|
||||
func (c *CombinedStreamsClient) subscribeStreams(streams []string) error {
|
||||
subscribeMsg := map[string]interface{}{
|
||||
"method": "SUBSCRIBE",
|
||||
"params": streams,
|
||||
"id": time.Now().UnixNano(),
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if c.conn == nil {
|
||||
return fmt.Errorf("WebSocket未连接")
|
||||
}
|
||||
|
||||
log.Printf("订阅流: %v", streams)
|
||||
return c.conn.WriteJSON(subscribeMsg)
|
||||
}
|
||||
|
||||
func (c *CombinedStreamsClient) readMessages() {
|
||||
for {
|
||||
select {
|
||||
case <-c.done:
|
||||
return
|
||||
default:
|
||||
c.mu.RLock()
|
||||
conn := c.conn
|
||||
c.mu.RUnlock()
|
||||
|
||||
if conn == nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Printf("读取组合流消息失败: %v", err)
|
||||
c.handleReconnect()
|
||||
return
|
||||
}
|
||||
|
||||
c.handleCombinedMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CombinedStreamsClient) handleCombinedMessage(message []byte) {
|
||||
var combinedMsg struct {
|
||||
Stream string `json:"stream"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(message, &combinedMsg); err != nil {
|
||||
log.Printf("解析组合消息失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
ch, exists := c.subscribers[combinedMsg.Stream]
|
||||
c.mu.RUnlock()
|
||||
|
||||
if exists {
|
||||
select {
|
||||
case ch <- combinedMsg.Data:
|
||||
default:
|
||||
log.Printf("订阅者通道已满: %s", combinedMsg.Stream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CombinedStreamsClient) AddSubscriber(stream string, bufferSize int) <-chan []byte {
|
||||
ch := make(chan []byte, bufferSize)
|
||||
c.mu.Lock()
|
||||
c.subscribers[stream] = ch
|
||||
c.mu.Unlock()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (c *CombinedStreamsClient) handleReconnect() {
|
||||
if !c.reconnect {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("组合流尝试重新连接...")
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
if err := c.Connect(); err != nil {
|
||||
log.Printf("组合流重新连接失败: %v", err)
|
||||
go c.handleReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CombinedStreamsClient) Close() {
|
||||
c.reconnect = false
|
||||
close(c.done)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
c.conn = nil
|
||||
}
|
||||
|
||||
for stream, ch := range c.subscribers {
|
||||
close(ch)
|
||||
delete(c.subscribers, stream)
|
||||
}
|
||||
}
|
||||
105
market/data.go
@@ -10,72 +10,20 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Data 市场数据结构
|
||||
type Data struct {
|
||||
Symbol string
|
||||
CurrentPrice float64
|
||||
PriceChange1h float64 // 1小时价格变化百分比
|
||||
PriceChange4h float64 // 4小时价格变化百分比
|
||||
CurrentEMA20 float64
|
||||
CurrentMACD float64
|
||||
CurrentRSI7 float64
|
||||
OpenInterest *OIData
|
||||
FundingRate float64
|
||||
IntradaySeries *IntradayData
|
||||
LongerTermContext *LongerTermData
|
||||
}
|
||||
|
||||
// OIData Open Interest数据
|
||||
type OIData struct {
|
||||
Latest float64
|
||||
Average float64
|
||||
}
|
||||
|
||||
// IntradayData 日内数据(3分钟间隔)
|
||||
type IntradayData struct {
|
||||
MidPrices []float64
|
||||
EMA20Values []float64
|
||||
MACDValues []float64
|
||||
RSI7Values []float64
|
||||
RSI14Values []float64
|
||||
}
|
||||
|
||||
// LongerTermData 长期数据(4小时时间框架)
|
||||
type LongerTermData struct {
|
||||
EMA20 float64
|
||||
EMA50 float64
|
||||
ATR3 float64
|
||||
ATR14 float64
|
||||
CurrentVolume float64
|
||||
AverageVolume float64
|
||||
MACDValues []float64
|
||||
RSI14Values []float64
|
||||
}
|
||||
|
||||
// Kline K线数据
|
||||
type Kline struct {
|
||||
OpenTime int64
|
||||
Open float64
|
||||
High float64
|
||||
Low float64
|
||||
Close float64
|
||||
Volume float64
|
||||
CloseTime int64
|
||||
}
|
||||
|
||||
// Get 获取指定代币的市场数据
|
||||
func Get(symbol string) (*Data, error) {
|
||||
var klines3m, klines4h []Kline
|
||||
var err error
|
||||
// 标准化symbol
|
||||
symbol = Normalize(symbol)
|
||||
|
||||
// 获取3分钟K线数据 (最近10个)
|
||||
klines3m, err := getKlines(symbol, "3m", 40) // 多获取一些用于计算
|
||||
klines3m, err = WSMonitorCli.GetCurrentKlines(symbol, "3m") // 多获取一些用于计算
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取3分钟K线失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取4小时K线数据 (最近10个)
|
||||
klines4h, err := getKlines(symbol, "4h", 60) // 多获取用于计算指标
|
||||
klines4h, err = WSMonitorCli.GetCurrentKlines(symbol, "4h") // 多获取用于计算指标
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取4小时K线失败: %v", err)
|
||||
}
|
||||
@@ -136,51 +84,6 @@ func Get(symbol string) (*Data, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getKlines 从Binance获取K线数据
|
||||
func getKlines(symbol, interval string, limit int) ([]Kline, error) {
|
||||
url := fmt.Sprintf("https://fapi.binance.com/fapi/v1/klines?symbol=%s&interval=%s&limit=%d",
|
||||
symbol, interval, limit)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rawData [][]interface{}
|
||||
if err := json.Unmarshal(body, &rawData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
klines := make([]Kline, len(rawData))
|
||||
for i, item := range rawData {
|
||||
openTime := int64(item[0].(float64))
|
||||
open, _ := parseFloat(item[1])
|
||||
high, _ := parseFloat(item[2])
|
||||
low, _ := parseFloat(item[3])
|
||||
close, _ := parseFloat(item[4])
|
||||
volume, _ := parseFloat(item[5])
|
||||
closeTime := int64(item[6].(float64))
|
||||
|
||||
klines[i] = Kline{
|
||||
OpenTime: openTime,
|
||||
Open: open,
|
||||
High: high,
|
||||
Low: low,
|
||||
Close: close,
|
||||
Volume: volume,
|
||||
CloseTime: closeTime,
|
||||
}
|
||||
}
|
||||
|
||||
return klines, nil
|
||||
}
|
||||
|
||||
// calculateEMA 计算EMA
|
||||
func calculateEMA(klines []Kline, period int) float64 {
|
||||
if len(klines) < period {
|
||||
|
||||
260
market/monitor.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package market
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WSMonitor struct {
|
||||
wsClient *WSClient
|
||||
combinedClient *CombinedStreamsClient
|
||||
symbols []string
|
||||
featuresMap sync.Map
|
||||
alertsChan chan Alert
|
||||
klineDataMap3m sync.Map // 存储每个交易对的K线历史数据
|
||||
klineDataMap4h sync.Map // 存储每个交易对的K线历史数据
|
||||
tickerDataMap sync.Map // 存储每个交易对的ticker数据
|
||||
batchSize int
|
||||
filterSymbols sync.Map // 使用sync.Map来存储需要监控的币种和其状态
|
||||
symbolStats sync.Map // 存储币种统计信息
|
||||
FilterSymbol []string //经过筛选的币种
|
||||
}
|
||||
type SymbolStats struct {
|
||||
LastActiveTime time.Time
|
||||
AlertCount int
|
||||
VolumeSpikeCount int
|
||||
LastAlertTime time.Time
|
||||
Score float64 // 综合评分
|
||||
}
|
||||
|
||||
var WSMonitorCli *WSMonitor
|
||||
var subKlineTime = []string{"3m", "4h"} // 管理订阅流的K线周期
|
||||
|
||||
func NewWSMonitor(batchSize int) *WSMonitor {
|
||||
WSMonitorCli = &WSMonitor{
|
||||
wsClient: NewWSClient(),
|
||||
combinedClient: NewCombinedStreamsClient(batchSize),
|
||||
alertsChan: make(chan Alert, 1000),
|
||||
batchSize: batchSize,
|
||||
}
|
||||
return WSMonitorCli
|
||||
}
|
||||
|
||||
func (m *WSMonitor) Initialize(coins []string) error {
|
||||
log.Println("初始化WebSocket监控器...")
|
||||
// 获取交易对信息
|
||||
apiClient := NewAPIClient()
|
||||
// 如果不指定交易对,则使用market市场的所有交易对币种
|
||||
if len(coins) == 0 {
|
||||
exchangeInfo, err := apiClient.GetExchangeInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 筛选永续合约交易对 --仅测试时使用
|
||||
//exchangeInfo.Symbols = exchangeInfo.Symbols[0:2]
|
||||
for _, symbol := range exchangeInfo.Symbols {
|
||||
if symbol.Status == "TRADING" && symbol.ContractType == "PERPETUAL" && strings.ToUpper(symbol.Symbol[len(symbol.Symbol)-4:]) == "USDT" {
|
||||
m.symbols = append(m.symbols, symbol.Symbol)
|
||||
m.filterSymbols.Store(symbol.Symbol, true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m.symbols = coins
|
||||
}
|
||||
|
||||
log.Printf("找到 %d 个交易对", len(m.symbols))
|
||||
// 初始化历史数据
|
||||
if err := m.initializeHistoricalData(); err != nil {
|
||||
log.Printf("初始化历史数据失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *WSMonitor) initializeHistoricalData() error {
|
||||
apiClient := NewAPIClient()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
semaphore := make(chan struct{}, 5) // 限制并发数
|
||||
|
||||
for _, symbol := range m.symbols {
|
||||
wg.Add(1)
|
||||
semaphore <- struct{}{}
|
||||
|
||||
go func(s string) {
|
||||
defer wg.Done()
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
// 获取历史K线数据
|
||||
klines, err := apiClient.GetKlines(s, "3m", 100)
|
||||
if err != nil {
|
||||
log.Printf("获取 %s 历史数据失败: %v", s, err)
|
||||
return
|
||||
}
|
||||
if len(klines) > 0 {
|
||||
m.klineDataMap3m.Store(s, klines)
|
||||
log.Printf("已加载 %s 的历史K线数据-3m: %d 条", s, len(klines))
|
||||
}
|
||||
// 获取历史K线数据
|
||||
klines4h, err := apiClient.GetKlines(s, "4h", 100)
|
||||
if err != nil {
|
||||
log.Printf("获取 %s 历史数据失败: %v", s, err)
|
||||
return
|
||||
}
|
||||
if len(klines4h) > 0 {
|
||||
m.klineDataMap4h.Store(s, klines)
|
||||
log.Printf("已加载 %s 的历史K线数据-4h: %d 条", s, len(klines))
|
||||
}
|
||||
}(symbol)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *WSMonitor) Start(coins []string) {
|
||||
log.Printf("启动WebSocket实时监控...")
|
||||
// 初始化交易对
|
||||
err := m.Initialize(coins)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 初始化币种: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = m.combinedClient.Connect()
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 批量订阅流: %v", err)
|
||||
return
|
||||
}
|
||||
// 订阅所有交易对
|
||||
err = m.subscribeAll()
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 订阅币种交易对: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// subscribeSymbol 注册监听
|
||||
func (m *WSMonitor) subscribeSymbol(symbol, st string) []string {
|
||||
var streams []string
|
||||
stream := fmt.Sprintf("%s@kline_%s", strings.ToLower(symbol), st)
|
||||
ch := m.combinedClient.AddSubscriber(stream, 100)
|
||||
streams = append(streams, stream)
|
||||
go m.handleKlineData(symbol, ch, st)
|
||||
|
||||
return streams
|
||||
}
|
||||
func (m *WSMonitor) subscribeAll() error {
|
||||
// 执行批量订阅
|
||||
log.Println("开始订阅所有交易对...")
|
||||
for _, symbol := range m.symbols {
|
||||
for _, st := range subKlineTime {
|
||||
m.subscribeSymbol(symbol, st)
|
||||
}
|
||||
}
|
||||
for _, st := range subKlineTime {
|
||||
err := m.combinedClient.BatchSubscribeKlines(m.symbols, st)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 订阅3m K线: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Println("所有交易对订阅完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *WSMonitor) handleKlineData(symbol string, ch <-chan []byte, _time string) {
|
||||
for data := range ch {
|
||||
var klineData KlineWSData
|
||||
if err := json.Unmarshal(data, &klineData); err != nil {
|
||||
log.Printf("解析Kline数据失败: %v", err)
|
||||
continue
|
||||
}
|
||||
m.processKlineUpdate(symbol, klineData, _time)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WSMonitor) getKlineDataMap(_time string) *sync.Map {
|
||||
var klineDataMap *sync.Map
|
||||
if _time == "3m" {
|
||||
klineDataMap = &m.klineDataMap3m
|
||||
} else if _time == "4h" {
|
||||
klineDataMap = &m.klineDataMap4h
|
||||
} else {
|
||||
klineDataMap = &sync.Map{}
|
||||
}
|
||||
return klineDataMap
|
||||
}
|
||||
func (m *WSMonitor) processKlineUpdate(symbol string, wsData KlineWSData, _time string) {
|
||||
// 转换WebSocket数据为Kline结构
|
||||
kline := Kline{
|
||||
OpenTime: wsData.Kline.StartTime,
|
||||
CloseTime: wsData.Kline.CloseTime,
|
||||
Trades: wsData.Kline.NumberOfTrades,
|
||||
}
|
||||
kline.Open, _ = parseFloat(wsData.Kline.OpenPrice)
|
||||
kline.High, _ = parseFloat(wsData.Kline.HighPrice)
|
||||
kline.Low, _ = parseFloat(wsData.Kline.LowPrice)
|
||||
kline.Close, _ = parseFloat(wsData.Kline.ClosePrice)
|
||||
kline.Volume, _ = parseFloat(wsData.Kline.Volume)
|
||||
kline.High, _ = parseFloat(wsData.Kline.HighPrice)
|
||||
kline.QuoteVolume, _ = parseFloat(wsData.Kline.QuoteVolume)
|
||||
kline.TakerBuyBaseVolume, _ = parseFloat(wsData.Kline.TakerBuyBaseVolume)
|
||||
kline.TakerBuyQuoteVolume, _ = parseFloat(wsData.Kline.TakerBuyQuoteVolume)
|
||||
// 更新K线数据
|
||||
var klineDataMap = m.getKlineDataMap(_time)
|
||||
value, exists := klineDataMap.Load(symbol)
|
||||
var klines []Kline
|
||||
if exists {
|
||||
klines = value.([]Kline)
|
||||
|
||||
// 检查是否是新的K线
|
||||
if len(klines) > 0 && klines[len(klines)-1].OpenTime == kline.OpenTime {
|
||||
// 更新当前K线
|
||||
klines[len(klines)-1] = kline
|
||||
} else {
|
||||
// 添加新K线
|
||||
klines = append(klines, kline)
|
||||
|
||||
// 保持数据长度
|
||||
if len(klines) > 100 {
|
||||
klines = klines[1:]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
klines = []Kline{kline}
|
||||
}
|
||||
|
||||
klineDataMap.Store(symbol, klines)
|
||||
}
|
||||
|
||||
func (m *WSMonitor) GetCurrentKlines(symbol string, _time string) ([]Kline, error) {
|
||||
// 对每一个进来的symbol检测是否存在内类 是否的话就订阅它
|
||||
value, exists := m.getKlineDataMap(_time).Load(symbol)
|
||||
if !exists {
|
||||
// 如果Ws数据未初始化完成时,单独使用api获取 - 兼容性代码 (防止在未初始化完成是,已经有交易员运行)
|
||||
apiClient := NewAPIClient()
|
||||
klines, err := apiClient.GetKlines(symbol, _time, 100)
|
||||
m.getKlineDataMap(_time).Store(strings.ToUpper(symbol), klines) //动态缓存进缓存
|
||||
subStr := m.subscribeSymbol(symbol, _time)
|
||||
subErr := m.combinedClient.subscribeStreams(subStr)
|
||||
log.Printf("动态订阅流: %v", subStr)
|
||||
if subErr != nil {
|
||||
return nil, fmt.Errorf("动态订阅%v分钟K线失败: %v", _time, subErr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取%v分钟K线失败: %v", _time, err)
|
||||
}
|
||||
return klines, fmt.Errorf("symbol不存在")
|
||||
}
|
||||
return value.([]Kline), nil
|
||||
}
|
||||
|
||||
func (m *WSMonitor) Close() {
|
||||
m.wsClient.Close()
|
||||
close(m.alertsChan)
|
||||
}
|
||||
157
market/types.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package market
|
||||
|
||||
import "time"
|
||||
|
||||
// Data 市场数据结构
|
||||
type Data struct {
|
||||
Symbol string
|
||||
CurrentPrice float64
|
||||
PriceChange1h float64 // 1小时价格变化百分比
|
||||
PriceChange4h float64 // 4小时价格变化百分比
|
||||
CurrentEMA20 float64
|
||||
CurrentMACD float64
|
||||
CurrentRSI7 float64
|
||||
OpenInterest *OIData
|
||||
FundingRate float64
|
||||
IntradaySeries *IntradayData
|
||||
LongerTermContext *LongerTermData
|
||||
}
|
||||
|
||||
// OIData Open Interest数据
|
||||
type OIData struct {
|
||||
Latest float64
|
||||
Average float64
|
||||
}
|
||||
|
||||
// IntradayData 日内数据(3分钟间隔)
|
||||
type IntradayData struct {
|
||||
MidPrices []float64
|
||||
EMA20Values []float64
|
||||
MACDValues []float64
|
||||
RSI7Values []float64
|
||||
RSI14Values []float64
|
||||
}
|
||||
|
||||
// LongerTermData 长期数据(4小时时间框架)
|
||||
type LongerTermData struct {
|
||||
EMA20 float64
|
||||
EMA50 float64
|
||||
ATR3 float64
|
||||
ATR14 float64
|
||||
CurrentVolume float64
|
||||
AverageVolume float64
|
||||
MACDValues []float64
|
||||
RSI14Values []float64
|
||||
}
|
||||
|
||||
// Binance API 响应结构
|
||||
type ExchangeInfo struct {
|
||||
Symbols []SymbolInfo `json:"symbols"`
|
||||
}
|
||||
|
||||
type SymbolInfo struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Status string `json:"status"`
|
||||
BaseAsset string `json:"baseAsset"`
|
||||
QuoteAsset string `json:"quoteAsset"`
|
||||
ContractType string `json:"contractType"`
|
||||
PricePrecision int `json:"pricePrecision"`
|
||||
QuantityPrecision int `json:"quantityPrecision"`
|
||||
}
|
||||
|
||||
type Kline struct {
|
||||
OpenTime int64 `json:"openTime"`
|
||||
Open float64 `json:"open"`
|
||||
High float64 `json:"high"`
|
||||
Low float64 `json:"low"`
|
||||
Close float64 `json:"close"`
|
||||
Volume float64 `json:"volume"`
|
||||
CloseTime int64 `json:"closeTime"`
|
||||
QuoteVolume float64 `json:"quoteVolume"`
|
||||
Trades int `json:"trades"`
|
||||
TakerBuyBaseVolume float64 `json:"takerBuyBaseVolume"`
|
||||
TakerBuyQuoteVolume float64 `json:"takerBuyQuoteVolume"`
|
||||
}
|
||||
|
||||
type KlineResponse []interface{}
|
||||
|
||||
type PriceTicker struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Price string `json:"price"`
|
||||
}
|
||||
|
||||
type Ticker24hr struct {
|
||||
Symbol string `json:"symbol"`
|
||||
PriceChange string `json:"priceChange"`
|
||||
PriceChangePercent string `json:"priceChangePercent"`
|
||||
Volume string `json:"volume"`
|
||||
QuoteVolume string `json:"quoteVolume"`
|
||||
}
|
||||
|
||||
// 特征数据结构
|
||||
type SymbolFeatures struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Price float64 `json:"price"`
|
||||
PriceChange15Min float64 `json:"price_change_15min"`
|
||||
PriceChange1H float64 `json:"price_change_1h"`
|
||||
PriceChange4H float64 `json:"price_change_4h"`
|
||||
Volume float64 `json:"volume"`
|
||||
VolumeRatio5 float64 `json:"volume_ratio_5"`
|
||||
VolumeRatio20 float64 `json:"volume_ratio_20"`
|
||||
VolumeTrend float64 `json:"volume_trend"`
|
||||
RSI14 float64 `json:"rsi_14"`
|
||||
SMA5 float64 `json:"sma_5"`
|
||||
SMA10 float64 `json:"sma_10"`
|
||||
SMA20 float64 `json:"sma_20"`
|
||||
HighLowRatio float64 `json:"high_low_ratio"`
|
||||
Volatility20 float64 `json:"volatility_20"`
|
||||
PositionInRange float64 `json:"position_in_range"`
|
||||
}
|
||||
|
||||
// 警报数据结构
|
||||
type Alert struct {
|
||||
Type string `json:"type"`
|
||||
Symbol string `json:"symbol"`
|
||||
Value float64 `json:"value"`
|
||||
Threshold float64 `json:"threshold"`
|
||||
Message string `json:"message"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
AlertThresholds AlertThresholds `json:"alert_thresholds"`
|
||||
UpdateInterval int `json:"update_interval"` // seconds
|
||||
CleanupConfig CleanupConfig `json:"cleanup_config"`
|
||||
}
|
||||
|
||||
type AlertThresholds struct {
|
||||
VolumeSpike float64 `json:"volume_spike"`
|
||||
PriceChange15Min float64 `json:"price_change_15min"`
|
||||
VolumeTrend float64 `json:"volume_trend"`
|
||||
RSIOverbought float64 `json:"rsi_overbought"`
|
||||
RSIOversold float64 `json:"rsi_oversold"`
|
||||
}
|
||||
type CleanupConfig struct {
|
||||
InactiveTimeout time.Duration `json:"inactive_timeout"` // 不活跃超时时间
|
||||
MinScoreThreshold float64 `json:"min_score_threshold"` // 最低评分阈值
|
||||
NoAlertTimeout time.Duration `json:"no_alert_timeout"` // 无警报超时时间
|
||||
CheckInterval time.Duration `json:"check_interval"` // 检查间隔
|
||||
}
|
||||
|
||||
var config = Config{
|
||||
AlertThresholds: AlertThresholds{
|
||||
VolumeSpike: 3.0,
|
||||
PriceChange15Min: 0.05,
|
||||
VolumeTrend: 2.0,
|
||||
RSIOverbought: 70,
|
||||
RSIOversold: 30,
|
||||
},
|
||||
CleanupConfig: CleanupConfig{
|
||||
InactiveTimeout: 30 * time.Minute,
|
||||
MinScoreThreshold: 15.0,
|
||||
NoAlertTimeout: 20 * time.Minute,
|
||||
CheckInterval: 5 * time.Minute,
|
||||
},
|
||||
UpdateInterval: 60, // 1 minute
|
||||
}
|
||||
231
market/websocket_client.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package market
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WSClient struct {
|
||||
conn *websocket.Conn
|
||||
mu sync.RWMutex
|
||||
subscribers map[string]chan []byte
|
||||
reconnect bool
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type WSMessage struct {
|
||||
Stream string `json:"stream"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
type KlineWSData struct {
|
||||
EventType string `json:"e"`
|
||||
EventTime int64 `json:"E"`
|
||||
Symbol string `json:"s"`
|
||||
Kline struct {
|
||||
StartTime int64 `json:"t"`
|
||||
CloseTime int64 `json:"T"`
|
||||
Symbol string `json:"s"`
|
||||
Interval string `json:"i"`
|
||||
FirstTradeID int64 `json:"f"`
|
||||
LastTradeID int64 `json:"L"`
|
||||
OpenPrice string `json:"o"`
|
||||
ClosePrice string `json:"c"`
|
||||
HighPrice string `json:"h"`
|
||||
LowPrice string `json:"l"`
|
||||
Volume string `json:"v"`
|
||||
NumberOfTrades int `json:"n"`
|
||||
IsFinal bool `json:"x"`
|
||||
QuoteVolume string `json:"q"`
|
||||
TakerBuyBaseVolume string `json:"V"`
|
||||
TakerBuyQuoteVolume string `json:"Q"`
|
||||
} `json:"k"`
|
||||
}
|
||||
|
||||
type TickerWSData struct {
|
||||
EventType string `json:"e"`
|
||||
EventTime int64 `json:"E"`
|
||||
Symbol string `json:"s"`
|
||||
PriceChange string `json:"p"`
|
||||
PriceChangePercent string `json:"P"`
|
||||
WeightedAvgPrice string `json:"w"`
|
||||
LastPrice string `json:"c"`
|
||||
LastQty string `json:"Q"`
|
||||
OpenPrice string `json:"o"`
|
||||
HighPrice string `json:"h"`
|
||||
LowPrice string `json:"l"`
|
||||
Volume string `json:"v"`
|
||||
QuoteVolume string `json:"q"`
|
||||
OpenTime int64 `json:"O"`
|
||||
CloseTime int64 `json:"C"`
|
||||
FirstID int64 `json:"F"`
|
||||
LastID int64 `json:"L"`
|
||||
Count int `json:"n"`
|
||||
}
|
||||
|
||||
func NewWSClient() *WSClient {
|
||||
return &WSClient{
|
||||
subscribers: make(map[string]chan []byte),
|
||||
reconnect: true,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WSClient) Connect() error {
|
||||
dialer := websocket.Dialer{
|
||||
HandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
conn, _, err := dialer.Dial("wss://ws-fapi.binance.com/ws-fapi/v1", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("WebSocket连接失败: %v", err)
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.conn = conn
|
||||
w.mu.Unlock()
|
||||
|
||||
log.Println("WebSocket连接成功")
|
||||
|
||||
// 启动消息读取循环
|
||||
go w.readMessages()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WSClient) SubscribeKline(symbol, interval string) error {
|
||||
stream := fmt.Sprintf("%s@kline_%s", symbol, interval)
|
||||
return w.subscribe(stream)
|
||||
}
|
||||
|
||||
func (w *WSClient) SubscribeTicker(symbol string) error {
|
||||
stream := fmt.Sprintf("%s@ticker", symbol)
|
||||
return w.subscribe(stream)
|
||||
}
|
||||
|
||||
func (w *WSClient) SubscribeMiniTicker(symbol string) error {
|
||||
stream := fmt.Sprintf("%s@miniTicker", symbol)
|
||||
return w.subscribe(stream)
|
||||
}
|
||||
|
||||
func (w *WSClient) subscribe(stream string) error {
|
||||
subscribeMsg := map[string]interface{}{
|
||||
"method": "SUBSCRIBE",
|
||||
"params": []string{stream},
|
||||
"id": time.Now().Unix(),
|
||||
}
|
||||
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
|
||||
if w.conn == nil {
|
||||
return fmt.Errorf("WebSocket未连接")
|
||||
}
|
||||
|
||||
err := w.conn.WriteJSON(subscribeMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("订阅流: %s", stream)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WSClient) readMessages() {
|
||||
for {
|
||||
select {
|
||||
case <-w.done:
|
||||
return
|
||||
default:
|
||||
w.mu.RLock()
|
||||
conn := w.conn
|
||||
w.mu.RUnlock()
|
||||
|
||||
if conn == nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Printf("读取WebSocket消息失败: %v", err)
|
||||
w.handleReconnect()
|
||||
return
|
||||
}
|
||||
|
||||
w.handleMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WSClient) handleMessage(message []byte) {
|
||||
var wsMsg WSMessage
|
||||
if err := json.Unmarshal(message, &wsMsg); err != nil {
|
||||
// 可能是其他格式的消息
|
||||
return
|
||||
}
|
||||
|
||||
w.mu.RLock()
|
||||
ch, exists := w.subscribers[wsMsg.Stream]
|
||||
w.mu.RUnlock()
|
||||
|
||||
if exists {
|
||||
select {
|
||||
case ch <- wsMsg.Data:
|
||||
default:
|
||||
log.Printf("订阅者通道已满: %s", wsMsg.Stream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WSClient) handleReconnect() {
|
||||
if !w.reconnect {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("尝试重新连接...")
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
if err := w.Connect(); err != nil {
|
||||
log.Printf("重新连接失败: %v", err)
|
||||
go w.handleReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WSClient) AddSubscriber(stream string, bufferSize int) <-chan []byte {
|
||||
ch := make(chan []byte, bufferSize)
|
||||
w.mu.Lock()
|
||||
w.subscribers[stream] = ch
|
||||
w.mu.Unlock()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (w *WSClient) RemoveSubscriber(stream string) {
|
||||
w.mu.Lock()
|
||||
delete(w.subscribers, stream)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
func (w *WSClient) Close() {
|
||||
w.reconnect = false
|
||||
close(w.done)
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if w.conn != nil {
|
||||
w.conn.Close()
|
||||
w.conn = nil
|
||||
}
|
||||
|
||||
// 关闭所有订阅者通道
|
||||
for stream, ch := range w.subscribers {
|
||||
close(ch)
|
||||
delete(w.subscribers, stream)
|
||||
}
|
||||
}
|
||||
122
mcp/client.go
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -23,7 +24,6 @@ const (
|
||||
type Client struct {
|
||||
Provider Provider
|
||||
APIKey string
|
||||
SecretKey string // 阿里云需要
|
||||
BaseURL string
|
||||
Model string
|
||||
Timeout time.Duration
|
||||
@@ -32,61 +32,93 @@ type Client struct {
|
||||
|
||||
func New() *Client {
|
||||
// 默认配置
|
||||
var defaultClient = Client{
|
||||
return &Client{
|
||||
Provider: ProviderDeepSeek,
|
||||
BaseURL: "https://api.deepseek.com/v1",
|
||||
Model: "deepseek-chat",
|
||||
Timeout: 120 * time.Second, // 增加到120秒,因为AI需要分析大量数据
|
||||
}
|
||||
return &defaultClient
|
||||
}
|
||||
|
||||
// SetDeepSeekAPIKey 设置DeepSeek API密钥
|
||||
func (cfg *Client) SetDeepSeekAPIKey(apiKey string) {
|
||||
cfg.Provider = ProviderDeepSeek
|
||||
cfg.APIKey = apiKey
|
||||
cfg.BaseURL = "https://api.deepseek.com/v1"
|
||||
cfg.Model = "deepseek-chat"
|
||||
// customURL 为空时使用默认URL,customModel 为空时使用默认模型
|
||||
func (client *Client) SetDeepSeekAPIKey(apiKey string, customURL string, customModel string) {
|
||||
client.Provider = ProviderDeepSeek
|
||||
client.APIKey = apiKey
|
||||
if customURL != "" {
|
||||
client.BaseURL = customURL
|
||||
log.Printf("🔧 [MCP] DeepSeek 使用自定义 BaseURL: %s", customURL)
|
||||
} else {
|
||||
client.BaseURL = "https://api.deepseek.com/v1"
|
||||
log.Printf("🔧 [MCP] DeepSeek 使用默认 BaseURL: %s", client.BaseURL)
|
||||
}
|
||||
if customModel != "" {
|
||||
client.Model = customModel
|
||||
log.Printf("🔧 [MCP] DeepSeek 使用自定义 Model: %s", customModel)
|
||||
} else {
|
||||
client.Model = "deepseek-chat"
|
||||
log.Printf("🔧 [MCP] DeepSeek 使用默认 Model: %s", client.Model)
|
||||
}
|
||||
// 打印 API Key 的前后各4位用于验证
|
||||
if len(apiKey) > 8 {
|
||||
log.Printf("🔧 [MCP] DeepSeek API Key: %s...%s", apiKey[:4], apiKey[len(apiKey)-4:])
|
||||
}
|
||||
}
|
||||
|
||||
// SetQwenAPIKey 设置阿里云Qwen API密钥
|
||||
func (cfg *Client) SetQwenAPIKey(apiKey, secretKey string) {
|
||||
cfg.Provider = ProviderQwen
|
||||
cfg.APIKey = apiKey
|
||||
cfg.SecretKey = secretKey
|
||||
cfg.BaseURL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
cfg.Model = "qwen-plus" // 可选: qwen-turbo, qwen-plus, qwen-max
|
||||
// customURL 为空时使用默认URL,customModel 为空时使用默认模型
|
||||
func (client *Client) SetQwenAPIKey(apiKey string, customURL string, customModel string) {
|
||||
client.Provider = ProviderQwen
|
||||
client.APIKey = apiKey
|
||||
if customURL != "" {
|
||||
client.BaseURL = customURL
|
||||
log.Printf("🔧 [MCP] Qwen 使用自定义 BaseURL: %s", customURL)
|
||||
} else {
|
||||
client.BaseURL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
log.Printf("🔧 [MCP] Qwen 使用默认 BaseURL: %s", client.BaseURL)
|
||||
}
|
||||
if customModel != "" {
|
||||
client.Model = customModel
|
||||
log.Printf("🔧 [MCP] Qwen 使用自定义 Model: %s", customModel)
|
||||
} else {
|
||||
client.Model = "qwen-plus" // 可选: qwen-turbo, qwen-plus, qwen-max
|
||||
log.Printf("🔧 [MCP] Qwen 使用默认 Model: %s", client.Model)
|
||||
}
|
||||
// 打印 API Key 的前后各4位用于验证
|
||||
if len(apiKey) > 8 {
|
||||
log.Printf("🔧 [MCP] Qwen API Key: %s...%s", apiKey[:4], apiKey[len(apiKey)-4:])
|
||||
}
|
||||
}
|
||||
|
||||
// SetCustomAPI 设置自定义OpenAI兼容API
|
||||
func (cfg *Client) SetCustomAPI(apiURL, apiKey, modelName string) {
|
||||
cfg.Provider = ProviderCustom
|
||||
cfg.APIKey = apiKey
|
||||
func (client *Client) SetCustomAPI(apiURL, apiKey, modelName string) {
|
||||
client.Provider = ProviderCustom
|
||||
client.APIKey = apiKey
|
||||
|
||||
// 检查URL是否以#结尾,如果是则使用完整URL(不添加/chat/completions)
|
||||
if strings.HasSuffix(apiURL, "#") {
|
||||
cfg.BaseURL = strings.TrimSuffix(apiURL, "#")
|
||||
cfg.UseFullURL = true
|
||||
client.BaseURL = strings.TrimSuffix(apiURL, "#")
|
||||
client.UseFullURL = true
|
||||
} else {
|
||||
cfg.BaseURL = apiURL
|
||||
cfg.UseFullURL = false
|
||||
client.BaseURL = apiURL
|
||||
client.UseFullURL = false
|
||||
}
|
||||
|
||||
cfg.Model = modelName
|
||||
cfg.Timeout = 120 * time.Second
|
||||
client.Model = modelName
|
||||
client.Timeout = 120 * time.Second
|
||||
}
|
||||
|
||||
// SetClient 设置完整的AI配置(高级用户)
|
||||
func (cfg *Client) SetClient(Client Client) {
|
||||
func (client *Client) SetClient(Client Client) {
|
||||
if Client.Timeout == 0 {
|
||||
Client.Timeout = 30 * time.Second
|
||||
}
|
||||
cfg = &Client
|
||||
client = &Client
|
||||
}
|
||||
|
||||
// CallWithMessages 使用 system + user prompt 调用AI API(推荐)
|
||||
func (cfg *Client) CallWithMessages(systemPrompt, userPrompt string) (string, error) {
|
||||
if cfg.APIKey == "" {
|
||||
func (client *Client) CallWithMessages(systemPrompt, userPrompt string) (string, error) {
|
||||
if client.APIKey == "" {
|
||||
return "", fmt.Errorf("AI API密钥未设置,请先调用 SetDeepSeekAPIKey() 或 SetQwenAPIKey()")
|
||||
}
|
||||
|
||||
@@ -99,7 +131,7 @@ func (cfg *Client) CallWithMessages(systemPrompt, userPrompt string) (string, er
|
||||
fmt.Printf("⚠️ AI API调用失败,正在重试 (%d/%d)...\n", attempt, maxRetries)
|
||||
}
|
||||
|
||||
result, err := cfg.callOnce(systemPrompt, userPrompt)
|
||||
result, err := client.callOnce(systemPrompt, userPrompt)
|
||||
if err == nil {
|
||||
if attempt > 1 {
|
||||
fmt.Printf("✓ AI API重试成功\n")
|
||||
@@ -125,7 +157,17 @@ func (cfg *Client) CallWithMessages(systemPrompt, userPrompt string) (string, er
|
||||
}
|
||||
|
||||
// callOnce 单次调用AI API(内部使用)
|
||||
func (cfg *Client) callOnce(systemPrompt, userPrompt string) (string, error) {
|
||||
func (client *Client) callOnce(systemPrompt, userPrompt string) (string, error) {
|
||||
// 打印当前 AI 配置
|
||||
log.Printf("📡 [MCP] AI 请求配置:")
|
||||
log.Printf(" Provider: %s", client.Provider)
|
||||
log.Printf(" BaseURL: %s", client.BaseURL)
|
||||
log.Printf(" Model: %s", client.Model)
|
||||
log.Printf(" UseFullURL: %v", client.UseFullURL)
|
||||
if len(client.APIKey) > 8 {
|
||||
log.Printf(" API Key: %s...%s", client.APIKey[:4], client.APIKey[len(client.APIKey)-4:])
|
||||
}
|
||||
|
||||
// 构建 messages 数组
|
||||
messages := []map[string]string{}
|
||||
|
||||
@@ -145,7 +187,7 @@ func (cfg *Client) callOnce(systemPrompt, userPrompt string) (string, error) {
|
||||
|
||||
// 构建请求体
|
||||
requestBody := map[string]interface{}{
|
||||
"model": cfg.Model,
|
||||
"model": client.Model,
|
||||
"messages": messages,
|
||||
"temperature": 0.5, // 降低temperature以提高JSON格式稳定性
|
||||
"max_tokens": 2000,
|
||||
@@ -161,13 +203,15 @@ func (cfg *Client) callOnce(systemPrompt, userPrompt string) (string, error) {
|
||||
|
||||
// 创建HTTP请求
|
||||
var url string
|
||||
if cfg.UseFullURL {
|
||||
if client.UseFullURL {
|
||||
// 使用完整URL,不添加/chat/completions
|
||||
url = cfg.BaseURL
|
||||
url = client.BaseURL
|
||||
} else {
|
||||
// 默认行为:添加/chat/completions
|
||||
url = fmt.Sprintf("%s/chat/completions", cfg.BaseURL)
|
||||
url = fmt.Sprintf("%s/chat/completions", client.BaseURL)
|
||||
}
|
||||
log.Printf("📡 [MCP] 请求 URL: %s", url)
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建请求失败: %w", err)
|
||||
@@ -176,20 +220,20 @@ func (cfg *Client) callOnce(systemPrompt, userPrompt string) (string, error) {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 根据不同的Provider设置认证方式
|
||||
switch cfg.Provider {
|
||||
switch client.Provider {
|
||||
case ProviderDeepSeek:
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cfg.APIKey))
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.APIKey))
|
||||
case ProviderQwen:
|
||||
// 阿里云Qwen使用API-Key认证
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cfg.APIKey))
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.APIKey))
|
||||
// 注意:如果使用的不是兼容模式,可能需要不同的认证方式
|
||||
default:
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cfg.APIKey))
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.APIKey))
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{Timeout: cfg.Timeout}
|
||||
resp, err := client.Do(req)
|
||||
httpClient := &http.Client{Timeout: client.Timeout}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("发送请求失败: %w", err)
|
||||
}
|
||||
|
||||
114
prompts/default.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
你是专业的加密货币交易AI,在合约市场进行自主交易。
|
||||
|
||||
# 核心目标
|
||||
|
||||
最大化夏普比率(Sharpe Ratio)
|
||||
|
||||
夏普比率 = 平均收益 / 收益波动率
|
||||
|
||||
这意味着:
|
||||
- 高质量交易(高胜率、大盈亏比)→ 提升夏普
|
||||
- 稳定收益、控制回撤 → 提升夏普
|
||||
- 耐心持仓、让利润奔跑 → 提升夏普
|
||||
- 频繁交易、小盈小亏 → 增加波动,严重降低夏普
|
||||
- 过度交易、手续费损耗 → 直接亏损
|
||||
- 过早平仓、频繁进出 → 错失大行情
|
||||
|
||||
关键认知: 系统每3分钟扫描一次,但不意味着每次都要交易!
|
||||
大多数时候应该是 `wait` 或 `hold`,只在极佳机会时才开仓。
|
||||
|
||||
# 交易哲学 & 最佳实践
|
||||
|
||||
## 核心原则:
|
||||
|
||||
资金保全第一:保护资本比追求收益更重要
|
||||
|
||||
纪律胜于情绪:执行你的退出方案,不随意移动止损或目标
|
||||
|
||||
质量优于数量:少量高信念交易胜过大量低信念交易
|
||||
|
||||
适应波动性:根据市场条件调整仓位
|
||||
|
||||
尊重趋势:不要与强趋势作对
|
||||
|
||||
## 常见误区避免:
|
||||
|
||||
过度交易:频繁交易导致费用侵蚀利润
|
||||
|
||||
复仇式交易:亏损后立即加码试图"翻本"
|
||||
|
||||
分析瘫痪:过度等待完美信号,导致失机
|
||||
|
||||
忽视相关性:BTC常引领山寨币,须优先观察BTC
|
||||
|
||||
过度杠杆:放大收益同时放大亏损
|
||||
|
||||
#交易频率认知
|
||||
|
||||
量化标准:
|
||||
- 优秀交易员:每天2-4笔 = 每小时0.1-0.2笔
|
||||
- 过度交易:每小时>2笔 = 严重问题
|
||||
- 最佳节奏:开仓后持有至少30-60分钟
|
||||
|
||||
自查:
|
||||
如果你发现自己每个周期都在交易 → 说明标准太低
|
||||
如果你发现持仓<30分钟就平仓 → 说明太急躁
|
||||
|
||||
# 开仓标准(严格)
|
||||
|
||||
只在强信号时开仓,不确定就观望。
|
||||
|
||||
你拥有的完整数据:
|
||||
- 原始序列:3分钟价格序列(MidPrices数组) + 4小时K线序列
|
||||
- 技术序列:EMA20序列、MACD序列、RSI7序列、RSI14序列
|
||||
- 资金序列:成交量序列、持仓量(OI)序列、资金费率
|
||||
- 筛选标记:AI500评分 / OI_Top排名(如果有标注)
|
||||
|
||||
分析方法(完全由你自主决定):
|
||||
- 自由运用序列数据,你可以做但不限于趋势分析、形态识别、支撑阻力、技术阻力位、斐波那契、波动带计算
|
||||
- 多维度交叉验证(价格+量+OI+指标+序列形态)
|
||||
- 用你认为最有效的方法发现高确定性机会
|
||||
- 综合信心度 ≥ 75 才开仓
|
||||
|
||||
避免低质量信号:
|
||||
- 单一维度(只看一个指标)
|
||||
- 相互矛盾(涨但量萎缩)
|
||||
- 横盘震荡
|
||||
- 刚平仓不久(<15分钟)
|
||||
|
||||
# 夏普比率自我进化
|
||||
|
||||
每次你会收到夏普比率作为绩效反馈(周期级别):
|
||||
|
||||
夏普比率 < -0.5 (持续亏损):
|
||||
→ 停止交易,连续观望至少6个周期(18分钟)
|
||||
→ 深度反思:
|
||||
• 交易频率过高?(每小时>2次就是过度)
|
||||
• 持仓时间过短?(<30分钟就是过早平仓)
|
||||
• 信号强度不足?(信心度<75)
|
||||
夏普比率 -0.5 ~ 0 (轻微亏损):
|
||||
→ 严格控制:只做信心度>80的交易
|
||||
→ 减少交易频率:每小时最多1笔新开仓
|
||||
→ 耐心持仓:至少持有30分钟以上
|
||||
|
||||
夏普比率 0 ~ 0.7 (正收益):
|
||||
→ 维持当前策略
|
||||
|
||||
夏普比率 > 0.7 (优异表现):
|
||||
→ 可适度扩大仓位
|
||||
|
||||
关键: 夏普比率是唯一指标,它会自然惩罚频繁交易和过度进出。
|
||||
|
||||
#决策流程
|
||||
|
||||
1. 分析夏普比率: 当前策略是否有效?需要调整吗?
|
||||
2. 评估持仓: 趋势是否改变?是否该止盈/止损?
|
||||
3. 寻找新机会: 有强信号吗?多空机会?
|
||||
4. 输出决策: 思维链分析 + JSON
|
||||
|
||||
---
|
||||
|
||||
记住:
|
||||
- 目标是夏普比率,不是交易频率
|
||||
- 宁可错过,不做低质量交易
|
||||
- 风险回报比1:3是底线
|
||||
223
prompts/nof1.txt
Normal file
@@ -0,0 +1,223 @@
|
||||
# ROLE & IDENTITY
|
||||
|
||||
You are an autonomous cryptocurrency trading agent operating in live markets on the Hyperliquid decentralized exchange.
|
||||
|
||||
Your mission: Maximize risk-adjusted returns (PnL) through systematic, disciplined trading.
|
||||
|
||||
---
|
||||
|
||||
# TRADING ENVIRONMENT SPECIFICATION
|
||||
|
||||
## Trading Mechanics
|
||||
|
||||
- **Contract Type**: Perpetual futures (no expiration)
|
||||
- **Funding Mechanism**:
|
||||
- Positive funding rate = longs pay shorts (bullish market sentiment)
|
||||
- Negative funding rate = shorts pay longs (bearish market sentiment)
|
||||
- **Trading Fees**: ~0.02-0.05% per trade (maker/taker fees apply)
|
||||
- **Slippage**: Expect 0.01-0.1% on market orders depending on size
|
||||
|
||||
---
|
||||
|
||||
# ACTION SPACE DEFINITION
|
||||
|
||||
You have exactly FOUR possible actions per decision cycle:
|
||||
|
||||
1. **buy_to_enter**: Open a new LONG position (bet on price appreciation)
|
||||
- Use when: Bullish technical setup, positive momentum, risk-reward favors upside
|
||||
|
||||
2. **sell_to_enter**: Open a new SHORT position (bet on price depreciation)
|
||||
- Use when: Bearish technical setup, negative momentum, risk-reward favors downside
|
||||
|
||||
3. **hold**: Maintain current positions without modification
|
||||
- Use when: Existing positions are performing as expected, or no clear edge exists
|
||||
|
||||
4. **close**: Exit an existing position entirely
|
||||
- Use when: Profit target reached, stop loss triggered, or thesis invalidated
|
||||
|
||||
## Position Management Constraints
|
||||
|
||||
- **NO pyramiding**: Cannot add to existing positions (one position per coin maximum)
|
||||
- **NO hedging**: Cannot hold both long and short positions in the same asset
|
||||
- **NO partial exits**: Must close entire position at once
|
||||
|
||||
---
|
||||
|
||||
# POSITION SIZING FRAMEWORK
|
||||
|
||||
Calculate position size using this formula:
|
||||
|
||||
Position Size (USD) = Available Cash × Leverage × Allocation %
|
||||
Position Size (Coins) = Position Size (USD) / Current Price
|
||||
|
||||
## Sizing Considerations
|
||||
|
||||
1. **Available Capital**: Only use available cash (not account value)
|
||||
2. **Leverage Selection**:
|
||||
- Low conviction (0.3-0.5): Use 1-3x leverage
|
||||
- Medium conviction (0.5-0.7): Use 3-8x leverage
|
||||
- High conviction (0.7-1.0): Use 8-20x leverage
|
||||
3. **Diversification**: Avoid concentrating >40% of capital in single position
|
||||
4. **Fee Impact**: On positions <$500, fees will materially erode profits
|
||||
5. **Liquidation Risk**: Ensure liquidation price is >15% away from entry
|
||||
|
||||
---
|
||||
|
||||
# RISK MANAGEMENT PROTOCOL (MANDATORY)
|
||||
|
||||
For EVERY trade decision, you MUST specify:
|
||||
|
||||
1. **profit_target** (float): Exact price level to take profits
|
||||
- Should offer minimum 2:1 reward-to-risk ratio
|
||||
- Based on technical resistance levels, Fibonacci extensions, or volatility bands
|
||||
|
||||
2. **stop_loss** (float): Exact price level to cut losses
|
||||
- Should limit loss to 1-3% of account value per trade
|
||||
- Placed beyond recent support/resistance to avoid premature stops
|
||||
|
||||
3. **invalidation_condition** (string): Specific market signal that voids your thesis
|
||||
- Examples: "BTC breaks below $100k", "RSI drops below 30", "Funding rate flips negative"
|
||||
- Must be objective and observable
|
||||
|
||||
4. **confidence** (float, 0-1): Your conviction level in this trade
|
||||
- 0.0-0.3: Low confidence (avoid trading or use minimal size)
|
||||
- 0.3-0.6: Moderate confidence (standard position sizing)
|
||||
- 0.6-0.8: High confidence (larger position sizing acceptable)
|
||||
- 0.8-1.0: Very high confidence (use cautiously, beware overconfidence)
|
||||
|
||||
5. **risk_usd** (float): Dollar amount at risk (distance from entry to stop loss)
|
||||
- Calculate as: |Entry Price - Stop Loss| × Position Size × Leverage
|
||||
|
||||
|
||||
# PERFORMANCE METRICS & FEEDBACK
|
||||
|
||||
You will receive your Sharpe Ratio at each invocation:
|
||||
|
||||
Sharpe Ratio = (Average Return - Risk-Free Rate) / Standard Deviation of Returns
|
||||
|
||||
Interpretation:
|
||||
- < 0: Losing money on average
|
||||
- 0-1: Positive returns but high volatility
|
||||
- 1-2: Good risk-adjusted performance
|
||||
- > 2: Excellent risk-adjusted performance
|
||||
|
||||
Use Sharpe Ratio to calibrate your behavior:
|
||||
- Low Sharpe → Reduce position sizes, tighten stops, be more selective
|
||||
- High Sharpe → Current strategy is working, maintain discipline
|
||||
|
||||
---
|
||||
|
||||
# DATA INTERPRETATION GUIDELINES
|
||||
|
||||
## Technical Indicators Provided
|
||||
|
||||
**EMA (Exponential Moving Average)**: Trend direction
|
||||
- Price > EMA = Uptrend
|
||||
- Price < EMA = Downtrend
|
||||
|
||||
**MACD (Moving Average Convergence Divergence)**: Momentum
|
||||
- Positive MACD = Bullish momentum
|
||||
- Negative MACD = Bearish momentum
|
||||
|
||||
**RSI (Relative Strength Index)**: Overbought/Oversold conditions
|
||||
- RSI > 70 = Overbought (potential reversal down)
|
||||
- RSI < 30 = Oversold (potential reversal up)
|
||||
- RSI 40-60 = Neutral zone
|
||||
|
||||
**ATR (Average True Range)**: Volatility measurement
|
||||
- Higher ATR = More volatile (wider stops needed)
|
||||
- Lower ATR = Less volatile (tighter stops possible)
|
||||
|
||||
**Open Interest**: Total outstanding contracts
|
||||
- Rising OI + Rising Price = Strong uptrend
|
||||
- Rising OI + Falling Price = Strong downtrend
|
||||
- Falling OI = Trend weakening
|
||||
|
||||
**Funding Rate**: Market sentiment indicator
|
||||
- Positive funding = Bullish sentiment (longs paying shorts)
|
||||
- Negative funding = Bearish sentiment (shorts paying longs)
|
||||
- Extreme funding rates (>0.01%) = Potential reversal signal
|
||||
|
||||
## Data Ordering (CRITICAL)
|
||||
|
||||
⚠️ **ALL PRICE AND INDICATOR DATA IS ORDERED: OLDEST → NEWEST**
|
||||
|
||||
**The LAST element in each array is the MOST RECENT data point.**
|
||||
**The FIRST element is the OLDEST data point.**
|
||||
|
||||
Do NOT confuse the order. This is a common error that leads to incorrect decisions.
|
||||
|
||||
---
|
||||
|
||||
# OPERATIONAL CONSTRAINTS
|
||||
|
||||
## What You DON'T Have Access To
|
||||
|
||||
- No news feeds or social media sentiment
|
||||
- No conversation history (each decision is stateless)
|
||||
- No ability to query external APIs
|
||||
- No access to order book depth beyond mid-price
|
||||
- No ability to place limit orders (market orders only)
|
||||
|
||||
## What You MUST Infer From Data
|
||||
|
||||
- Market narratives and sentiment (from price action + funding rates)
|
||||
- Institutional positioning (from open interest changes)
|
||||
- Trend strength and sustainability (from technical indicators)
|
||||
- Risk-on vs risk-off regime (from correlation across coins)
|
||||
|
||||
---
|
||||
|
||||
# TRADING PHILOSOPHY & BEST PRACTICES
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Capital Preservation First**: Protecting capital is more important than chasing gains
|
||||
2. **Discipline Over Emotion**: Follow your exit plan, don't move stops or targets
|
||||
3. **Quality Over Quantity**: Fewer high-conviction trades beat many low-conviction trades
|
||||
4. **Adapt to Volatility**: Adjust position sizes based on market conditions
|
||||
5. **Respect the Trend**: Don't fight strong directional moves
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
|
||||
- ⚠️ **Overtrading**: Excessive trading erodes capital through fees
|
||||
- ⚠️ **Revenge Trading**: Don't increase size after losses to "make it back"
|
||||
- ⚠️ **Analysis Paralysis**: Don't wait for perfect setups, they don't exist
|
||||
- ⚠️ **Ignoring Correlation**: BTC often leads altcoins, watch BTC first
|
||||
- ⚠️ **Overleveraging**: High leverage amplifies both gains AND losses
|
||||
|
||||
## Decision-Making Framework
|
||||
|
||||
1. Analyze current positions first (are they performing as expected?)
|
||||
2. Check for invalidation conditions on existing trades
|
||||
3. Scan for new opportunities only if capital is available
|
||||
4. Prioritize risk management over profit maximization
|
||||
5. When in doubt, choose "hold" over forcing a trade
|
||||
|
||||
---
|
||||
|
||||
# CONTEXT WINDOW MANAGEMENT
|
||||
|
||||
You have limited context. The prompt contains:
|
||||
- ~10 recent data points per indicator (3-minute intervals)
|
||||
- ~10 recent data points for 4-hour timeframe
|
||||
- Current account state and open positions
|
||||
|
||||
Optimize your analysis:
|
||||
- Focus on most recent 3-5 data points for short-term signals
|
||||
- Use 4-hour data for trend context and support/resistance levels
|
||||
- Don't try to memorize all numbers, identify patterns instead
|
||||
|
||||
---
|
||||
|
||||
# FINAL INSTRUCTIONS
|
||||
|
||||
1. Read the entire user prompt carefully before deciding
|
||||
2. Verify your position sizing math (double-check calculations)
|
||||
3. Ensure your JSON output is valid and complete
|
||||
4. Provide honest confidence scores (don't overstate conviction)
|
||||
5. Be consistent with your exit plans (don't abandon stops prematurely)
|
||||
|
||||
Remember: You are trading with real money in real markets. Every decision has consequences. Trade systematically, manage risk religiously, and let probability work in your favor over time.
|
||||
|
||||
Now, analyze the market data provided below and make your trading decision.
|
||||
413
scripts/pr-check.sh
Executable file
@@ -0,0 +1,413 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🔍 PR Health Check Script
|
||||
# Analyzes your PR and gives suggestions on how to meet the new standards
|
||||
# This script only analyzes and suggests - it won't modify your code
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Counters
|
||||
ISSUES_FOUND=0
|
||||
WARNINGS_FOUND=0
|
||||
PASSED_CHECKS=0
|
||||
|
||||
# Helper functions
|
||||
log_section() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} $1${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════${NC}"
|
||||
}
|
||||
|
||||
log_check() {
|
||||
echo -e "${BLUE}🔍 Checking: $1${NC}"
|
||||
}
|
||||
|
||||
log_pass() {
|
||||
echo -e "${GREEN}✅ PASS: $1${NC}"
|
||||
((PASSED_CHECKS++))
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ WARNING: $1${NC}"
|
||||
((WARNINGS_FOUND++))
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ ISSUE: $1${NC}"
|
||||
((ISSUES_FOUND++))
|
||||
}
|
||||
|
||||
log_suggestion() {
|
||||
echo -e "${CYAN}💡 Suggestion: $1${NC}"
|
||||
}
|
||||
|
||||
log_command() {
|
||||
echo -e "${GREEN} Run: ${NC}$1"
|
||||
}
|
||||
|
||||
# Welcome
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ NOFX PR Health Check ║"
|
||||
echo "║ Analyze your PR and get suggestions ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Check if we're in a git repo
|
||||
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
|
||||
log_error "Not a git repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current branch
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
echo -e "${BLUE}Current branch: ${GREEN}$CURRENT_BRANCH${NC}"
|
||||
|
||||
if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "dev" ]; then
|
||||
log_error "You're on the $CURRENT_BRANCH branch. Please switch to your PR branch."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if upstream exists
|
||||
if ! git remote | grep -q "^upstream$"; then
|
||||
log_warning "Upstream remote not found"
|
||||
log_suggestion "Add upstream remote:"
|
||||
log_command "git remote add upstream https://github.com/tinkle-community/nofx.git"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 1. GIT BRANCH CHECKS
|
||||
# ═══════════════════════════════════════════
|
||||
log_section "1. Git Branch Status"
|
||||
|
||||
# Check if branch is up to date with upstream
|
||||
log_check "Is branch based on latest upstream/dev?"
|
||||
if git remote | grep -q "^upstream$"; then
|
||||
git fetch upstream -q 2>/dev/null || true
|
||||
|
||||
if git merge-base --is-ancestor upstream/dev HEAD 2>/dev/null; then
|
||||
log_pass "Branch is up to date with upstream/dev"
|
||||
else
|
||||
log_error "Branch is not based on latest upstream/dev"
|
||||
log_suggestion "Rebase your branch:"
|
||||
log_command "git fetch upstream && git rebase upstream/dev"
|
||||
echo ""
|
||||
fi
|
||||
else
|
||||
log_warning "Cannot check - upstream remote not configured"
|
||||
fi
|
||||
|
||||
# Check for merge conflicts
|
||||
log_check "Any merge conflicts?"
|
||||
if git diff --check > /dev/null 2>&1; then
|
||||
log_pass "No merge conflicts detected"
|
||||
else
|
||||
log_error "Merge conflicts detected"
|
||||
log_suggestion "Resolve conflicts and commit"
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 2. COMMIT MESSAGE CHECKS
|
||||
# ═══════════════════════════════════════════
|
||||
log_section "2. Commit Messages"
|
||||
|
||||
# Get commits in this branch (not in upstream/dev)
|
||||
if git remote | grep -q "^upstream$"; then
|
||||
COMMITS=$(git log upstream/dev..HEAD --oneline 2>/dev/null || git log --oneline -10)
|
||||
else
|
||||
COMMITS=$(git log --oneline -10)
|
||||
fi
|
||||
|
||||
COMMIT_COUNT=$(echo "$COMMITS" | wc -l | tr -d ' ')
|
||||
echo -e "${BLUE}Found $COMMIT_COUNT commit(s) in your branch${NC}"
|
||||
echo ""
|
||||
|
||||
# Check each commit message
|
||||
echo "$COMMITS" | while read -r line; do
|
||||
COMMIT_MSG=$(echo "$line" | cut -d' ' -f2-)
|
||||
|
||||
# Check if follows conventional commits
|
||||
if echo "$COMMIT_MSG" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|chore|ci|security)(\(.+\))?: .+"; then
|
||||
log_pass "\"$COMMIT_MSG\""
|
||||
else
|
||||
log_warning "\"$COMMIT_MSG\""
|
||||
log_suggestion "Should follow format: type(scope): description"
|
||||
echo " Examples:"
|
||||
echo " - feat(exchange): add OKX integration"
|
||||
echo " - fix(trader): resolve position bug"
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
|
||||
# Suggest PR title based on commits
|
||||
echo ""
|
||||
log_check "Suggested PR title:"
|
||||
SUGGESTED_TITLE=$(git log --pretty=%s upstream/dev..HEAD 2>/dev/null | head -1 || git log --pretty=%s -1)
|
||||
echo -e "${GREEN} \"$SUGGESTED_TITLE\"${NC}"
|
||||
echo ""
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 3. CODE QUALITY - BACKEND (Go)
|
||||
# ═══════════════════════════════════════════
|
||||
if find . -name "*.go" -not -path "./vendor/*" -not -path "./.git/*" | grep -q .; then
|
||||
log_section "3. Backend Code Quality (Go)"
|
||||
|
||||
# Check if Go is installed
|
||||
if ! command -v go &> /dev/null; then
|
||||
log_warning "Go not installed - skipping backend checks"
|
||||
log_suggestion "Install Go: https://go.dev/doc/install"
|
||||
else
|
||||
# Check go fmt
|
||||
log_check "Go code formatting (go fmt)"
|
||||
UNFORMATTED=$(gofmt -l . 2>/dev/null | grep -v vendor || true)
|
||||
if [ -z "$UNFORMATTED" ]; then
|
||||
log_pass "All Go files are formatted"
|
||||
else
|
||||
log_error "Some files need formatting:"
|
||||
echo "$UNFORMATTED" | head -5 | while read -r file; do
|
||||
echo " - $file"
|
||||
done
|
||||
log_suggestion "Format your code:"
|
||||
log_command "go fmt ./..."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check go vet
|
||||
log_check "Go static analysis (go vet)"
|
||||
if go vet ./... > /tmp/vet-output.txt 2>&1; then
|
||||
log_pass "No issues found by go vet"
|
||||
else
|
||||
log_error "Go vet found issues:"
|
||||
head -10 /tmp/vet-output.txt | sed 's/^/ /'
|
||||
log_suggestion "Fix the issues above"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check tests exist
|
||||
log_check "Do tests exist?"
|
||||
TEST_FILES=$(find . -name "*_test.go" -not -path "./vendor/*" | wc -l)
|
||||
if [ "$TEST_FILES" -gt 0 ]; then
|
||||
log_pass "Found $TEST_FILES test file(s)"
|
||||
else
|
||||
log_warning "No test files found"
|
||||
log_suggestion "Add tests for your changes"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Run tests
|
||||
log_check "Running Go tests..."
|
||||
if go test ./... -v > /tmp/test-output.txt 2>&1; then
|
||||
log_pass "All tests passed"
|
||||
else
|
||||
log_error "Some tests failed:"
|
||||
grep -E "FAIL|ERROR" /tmp/test-output.txt | head -10 | sed 's/^/ /' || true
|
||||
log_suggestion "Fix failing tests:"
|
||||
log_command "go test ./... -v"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 4. CODE QUALITY - FRONTEND
|
||||
# ═══════════════════════════════════════════
|
||||
if [ -d "web" ]; then
|
||||
log_section "4. Frontend Code Quality"
|
||||
|
||||
# Check if npm is installed
|
||||
if ! command -v npm &> /dev/null; then
|
||||
log_warning "npm not installed - skipping frontend checks"
|
||||
log_suggestion "Install Node.js: https://nodejs.org/"
|
||||
else
|
||||
cd web
|
||||
|
||||
# Check if node_modules exists
|
||||
if [ ! -d "node_modules" ]; then
|
||||
log_warning "Dependencies not installed"
|
||||
log_suggestion "Install dependencies:"
|
||||
log_command "cd web && npm install"
|
||||
cd ..
|
||||
else
|
||||
# Check linting
|
||||
log_check "Frontend linting"
|
||||
if npm run lint > /tmp/lint-output.txt 2>&1; then
|
||||
log_pass "No linting issues"
|
||||
else
|
||||
log_error "Linting issues found:"
|
||||
tail -20 /tmp/lint-output.txt | sed 's/^/ /' || true
|
||||
log_suggestion "Fix linting issues:"
|
||||
log_command "cd web && npm run lint -- --fix"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check type errors
|
||||
log_check "TypeScript type checking"
|
||||
if npm run type-check > /tmp/typecheck-output.txt 2>&1; then
|
||||
log_pass "No type errors"
|
||||
else
|
||||
log_error "Type errors found:"
|
||||
tail -20 /tmp/typecheck-output.txt | sed 's/^/ /' || true
|
||||
log_suggestion "Fix type errors in your code"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check build
|
||||
log_check "Frontend build"
|
||||
if npm run build > /tmp/build-output.txt 2>&1; then
|
||||
log_pass "Build successful"
|
||||
else
|
||||
log_error "Build failed:"
|
||||
tail -20 /tmp/build-output.txt | sed 's/^/ /' || true
|
||||
log_suggestion "Fix build errors"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
cd ..
|
||||
fi
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 5. PR SIZE CHECK
|
||||
# ═══════════════════════════════════════════
|
||||
log_section "5. PR Size"
|
||||
|
||||
if git remote | grep -q "^upstream$"; then
|
||||
ADDED=$(git diff --numstat upstream/dev...HEAD | awk '{sum+=$1} END {print sum+0}')
|
||||
DELETED=$(git diff --numstat upstream/dev...HEAD | awk '{sum+=$2} END {print sum+0}')
|
||||
TOTAL=$((ADDED + DELETED))
|
||||
FILES_CHANGED=$(git diff --name-only upstream/dev...HEAD | wc -l)
|
||||
|
||||
echo -e "${BLUE}Lines changed: ${GREEN}+$ADDED ${RED}-$DELETED ${NC}(total: $TOTAL)"
|
||||
echo -e "${BLUE}Files changed: ${GREEN}$FILES_CHANGED${NC}"
|
||||
echo ""
|
||||
|
||||
if [ "$TOTAL" -lt 100 ]; then
|
||||
log_pass "Small PR (<100 lines) - ideal for quick review"
|
||||
elif [ "$TOTAL" -lt 500 ]; then
|
||||
log_pass "Medium PR (100-500 lines) - reasonable size"
|
||||
elif [ "$TOTAL" -lt 1000 ]; then
|
||||
log_warning "Large PR (500-1000 lines) - consider splitting"
|
||||
log_suggestion "Breaking into smaller PRs makes review faster"
|
||||
else
|
||||
log_error "Very large PR (>1000 lines) - strongly consider splitting"
|
||||
log_suggestion "Split into multiple smaller PRs, each with a focused change"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 6. DOCUMENTATION CHECK
|
||||
# ═══════════════════════════════════════════
|
||||
log_section "6. Documentation"
|
||||
|
||||
# Check if README or docs were updated
|
||||
log_check "Documentation updates"
|
||||
if git remote | grep -q "^upstream$"; then
|
||||
DOC_CHANGES=$(git diff --name-only upstream/dev...HEAD | grep -E "\.(md|txt)$" || true)
|
||||
|
||||
if [ -n "$DOC_CHANGES" ]; then
|
||||
log_pass "Documentation files updated"
|
||||
echo "$DOC_CHANGES" | sed 's/^/ - /'
|
||||
else
|
||||
# Check if this is a feature/fix that might need docs
|
||||
COMMIT_TYPES=$(git log --pretty=%s upstream/dev..HEAD | grep -oE "^(feat|fix)" || true)
|
||||
if [ -n "$COMMIT_TYPES" ]; then
|
||||
log_warning "No documentation updates found"
|
||||
log_suggestion "Consider updating docs if your changes affect usage"
|
||||
echo ""
|
||||
else
|
||||
log_pass "No documentation update needed"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 7. ROADMAP ALIGNMENT
|
||||
# ═══════════════════════════════════════════
|
||||
log_section "7. Roadmap Alignment"
|
||||
|
||||
log_check "Does your PR align with the roadmap?"
|
||||
echo ""
|
||||
echo "Current priorities (Phase 1):"
|
||||
echo " ✅ Security enhancements"
|
||||
echo " ✅ AI model integrations"
|
||||
echo " ✅ Exchange integrations (OKX, Bybit, Lighter, EdgeX)"
|
||||
echo " ✅ UI/UX improvements"
|
||||
echo " ✅ Performance optimizations"
|
||||
echo " ✅ Bug fixes"
|
||||
echo ""
|
||||
log_suggestion "Check roadmap: https://github.com/tinkle-community/nofx/blob/dev/docs/roadmap/README.md"
|
||||
echo ""
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# FINAL REPORT
|
||||
# ═══════════════════════════════════════════
|
||||
log_section "Summary Report"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ Passed checks: $PASSED_CHECKS${NC}"
|
||||
echo -e "${YELLOW}⚠️ Warnings: $WARNINGS_FOUND${NC}"
|
||||
echo -e "${RED}❌ Issues found: $ISSUES_FOUND${NC}"
|
||||
echo ""
|
||||
|
||||
# Overall assessment
|
||||
if [ "$ISSUES_FOUND" -eq 0 ] && [ "$WARNINGS_FOUND" -eq 0 ]; then
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ 🎉 Excellent! Your PR looks great! ║"
|
||||
echo "║ Ready to submit or update your PR ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
elif [ "$ISSUES_FOUND" -eq 0 ]; then
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ 👍 Good! Minor warnings found ║"
|
||||
echo "║ Consider addressing warnings ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
elif [ "$ISSUES_FOUND" -le 3 ]; then
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ ⚠️ Issues found - Please fix ║"
|
||||
echo "║ See suggestions above ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
else
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ ❌ Multiple issues found ║"
|
||||
echo "║ Please address issues before submitting ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📖 Next steps:"
|
||||
echo ""
|
||||
|
||||
if [ "$ISSUES_FOUND" -gt 0 ] || [ "$WARNINGS_FOUND" -gt 0 ]; then
|
||||
echo "1. Fix the issues and warnings listed above"
|
||||
echo "2. Run this script again to verify: ./scripts/pr-check.sh"
|
||||
echo "3. Commit your fixes"
|
||||
echo "4. Push to your PR: git push origin $CURRENT_BRANCH"
|
||||
else
|
||||
echo "1. Push your changes: git push origin $CURRENT_BRANCH"
|
||||
echo "2. Create or update your PR on GitHub"
|
||||
echo "3. Wait for automated CI checks"
|
||||
echo "4. Address reviewer feedback"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📚 Resources:"
|
||||
echo " - Contributing Guide: https://github.com/tinkle-community/nofx/blob/dev/CONTRIBUTING.md"
|
||||
echo " - Migration Guide: https://github.com/tinkle-community/nofx/blob/dev/docs/community/MIGRATION_ANNOUNCEMENT.md"
|
||||
echo ""
|
||||
|
||||
# Cleanup temp files
|
||||
rm -f /tmp/vet-output.txt /tmp/test-output.txt /tmp/lint-output.txt /tmp/typecheck-output.txt /tmp/build-output.txt
|
||||
|
||||
echo "✨ Analysis complete! Good luck with your PR! 🚀"
|
||||
echo ""
|
||||
335
scripts/pr-fix.sh
Executable file
@@ -0,0 +1,335 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🔄 PR Migration Script for Contributors
|
||||
# This script helps you migrate your PR to the new format
|
||||
# Run this in your local fork to update your PR automatically
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Helper functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
confirm() {
|
||||
read -p "$(echo -e ${YELLOW}"$1 (y/N): "${NC})" -n 1 -r
|
||||
echo
|
||||
[[ $REPLY =~ ^[Yy]$ ]]
|
||||
}
|
||||
|
||||
# Welcome message
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ NOFX PR Migration Tool ║"
|
||||
echo "║ Migrate your PR to the new format ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Check if we're in a git repo
|
||||
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
|
||||
log_error "Not a git repository. Please run this from your NOFX fork."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check current branch
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
log_info "Current branch: $CURRENT_BRANCH"
|
||||
|
||||
if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "dev" ]; then
|
||||
log_warning "You're on the $CURRENT_BRANCH branch."
|
||||
log_info "This script should be run on your PR branch."
|
||||
|
||||
# List branches
|
||||
log_info "Your branches:"
|
||||
git branch
|
||||
|
||||
echo ""
|
||||
read -p "Enter your PR branch name: " PR_BRANCH
|
||||
|
||||
if [ -z "$PR_BRANCH" ]; then
|
||||
log_error "No branch specified. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git checkout "$PR_BRANCH" || {
|
||||
log_error "Failed to checkout branch $PR_BRANCH"
|
||||
exit 1
|
||||
}
|
||||
|
||||
CURRENT_BRANCH="$PR_BRANCH"
|
||||
fi
|
||||
|
||||
log_success "Working on branch: $CURRENT_BRANCH"
|
||||
|
||||
echo ""
|
||||
log_info "What this script will do:"
|
||||
echo " 1. ✅ Verify you're rebased on latest upstream/dev"
|
||||
echo " 2. ✅ Check and format Go code (go fmt)"
|
||||
echo " 3. ✅ Run Go linting (go vet)"
|
||||
echo " 4. ✅ Run Go tests"
|
||||
echo " 5. ✅ Check frontend code (if modified)"
|
||||
echo " 6. ✅ Give you feedback and suggestions"
|
||||
echo ""
|
||||
log_warning "Make sure you've already run: git fetch upstream && git rebase upstream/dev"
|
||||
echo ""
|
||||
|
||||
if ! confirm "Continue with migration?"; then
|
||||
log_info "Migration cancelled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Step 1: Verify upstream sync
|
||||
echo ""
|
||||
log_info "Step 1: Verifying upstream sync..."
|
||||
|
||||
# Check if upstream remote exists
|
||||
if ! git remote | grep -q "^upstream$"; then
|
||||
log_warning "Upstream remote not found. Adding it..."
|
||||
git remote add upstream https://github.com/tinkle-community/nofx.git
|
||||
git fetch upstream
|
||||
log_success "Added upstream remote"
|
||||
fi
|
||||
|
||||
# Check if we're up to date with upstream/dev
|
||||
if git merge-base --is-ancestor upstream/dev HEAD; then
|
||||
log_success "Your branch is up to date with upstream/dev"
|
||||
else
|
||||
log_warning "Your branch is not based on latest upstream/dev"
|
||||
log_info "Please run first: git fetch upstream && git rebase upstream/dev"
|
||||
|
||||
if confirm "Try to rebase now?"; then
|
||||
git fetch upstream
|
||||
if git rebase upstream/dev; then
|
||||
log_success "Successfully rebased on upstream/dev"
|
||||
else
|
||||
log_error "Rebase failed. Please resolve conflicts manually."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_warning "Skipping rebase. Results may not be accurate."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 2: Backend checks (if Go files exist)
|
||||
if find . -name "*.go" -not -path "./vendor/*" | grep -q .; then
|
||||
echo ""
|
||||
log_info "Step 2: Running backend checks..."
|
||||
|
||||
# Check if Go is installed
|
||||
if ! command -v go &> /dev/null; then
|
||||
log_warning "Go not found. Skipping backend checks."
|
||||
log_info "Install Go: https://go.dev/doc/install"
|
||||
else
|
||||
# Format Go code
|
||||
log_info "Formatting Go code..."
|
||||
if go fmt ./...; then
|
||||
log_success "Go code formatted"
|
||||
|
||||
# Check if there are changes
|
||||
if ! git diff --quiet; then
|
||||
log_info "Formatting created changes. Committing..."
|
||||
git add .
|
||||
git commit -m "chore: format Go code with go fmt" || true
|
||||
fi
|
||||
else
|
||||
log_warning "Go formatting had issues (non-critical)"
|
||||
fi
|
||||
|
||||
# Run go vet
|
||||
log_info "Running go vet..."
|
||||
if go vet ./...; then
|
||||
log_success "Go vet passed"
|
||||
else
|
||||
log_warning "Go vet found issues. Please review them."
|
||||
if confirm "Continue anyway?"; then
|
||||
log_info "Continuing..."
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run tests
|
||||
log_info "Running Go tests..."
|
||||
if go test ./...; then
|
||||
log_success "All Go tests passed"
|
||||
else
|
||||
log_warning "Some tests failed. Please fix them before pushing."
|
||||
if confirm "Continue anyway?"; then
|
||||
log_info "Continuing..."
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_info "Step 2: No Go files found, skipping backend checks"
|
||||
fi
|
||||
|
||||
# Step 3: Frontend checks (if web directory exists)
|
||||
if [ -d "web" ]; then
|
||||
echo ""
|
||||
log_info "Step 3: Running frontend checks..."
|
||||
|
||||
# Check if npm is installed
|
||||
if ! command -v npm &> /dev/null; then
|
||||
log_warning "npm not found. Skipping frontend checks."
|
||||
log_info "Install Node.js: https://nodejs.org/"
|
||||
else
|
||||
cd web
|
||||
|
||||
# Install dependencies if needed
|
||||
if [ ! -d "node_modules" ]; then
|
||||
log_info "Installing dependencies..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
# Run linter
|
||||
log_info "Running linter..."
|
||||
if npm run lint; then
|
||||
log_success "Linting passed"
|
||||
else
|
||||
log_warning "Linting found issues"
|
||||
log_info "Attempting to auto-fix..."
|
||||
npm run lint -- --fix || true
|
||||
|
||||
# Commit fixes if any
|
||||
if ! git diff --quiet; then
|
||||
git add .
|
||||
git commit -m "chore: fix linting issues" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Type check
|
||||
log_info "Running type check..."
|
||||
if npm run type-check; then
|
||||
log_success "Type checking passed"
|
||||
else
|
||||
log_warning "Type checking found issues. Please fix them."
|
||||
fi
|
||||
|
||||
# Build
|
||||
log_info "Testing build..."
|
||||
if npm run build; then
|
||||
log_success "Build successful"
|
||||
else
|
||||
log_error "Build failed. Please fix build errors."
|
||||
cd ..
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
log_info "Step 3: No frontend changes, skipping frontend checks"
|
||||
fi
|
||||
|
||||
# Step 4: Check PR title format
|
||||
echo ""
|
||||
log_info "Step 4: Checking PR title format..."
|
||||
|
||||
# Get the commit messages to suggest a title
|
||||
COMMITS=$(git log upstream/dev..HEAD --oneline)
|
||||
COMMIT_COUNT=$(echo "$COMMITS" | wc -l | tr -d ' ')
|
||||
|
||||
log_info "Found $COMMIT_COUNT commit(s) in your PR"
|
||||
|
||||
if [ "$COMMIT_COUNT" -eq 1 ]; then
|
||||
SUGGESTED_TITLE=$(git log -1 --pretty=%s)
|
||||
else
|
||||
SUGGESTED_TITLE=$(git log --pretty=%s upstream/dev..HEAD | head -1)
|
||||
fi
|
||||
|
||||
log_info "Current/suggested title: $SUGGESTED_TITLE"
|
||||
|
||||
# Check if it follows conventional commits
|
||||
if echo "$SUGGESTED_TITLE" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|chore|ci|security)(\(.+\))?: .+"; then
|
||||
log_success "Title follows Conventional Commits format"
|
||||
else
|
||||
log_warning "Title doesn't follow Conventional Commits format"
|
||||
echo ""
|
||||
echo "Conventional Commits format:"
|
||||
echo " <type>(<scope>): <description>"
|
||||
echo ""
|
||||
echo "Types: feat, fix, docs, style, refactor, perf, test, chore, ci, security"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " feat(exchange): add OKX integration"
|
||||
echo " fix(trader): resolve position tracking bug"
|
||||
echo " docs(readme): update installation guide"
|
||||
echo ""
|
||||
|
||||
read -p "Enter new title (or press Enter to keep current): " NEW_TITLE
|
||||
|
||||
if [ -n "$NEW_TITLE" ]; then
|
||||
log_info "You can update the PR title on GitHub after pushing"
|
||||
log_info "Suggested title: $NEW_TITLE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 5: Push changes
|
||||
echo ""
|
||||
log_info "Step 5: Ready to push changes"
|
||||
|
||||
# Check if there are changes to push
|
||||
if git diff upstream/dev..HEAD --quiet; then
|
||||
log_info "No changes to push"
|
||||
else
|
||||
log_info "Changes ready to push to origin/$CURRENT_BRANCH"
|
||||
|
||||
if confirm "Push changes now?"; then
|
||||
log_info "Pushing to origin/$CURRENT_BRANCH..."
|
||||
if git push -f origin "$CURRENT_BRANCH"; then
|
||||
log_success "Successfully pushed changes!"
|
||||
else
|
||||
log_error "Failed to push. You may need to push manually:"
|
||||
echo " git push -f origin $CURRENT_BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_info "Skipped push. You can push manually later:"
|
||||
echo " git push -f origin $CURRENT_BRANCH"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ ✅ Migration Complete! ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
log_success "Your PR has been migrated!"
|
||||
|
||||
echo ""
|
||||
log_info "Next steps:"
|
||||
echo " 1. Check your PR on GitHub"
|
||||
echo " 2. Update PR title if needed (Conventional Commits format)"
|
||||
echo " 3. Wait for CI checks to run"
|
||||
echo " 4. Address any reviewer feedback"
|
||||
echo ""
|
||||
|
||||
log_info "Need help? Ask in the PR comments or Telegram!"
|
||||
log_info "Telegram: https://t.me/nofx_dev_community"
|
||||
|
||||
echo ""
|
||||
log_success "Thank you for contributing to NOFX! 🚀"
|
||||
echo ""
|
||||
63
start.sh
@@ -70,27 +70,63 @@ check_env() {
|
||||
if [ ! -f ".env" ]; then
|
||||
print_warning ".env 不存在,从模板复制..."
|
||||
cp .env.example .env
|
||||
print_info "请编辑 .env 填入你的环境变量配置"
|
||||
print_info "运行: nano .env 或使用其他编辑器"
|
||||
exit 1
|
||||
print_info "✓ 已使用默认环境变量创建 .env"
|
||||
print_info "💡 如需修改端口等设置,可编辑 .env 文件"
|
||||
fi
|
||||
print_success "环境变量文件存在"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Validation: Configuration File (config.json)
|
||||
# Validation: Configuration File (config.json) - BASIC SETTINGS ONLY
|
||||
# ------------------------------------------------------------------------
|
||||
check_config() {
|
||||
if [ ! -f "config.json" ]; then
|
||||
print_warning "config.json 不存在,从模板复制..."
|
||||
cp config.json.example config.json
|
||||
print_info "请编辑 config.json 填入你的 API 密钥"
|
||||
print_info "运行: nano config.json 或使用其他编辑器"
|
||||
exit 1
|
||||
print_info "✓ 已使用默认配置创建 config.json"
|
||||
print_info "💡 如需修改基础设置(杠杆大小、开仓币种、管理员模式、JWT密钥等),可编辑 config.json"
|
||||
print_info "💡 模型/交易所/交易员配置请使用Web界面"
|
||||
fi
|
||||
print_success "配置文件存在"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Utility: Read Environment Variables
|
||||
# ------------------------------------------------------------------------
|
||||
read_env_vars() {
|
||||
if [ -f ".env" ]; then
|
||||
# 读取端口配置,设置默认值
|
||||
NOFX_FRONTEND_PORT=$(grep "^NOFX_FRONTEND_PORT=" .env 2>/dev/null | cut -d'=' -f2 || echo "3000")
|
||||
NOFX_BACKEND_PORT=$(grep "^NOFX_BACKEND_PORT=" .env 2>/dev/null | cut -d'=' -f2 || echo "8080")
|
||||
|
||||
# 去除可能的引号和空格
|
||||
NOFX_FRONTEND_PORT=$(echo "$NOFX_FRONTEND_PORT" | tr -d '"'"'" | tr -d ' ')
|
||||
NOFX_BACKEND_PORT=$(echo "$NOFX_BACKEND_PORT" | tr -d '"'"'" | tr -d ' ')
|
||||
|
||||
# 如果为空则使用默认值
|
||||
NOFX_FRONTEND_PORT=${NOFX_FRONTEND_PORT:-3000}
|
||||
NOFX_BACKEND_PORT=${NOFX_BACKEND_PORT:-8080}
|
||||
else
|
||||
# 如果.env不存在,使用默认端口
|
||||
NOFX_FRONTEND_PORT=3000
|
||||
NOFX_BACKEND_PORT=8080
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Validation: Database File (config.db)
|
||||
# ------------------------------------------------------------------------
|
||||
check_database() {
|
||||
if [ ! -f "config.db" ]; then
|
||||
print_warning "数据库文件不存在,创建空数据库文件..."
|
||||
# 创建空文件以避免Docker创建目录
|
||||
touch config.db
|
||||
print_info "✓ 已创建空数据库文件,系统将在启动时初始化"
|
||||
else
|
||||
print_success "数据库文件存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Build: Frontend (Node.js Based)
|
||||
# ------------------------------------------------------------------------
|
||||
@@ -126,6 +162,9 @@ check_config() {
|
||||
start() {
|
||||
print_info "正在启动 NOFX AI Trading System..."
|
||||
|
||||
# 读取环境变量
|
||||
read_env_vars
|
||||
|
||||
# Auto-build frontend if missing or forced
|
||||
# if [ ! -d "web/dist" ] || [ "$1" == "--build" ]; then
|
||||
# build_frontend
|
||||
@@ -141,8 +180,8 @@ start() {
|
||||
fi
|
||||
|
||||
print_success "服务已启动!"
|
||||
print_info "Web 界面: http://localhost:3000"
|
||||
print_info "API 端点: http://localhost:8080"
|
||||
print_info "Web 界面: http://localhost:${NOFX_FRONTEND_PORT}"
|
||||
print_info "API 端点: http://localhost:${NOFX_BACKEND_PORT}"
|
||||
print_info ""
|
||||
print_info "查看日志: ./start.sh logs"
|
||||
print_info "停止服务: ./start.sh stop"
|
||||
@@ -181,11 +220,14 @@ logs() {
|
||||
# Monitoring: Status
|
||||
# ------------------------------------------------------------------------
|
||||
status() {
|
||||
# 读取环境变量
|
||||
read_env_vars
|
||||
|
||||
print_info "服务状态:"
|
||||
$COMPOSE_CMD ps
|
||||
echo ""
|
||||
print_info "健康检查:"
|
||||
curl -s http://localhost:8080/health | jq '.' || echo "后端未响应"
|
||||
curl -s "http://localhost:${NOFX_BACKEND_PORT}/api/health" | jq '.' || echo "后端未响应"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
@@ -247,6 +289,7 @@ main() {
|
||||
start)
|
||||
check_env
|
||||
check_config
|
||||
check_database
|
||||
start "$2"
|
||||
;;
|
||||
stop)
|
||||
|
||||
@@ -819,6 +819,38 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetMarginMode 设置仓位模式
|
||||
func (t *AsterTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
|
||||
// Aster支持仓位模式设置
|
||||
// API格式与币安相似:CROSSED(全仓) / ISOLATED(逐仓)
|
||||
marginType := "CROSSED"
|
||||
if !isCrossMargin {
|
||||
marginType = "ISOLATED"
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"marginType": marginType,
|
||||
}
|
||||
|
||||
// 使用request方法调用API
|
||||
_, err := t.request("POST", "/fapi/v3/marginType", params)
|
||||
if err != nil {
|
||||
// 如果错误表示无需更改,忽略错误
|
||||
if strings.Contains(err.Error(), "No need to change") ||
|
||||
strings.Contains(err.Error(), "Margin type cannot be changed") {
|
||||
log.Printf(" ✓ %s 仓位模式已是 %s 或有持仓无法更改", symbol, marginType)
|
||||
return nil
|
||||
}
|
||||
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
|
||||
// 不返回错误,让交易继续
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLeverage 设置杠杆倍数
|
||||
func (t *AsterTrader) SetLeverage(symbol string, leverage int) error {
|
||||
params := map[string]interface{}{
|
||||
|
||||
@@ -63,6 +63,16 @@ type AutoTraderConfig struct {
|
||||
MaxDailyLoss float64 // 最大日亏损百分比(提示)
|
||||
MaxDrawdown float64 // 最大回撤百分比(提示)
|
||||
StopTradingTime time.Duration // 触发风控后暂停时长
|
||||
|
||||
// 仓位模式
|
||||
IsCrossMargin bool // true=全仓模式, false=逐仓模式
|
||||
|
||||
// 币种配置
|
||||
DefaultCoins []string // 默认币种列表(从数据库获取)
|
||||
TradingCoins []string // 实际交易币种列表
|
||||
|
||||
// 系统提示词模板
|
||||
SystemPromptTemplate string // 系统提示词模板名称(如 "default", "aggressive")
|
||||
}
|
||||
|
||||
// AutoTrader 自动交易器
|
||||
@@ -77,6 +87,11 @@ type AutoTrader struct {
|
||||
decisionLogger *logger.DecisionLogger // 决策日志记录器
|
||||
initialBalance float64
|
||||
dailyPnL float64
|
||||
customPrompt string // 自定义交易策略prompt
|
||||
overrideBasePrompt bool // 是否覆盖基础prompt
|
||||
systemPromptTemplate string // 系统提示词模板名称
|
||||
defaultCoins []string // 默认币种列表(从数据库获取)
|
||||
tradingCoins []string // 实际交易币种列表
|
||||
lastResetTime time.Time
|
||||
stopUntil time.Time
|
||||
isRunning bool
|
||||
@@ -110,13 +125,21 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
||||
mcpClient.SetCustomAPI(config.CustomAPIURL, config.CustomAPIKey, config.CustomModelName)
|
||||
log.Printf("🤖 [%s] 使用自定义AI API: %s (模型: %s)", config.Name, config.CustomAPIURL, config.CustomModelName)
|
||||
} else if config.UseQwen || config.AIModel == "qwen" {
|
||||
// 使用Qwen
|
||||
mcpClient.SetQwenAPIKey(config.QwenKey, "")
|
||||
log.Printf("🤖 [%s] 使用阿里云Qwen AI", config.Name)
|
||||
// 使用Qwen (支持自定义URL和Model)
|
||||
mcpClient.SetQwenAPIKey(config.QwenKey, config.CustomAPIURL, config.CustomModelName)
|
||||
if config.CustomAPIURL != "" || config.CustomModelName != "" {
|
||||
log.Printf("🤖 [%s] 使用阿里云Qwen AI (自定义URL: %s, 模型: %s)", config.Name, config.CustomAPIURL, config.CustomModelName)
|
||||
} else {
|
||||
log.Printf("🤖 [%s] 使用阿里云Qwen AI", config.Name)
|
||||
}
|
||||
} else {
|
||||
// 默认使用DeepSeek
|
||||
mcpClient.SetDeepSeekAPIKey(config.DeepSeekKey)
|
||||
log.Printf("🤖 [%s] 使用DeepSeek AI", config.Name)
|
||||
// 默认使用DeepSeek (支持自定义URL和Model)
|
||||
mcpClient.SetDeepSeekAPIKey(config.DeepSeekKey, config.CustomAPIURL, config.CustomModelName)
|
||||
if config.CustomAPIURL != "" || config.CustomModelName != "" {
|
||||
log.Printf("🤖 [%s] 使用DeepSeek AI (自定义URL: %s, 模型: %s)", config.Name, config.CustomAPIURL, config.CustomModelName)
|
||||
} else {
|
||||
log.Printf("🤖 [%s] 使用DeepSeek AI", config.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化币种池API
|
||||
@@ -133,6 +156,13 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
||||
var trader Trader
|
||||
var err error
|
||||
|
||||
// 记录仓位模式(通用)
|
||||
marginModeStr := "全仓"
|
||||
if !config.IsCrossMargin {
|
||||
marginModeStr = "逐仓"
|
||||
}
|
||||
log.Printf("📊 [%s] 仓位模式: %s", config.Name, marginModeStr)
|
||||
|
||||
switch config.Exchange {
|
||||
case "binance":
|
||||
log.Printf("🏦 [%s] 使用币安合约交易", config.Name)
|
||||
@@ -162,6 +192,12 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
||||
logDir := fmt.Sprintf("decision_logs/%s", config.ID)
|
||||
decisionLogger := logger.NewDecisionLogger(logDir)
|
||||
|
||||
// 设置默认系统提示词模板
|
||||
systemPromptTemplate := config.SystemPromptTemplate
|
||||
if systemPromptTemplate == "" {
|
||||
systemPromptTemplate = "default" // 默认使用 default 模板
|
||||
}
|
||||
|
||||
return &AutoTrader{
|
||||
id: config.ID,
|
||||
name: config.Name,
|
||||
@@ -172,6 +208,9 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
||||
mcpClient: mcpClient,
|
||||
decisionLogger: decisionLogger,
|
||||
initialBalance: config.InitialBalance,
|
||||
systemPromptTemplate: systemPromptTemplate,
|
||||
defaultCoins: config.DefaultCoins,
|
||||
tradingCoins: config.TradingCoins,
|
||||
lastResetTime: time.Now(),
|
||||
startTime: time.Now(),
|
||||
callCount: 0,
|
||||
@@ -286,11 +325,12 @@ func (at *AutoTrader) runCycle() error {
|
||||
ctx.Account.TotalEquity, ctx.Account.AvailableBalance, ctx.Account.PositionCount)
|
||||
|
||||
// 4. 调用AI获取完整决策
|
||||
log.Println("🤖 正在请求AI分析并决策...")
|
||||
decision, err := decision.GetFullDecision(ctx, at.mcpClient)
|
||||
log.Printf("🤖 正在请求AI分析并决策... [模板: %s]", at.systemPromptTemplate)
|
||||
decision, err := decision.GetFullDecisionWithCustomPrompt(ctx, at.mcpClient, at.customPrompt, at.overrideBasePrompt, at.systemPromptTemplate)
|
||||
|
||||
// 即使有错误,也保存思维链、决策和输入prompt(用于debug)
|
||||
if decision != nil {
|
||||
record.SystemPrompt = decision.SystemPrompt // 保存系统提示词
|
||||
record.InputPrompt = decision.UserPrompt
|
||||
record.CoTTrace = decision.CoTTrace
|
||||
if len(decision.Decisions) > 0 {
|
||||
@@ -303,38 +343,55 @@ func (at *AutoTrader) runCycle() error {
|
||||
record.Success = false
|
||||
record.ErrorMessage = fmt.Sprintf("获取AI决策失败: %v", err)
|
||||
|
||||
// 打印AI思维链(即使有错误)
|
||||
if decision != nil && decision.CoTTrace != "" {
|
||||
log.Printf("\n" + strings.Repeat("-", 70))
|
||||
log.Println("💭 AI思维链分析(错误情况):")
|
||||
log.Println(strings.Repeat("-", 70))
|
||||
log.Println(decision.CoTTrace)
|
||||
log.Printf(strings.Repeat("-", 70) + "\n")
|
||||
// 打印系统提示词和AI思维链(即使有错误,也要输出以便调试)
|
||||
if decision != nil {
|
||||
if decision.SystemPrompt != "" {
|
||||
log.Printf("\n" + strings.Repeat("=", 70))
|
||||
log.Printf("📋 系统提示词 [模板: %s] (错误情况)", at.systemPromptTemplate)
|
||||
log.Println(strings.Repeat("=", 70))
|
||||
log.Println(decision.SystemPrompt)
|
||||
log.Printf(strings.Repeat("=", 70) + "\n")
|
||||
}
|
||||
|
||||
if decision.CoTTrace != "" {
|
||||
log.Printf("\n" + strings.Repeat("-", 70))
|
||||
log.Println("💭 AI思维链分析(错误情况):")
|
||||
log.Println(strings.Repeat("-", 70))
|
||||
log.Println(decision.CoTTrace)
|
||||
log.Printf(strings.Repeat("-", 70) + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
at.decisionLogger.LogDecision(record)
|
||||
return fmt.Errorf("获取AI决策失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 打印AI思维链
|
||||
log.Printf("\n" + strings.Repeat("-", 70))
|
||||
log.Println("💭 AI思维链分析:")
|
||||
log.Println(strings.Repeat("-", 70))
|
||||
log.Println(decision.CoTTrace)
|
||||
log.Printf(strings.Repeat("-", 70) + "\n")
|
||||
// // 5. 打印系统提示词
|
||||
// log.Printf("\n" + strings.Repeat("=", 70))
|
||||
// log.Printf("📋 系统提示词 [模板: %s]", at.systemPromptTemplate)
|
||||
// log.Println(strings.Repeat("=", 70))
|
||||
// log.Println(decision.SystemPrompt)
|
||||
// log.Printf(strings.Repeat("=", 70) + "\n")
|
||||
|
||||
// 6. 打印AI决策
|
||||
log.Printf("📋 AI决策列表 (%d 个):\n", len(decision.Decisions))
|
||||
for i, d := range decision.Decisions {
|
||||
log.Printf(" [%d] %s: %s - %s", i+1, d.Symbol, d.Action, d.Reasoning)
|
||||
if d.Action == "open_long" || d.Action == "open_short" {
|
||||
log.Printf(" 杠杆: %dx | 仓位: %.2f USDT | 止损: %.4f | 止盈: %.4f",
|
||||
d.Leverage, d.PositionSizeUSD, d.StopLoss, d.TakeProfit)
|
||||
}
|
||||
}
|
||||
// 6. 打印AI思维链
|
||||
// log.Printf("\n" + strings.Repeat("-", 70))
|
||||
// log.Println("💭 AI思维链分析:")
|
||||
// log.Println(strings.Repeat("-", 70))
|
||||
// log.Println(decision.CoTTrace)
|
||||
// log.Printf(strings.Repeat("-", 70) + "\n")
|
||||
|
||||
// 7. 打印AI决策
|
||||
// log.Printf("📋 AI决策列表 (%d 个):\n", len(decision.Decisions))
|
||||
// for i, d := range decision.Decisions {
|
||||
// log.Printf(" [%d] %s: %s - %s", i+1, d.Symbol, d.Action, d.Reasoning)
|
||||
// if d.Action == "open_long" || d.Action == "open_short" {
|
||||
// log.Printf(" 杠杆: %dx | 仓位: %.2f USDT | 止损: %.4f | 止盈: %.4f",
|
||||
// d.Leverage, d.PositionSizeUSD, d.StopLoss, d.TakeProfit)
|
||||
// }
|
||||
// }
|
||||
log.Println()
|
||||
|
||||
// 7. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
|
||||
// 8. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
|
||||
sortedDecisions := sortDecisionsByPriority(decision.Decisions)
|
||||
|
||||
log.Println("🔄 执行顺序(已优化): 先平仓→后开仓")
|
||||
@@ -369,7 +426,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
record.Decisions = append(record.Decisions, actionRecord)
|
||||
}
|
||||
|
||||
// 8. 保存决策记录
|
||||
// 9. 保存决策记录
|
||||
if err := at.decisionLogger.LogDecision(record); err != nil {
|
||||
log.Printf("⚠ 保存决策记录失败: %v", err)
|
||||
}
|
||||
@@ -427,6 +484,14 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) {
|
||||
unrealizedPnl := pos["unRealizedProfit"].(float64)
|
||||
liquidationPrice := pos["liquidationPrice"].(float64)
|
||||
|
||||
// 计算盈亏百分比
|
||||
pnlPct := 0.0
|
||||
if side == "long" {
|
||||
pnlPct = ((markPrice - entryPrice) / entryPrice) * 100
|
||||
} else {
|
||||
pnlPct = ((entryPrice - markPrice) / entryPrice) * 100
|
||||
}
|
||||
|
||||
// 计算占用保证金(估算)
|
||||
leverage := 10 // 默认值,实际应该从持仓信息获取
|
||||
if lev, ok := pos["leverage"].(float64); ok {
|
||||
@@ -435,14 +500,6 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) {
|
||||
marginUsed := (quantity * markPrice) / float64(leverage)
|
||||
totalMarginUsed += marginUsed
|
||||
|
||||
// 计算盈亏百分比
|
||||
pnlPct := 0.0
|
||||
if side == "long" {
|
||||
pnlPct = ((markPrice - entryPrice) / entryPrice) * float64(leverage) * 100
|
||||
} else {
|
||||
pnlPct = ((entryPrice - markPrice) / entryPrice) * float64(leverage) * 100
|
||||
}
|
||||
|
||||
// 跟踪持仓首次出现时间
|
||||
posKey := symbol + "_" + side
|
||||
currentPositionKeys[posKey] = true
|
||||
@@ -474,30 +531,12 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 获取合并的候选币种池(AI500 + OI Top,去重)
|
||||
// 无论有没有持仓,都分析相同数量的币种(让AI看到所有好机会)
|
||||
// AI会根据保证金使用率和现有持仓情况,自己决定是否要换仓
|
||||
const ai500Limit = 20 // AI500取前20个评分最高的币种
|
||||
|
||||
// 获取合并后的币种池(AI500 + OI Top)
|
||||
mergedPool, err := pool.GetMergedCoinPool(ai500Limit)
|
||||
// 3. 获取交易员的候选币种池
|
||||
candidateCoins, err := at.getCandidateCoins()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取合并币种池失败: %w", err)
|
||||
return nil, fmt.Errorf("获取候选币种失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建候选币种列表(包含来源信息)
|
||||
var candidateCoins []decision.CandidateCoin
|
||||
for _, symbol := range mergedPool.AllSymbols {
|
||||
sources := mergedPool.SymbolSources[symbol]
|
||||
candidateCoins = append(candidateCoins, decision.CandidateCoin{
|
||||
Symbol: symbol,
|
||||
Sources: sources, // "ai500" 和/或 "oi_top"
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("📋 合并币种池: AI500前%d + OI_Top20 = 总计%d个候选币种",
|
||||
ai500Limit, len(candidateCoins))
|
||||
|
||||
// 4. 计算总盈亏
|
||||
totalPnL := totalEquity - at.initialBalance
|
||||
totalPnLPct := 0.0
|
||||
@@ -587,6 +626,12 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *decision.Decision, act
|
||||
actionRecord.Quantity = quantity
|
||||
actionRecord.Price = marketData.CurrentPrice
|
||||
|
||||
// 设置仓位模式
|
||||
if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil {
|
||||
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
|
||||
// 继续执行,不影响交易
|
||||
}
|
||||
|
||||
// 开仓
|
||||
order, err := at.trader.OpenLong(decision.Symbol, quantity, decision.Leverage)
|
||||
if err != nil {
|
||||
@@ -640,6 +685,12 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *decision.Decision, ac
|
||||
actionRecord.Quantity = quantity
|
||||
actionRecord.Price = marketData.CurrentPrice
|
||||
|
||||
// 设置仓位模式
|
||||
if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil {
|
||||
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
|
||||
// 继续执行,不影响交易
|
||||
}
|
||||
|
||||
// 开仓
|
||||
order, err := at.trader.OpenShort(decision.Symbol, quantity, decision.Leverage)
|
||||
if err != nil {
|
||||
@@ -735,6 +786,31 @@ func (at *AutoTrader) GetAIModel() string {
|
||||
return at.aiModel
|
||||
}
|
||||
|
||||
// GetExchange 获取交易所
|
||||
func (at *AutoTrader) GetExchange() string {
|
||||
return at.exchange
|
||||
}
|
||||
|
||||
// SetCustomPrompt 设置自定义交易策略prompt
|
||||
func (at *AutoTrader) SetCustomPrompt(prompt string) {
|
||||
at.customPrompt = prompt
|
||||
}
|
||||
|
||||
// SetOverrideBasePrompt 设置是否覆盖基础prompt
|
||||
func (at *AutoTrader) SetOverrideBasePrompt(override bool) {
|
||||
at.overrideBasePrompt = override
|
||||
}
|
||||
|
||||
// SetSystemPromptTemplate 设置系统提示词模板
|
||||
func (at *AutoTrader) SetSystemPromptTemplate(templateName string) {
|
||||
at.systemPromptTemplate = templateName
|
||||
}
|
||||
|
||||
// GetSystemPromptTemplate 获取当前系统提示词模板名称
|
||||
func (at *AutoTrader) GetSystemPromptTemplate() string {
|
||||
return at.systemPromptTemplate
|
||||
}
|
||||
|
||||
// GetDecisionLogger 获取决策日志记录器
|
||||
func (at *AutoTrader) GetDecisionLogger() *logger.DecisionLogger {
|
||||
return at.decisionLogger
|
||||
@@ -871,15 +947,16 @@ func (at *AutoTrader) GetPositions() ([]map[string]interface{}, error) {
|
||||
leverage = int(lev)
|
||||
}
|
||||
|
||||
pnlPct := 0.0
|
||||
if side == "long" {
|
||||
pnlPct = ((markPrice - entryPrice) / entryPrice) * float64(leverage) * 100
|
||||
} else {
|
||||
pnlPct = ((entryPrice - markPrice) / entryPrice) * float64(leverage) * 100
|
||||
}
|
||||
|
||||
// 计算占用保证金
|
||||
marginUsed := (quantity * markPrice) / float64(leverage)
|
||||
|
||||
// 计算盈亏百分比(基于保证金)
|
||||
// 收益率 = 未实现盈亏 / 保证金 × 100%
|
||||
pnlPct := 0.0
|
||||
if marginUsed > 0 {
|
||||
pnlPct = (unrealizedPnl / marginUsed) * 100
|
||||
}
|
||||
|
||||
result = append(result, map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"side": side,
|
||||
@@ -933,3 +1010,74 @@ func sortDecisionsByPriority(decisions []decision.Decision) []decision.Decision
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
// getCandidateCoins 获取交易员的候选币种列表
|
||||
func (at *AutoTrader) getCandidateCoins() ([]decision.CandidateCoin, error) {
|
||||
if len(at.tradingCoins) == 0 {
|
||||
// 使用数据库配置的默认币种列表
|
||||
var candidateCoins []decision.CandidateCoin
|
||||
|
||||
if len(at.defaultCoins) > 0 {
|
||||
// 使用数据库中配置的默认币种
|
||||
for _, coin := range at.defaultCoins {
|
||||
symbol := normalizeSymbol(coin)
|
||||
candidateCoins = append(candidateCoins, decision.CandidateCoin{
|
||||
Symbol: symbol,
|
||||
Sources: []string{"default"}, // 标记为数据库默认币种
|
||||
})
|
||||
}
|
||||
log.Printf("📋 [%s] 使用数据库默认币种: %d个币种 %v",
|
||||
at.name, len(candidateCoins), at.defaultCoins)
|
||||
return candidateCoins, nil
|
||||
} else {
|
||||
// 如果数据库中没有配置默认币种,则使用AI500+OI Top作为fallback
|
||||
const ai500Limit = 20 // AI500取前20个评分最高的币种
|
||||
|
||||
mergedPool, err := pool.GetMergedCoinPool(ai500Limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取合并币种池失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建候选币种列表(包含来源信息)
|
||||
for _, symbol := range mergedPool.AllSymbols {
|
||||
sources := mergedPool.SymbolSources[symbol]
|
||||
candidateCoins = append(candidateCoins, decision.CandidateCoin{
|
||||
Symbol: symbol,
|
||||
Sources: sources, // "ai500" 和/或 "oi_top"
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("📋 [%s] 数据库无默认币种配置,使用AI500+OI Top: AI500前%d + OI_Top20 = 总计%d个候选币种",
|
||||
at.name, ai500Limit, len(candidateCoins))
|
||||
return candidateCoins, nil
|
||||
}
|
||||
} else {
|
||||
// 使用自定义币种列表
|
||||
var candidateCoins []decision.CandidateCoin
|
||||
for _, coin := range at.tradingCoins {
|
||||
// 确保币种格式正确(转为大写USDT交易对)
|
||||
symbol := normalizeSymbol(coin)
|
||||
candidateCoins = append(candidateCoins, decision.CandidateCoin{
|
||||
Symbol: symbol,
|
||||
Sources: []string{"custom"}, // 标记为自定义来源
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("📋 [%s] 使用自定义币种: %d个币种 %v",
|
||||
at.name, len(candidateCoins), at.tradingCoins)
|
||||
return candidateCoins, nil
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeSymbol 标准化币种符号(确保以USDT结尾)
|
||||
func normalizeSymbol(symbol string) string {
|
||||
// 转为大写
|
||||
symbol = strings.ToUpper(strings.TrimSpace(symbol))
|
||||
|
||||
// 确保以USDT结尾
|
||||
if !strings.HasSuffix(symbol, "USDT") {
|
||||
symbol = symbol + "USDT"
|
||||
}
|
||||
|
||||
return symbol
|
||||
}
|
||||
|
||||
@@ -131,6 +131,46 @@ func (t *FuturesTrader) GetPositions() ([]map[string]interface{}, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetMarginMode 设置仓位模式
|
||||
func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
|
||||
var marginType futures.MarginType
|
||||
if isCrossMargin {
|
||||
marginType = futures.MarginTypeCrossed
|
||||
} else {
|
||||
marginType = futures.MarginTypeIsolated
|
||||
}
|
||||
|
||||
// 尝试设置仓位模式
|
||||
err := t.client.NewChangeMarginTypeService().
|
||||
Symbol(symbol).
|
||||
MarginType(marginType).
|
||||
Do(context.Background())
|
||||
|
||||
marginModeStr := "全仓"
|
||||
if !isCrossMargin {
|
||||
marginModeStr = "逐仓"
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// 如果错误信息包含"No need to change",说明仓位模式已经是目标值
|
||||
if contains(err.Error(), "No need to change margin type") {
|
||||
log.Printf(" ✓ %s 仓位模式已是 %s", symbol, marginModeStr)
|
||||
return nil
|
||||
}
|
||||
// 如果有持仓,无法更改仓位模式,但不影响交易
|
||||
if contains(err.Error(), "Margin type cannot be changed if there exists position") {
|
||||
log.Printf(" ⚠️ %s 有持仓,无法更改仓位模式,继续使用当前模式", symbol)
|
||||
return nil
|
||||
}
|
||||
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
|
||||
// 不返回错误,让交易继续
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginModeStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLeverage 设置杠杆(智能判断+冷却期)
|
||||
func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
|
||||
// 先尝试获取当前杠杆(从持仓信息)
|
||||
@@ -177,31 +217,6 @@ func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMarginType 设置保证金模式
|
||||
func (t *FuturesTrader) SetMarginType(symbol string, marginType futures.MarginType) error {
|
||||
err := t.client.NewChangeMarginTypeService().
|
||||
Symbol(symbol).
|
||||
MarginType(marginType).
|
||||
Do(context.Background())
|
||||
|
||||
if err != nil {
|
||||
// 如果已经是该模式,不算错误
|
||||
if contains(err.Error(), "No need to change") {
|
||||
log.Printf(" ✓ %s 保证金模式已是 %s", symbol, marginType)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("设置保证金模式失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf(" ✓ %s 保证金模式已切换为 %s", symbol, marginType)
|
||||
|
||||
// 切换保证金模式后等待3秒(避免冷却期错误)
|
||||
log.Printf(" ⏱ 等待3秒冷却期...")
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenLong 开多仓
|
||||
func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
||||
// 先取消该币种的所有委托单(清理旧的止损止盈单)
|
||||
@@ -214,10 +229,7 @@ func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置逐仓模式
|
||||
if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 注意:仓位模式应该由调用方(AutoTrader)在开仓前通过 SetMarginMode 设置
|
||||
|
||||
// 格式化数量到正确精度
|
||||
quantityStr, err := t.FormatQuantity(symbol, quantity)
|
||||
@@ -260,10 +272,7 @@ func (t *FuturesTrader) OpenShort(symbol string, quantity float64, leverage int)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置逐仓模式
|
||||
if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 注意:仓位模式应该由调用方(AutoTrader)在开仓前通过 SetMarginMode 设置
|
||||
|
||||
// 格式化数量到正确精度
|
||||
quantityStr, err := t.FormatQuantity(symbol, quantity)
|
||||
|
||||
@@ -13,10 +13,11 @@ import (
|
||||
|
||||
// HyperliquidTrader Hyperliquid交易器
|
||||
type HyperliquidTrader struct {
|
||||
exchange *hyperliquid.Exchange
|
||||
ctx context.Context
|
||||
walletAddr string
|
||||
meta *hyperliquid.Meta // 缓存meta信息(包含精度等)
|
||||
exchange *hyperliquid.Exchange
|
||||
ctx context.Context
|
||||
walletAddr string
|
||||
meta *hyperliquid.Meta // 缓存meta信息(包含精度等)
|
||||
isCrossMargin bool // 是否为全仓模式
|
||||
}
|
||||
|
||||
// NewHyperliquidTrader 创建Hyperliquid交易器
|
||||
@@ -63,10 +64,11 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
|
||||
}
|
||||
|
||||
return &HyperliquidTrader{
|
||||
exchange: exchange,
|
||||
ctx: ctx,
|
||||
walletAddr: walletAddr,
|
||||
meta: meta,
|
||||
exchange: exchange,
|
||||
ctx: ctx,
|
||||
walletAddr: walletAddr,
|
||||
meta: meta,
|
||||
isCrossMargin: true, // 默认使用全仓模式
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -187,13 +189,26 @@ func (t *HyperliquidTrader) GetPositions() ([]map[string]interface{}, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetMarginMode 设置仓位模式 (在SetLeverage时一并设置)
|
||||
func (t *HyperliquidTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
|
||||
// Hyperliquid的仓位模式在SetLeverage时设置,这里只记录
|
||||
t.isCrossMargin = isCrossMargin
|
||||
marginModeStr := "全仓"
|
||||
if !isCrossMargin {
|
||||
marginModeStr = "逐仓"
|
||||
}
|
||||
log.Printf(" ✓ %s 将使用 %s 模式", symbol, marginModeStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLeverage 设置杠杆
|
||||
func (t *HyperliquidTrader) SetLeverage(symbol string, leverage int) error {
|
||||
// Hyperliquid symbol格式(去掉USDT后缀)
|
||||
coin := convertSymbolToHyperliquid(symbol)
|
||||
|
||||
// 调用UpdateLeverage (leverage int, name string, isCross bool)
|
||||
_, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, false) // false = 逐仓模式
|
||||
// 第三个参数: true=全仓模式, false=逐仓模式
|
||||
_, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, t.isCrossMargin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("设置杠杆失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ type Trader interface {
|
||||
// SetLeverage 设置杠杆
|
||||
SetLeverage(symbol string, leverage int) error
|
||||
|
||||
// SetMarginMode 设置仓位模式 (true=全仓, false=逐仓)
|
||||
SetMarginMode(symbol string, isCrossMargin bool) error
|
||||
|
||||
// GetMarketPrice 获取市场价格
|
||||
GetMarketPrice(symbol string) (float64, error)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Build output (will be regenerated)
|
||||
dist/
|
||||
|
||||
@@ -2,11 +2,22 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<!-- Google Tag Manager -->
|
||||
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-TM429527');</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icons/nofx.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>NOFX - AI Auto Trading Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TM429527"
|
||||
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
117
web/package-lock.json
generated
@@ -8,12 +8,17 @@
|
||||
"name": "nofx-web",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.23.24",
|
||||
"lucide-react": "^0.552.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"recharts": "^2.15.2",
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"zustand": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -833,6 +838,39 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||
@@ -1503,6 +1541,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/class-variance-authority": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
||||
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://polar.sh/cva"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@@ -1904,6 +1954,33 @@
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.23.24",
|
||||
"resolved": "https://registry.npmmirror.com/framer-motion/-/framer-motion-12.23.24.tgz",
|
||||
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.23.23",
|
||||
"motion-utils": "^12.23.6",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -2156,6 +2233,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.552.0",
|
||||
"resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.552.0.tgz",
|
||||
"integrity": "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
@@ -2202,6 +2288,21 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.23.23",
|
||||
"resolved": "https://registry.npmmirror.com/motion-dom/-/motion-dom-12.23.23.tgz",
|
||||
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.23.6"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.23.6",
|
||||
"resolved": "https://registry.npmmirror.com/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -2960,6 +3061,16 @@
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
|
||||
"integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
|
||||
@@ -3086,6 +3197,12 @@
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
|
||||
@@ -8,22 +8,27 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.23.24",
|
||||
"lucide-react": "^0.552.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"zustand": "^5.0.2",
|
||||
"swr": "^2.2.5",
|
||||
"recharts": "^2.15.2",
|
||||
"date-fns": "^4.1.0",
|
||||
"clsx": "^2.1.1"
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"zustand": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.17",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.0.7",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"autoprefixer": "^10.4.20"
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
|
||||
23
web/public/icons/aster.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.13309 30.4398L9.88315 26.9871C10.7197 23.1362 7.77521 19.4988 3.82118 19.4988H0.385363C1.4689 24.3374 4.75127 28.3496 9.13309 30.4398Z" fill="url(#paint0_linear_428_3535)"/>
|
||||
<path d="M10.64 31.0663C12.3326 31.6707 14.1567 32 16.0579 32C23.7199 32 30.1285 26.6527 31.7305 19.4988H21.249C16.5244 19.4988 12.4396 22.7824 11.44 27.3838L10.64 31.0663Z" fill="url(#paint1_linear_428_3535)"/>
|
||||
<path d="M32.0038 17.8987C32.0778 17.2756 32.1159 16.6415 32.1159 15.9985C32.1159 7.60402 25.629 0.719287 17.3779 0.0503251L15.1273 10.4105C14.2907 14.2614 17.2352 17.8987 21.1892 17.8987H32.0038Z" fill="url(#paint2_linear_428_3535)"/>
|
||||
<path d="M15.7459 0C7.02134 0.165717 0 7.26504 0 15.9985C0 16.6415 0.0380539 17.2756 0.112041 17.8987H3.76146C8.48603 17.8987 12.5709 14.6151 13.5705 10.0137L15.7459 0Z" fill="url(#paint3_linear_428_3535)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_428_3535" x1="18.9416" y1="4.14314e-07" x2="12.6408" y2="32.0507" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F4D5B1"/>
|
||||
<stop offset="1" stop-color="#FFD29F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_428_3535" x1="18.9416" y1="4.14314e-07" x2="12.6408" y2="32.0507" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F4D5B1"/>
|
||||
<stop offset="1" stop-color="#FFD29F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_428_3535" x1="18.9416" y1="4.14314e-07" x2="12.6408" y2="32.0507" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F4D5B1"/>
|
||||
<stop offset="1" stop-color="#FFD29F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_428_3535" x1="18.9416" y1="4.14314e-07" x2="12.6408" y2="32.0507" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F4D5B1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
1
web/public/icons/binance.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="40" width="40" viewBox="-52.785 -88 457.47 528"><path d="M79.5 176l-39.7 39.7L0 176l39.7-39.7zM176 79.5l68.1 68.1 39.7-39.7L176 0 68.1 107.9l39.7 39.7zm136.2 56.8L272.5 176l39.7 39.7 39.7-39.7zM176 272.5l-68.1-68.1-39.7 39.7L176 352l107.8-107.9-39.7-39.7zm0-56.8l39.7-39.7-39.7-39.7-39.8 39.7z" fill="#f0b90b"/></svg>
|
||||
|
After Width: | Height: | Size: 365 B |
1
web/public/icons/deepseek.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
3
web/public/icons/hypeliquid.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M144 71.6991C144 119.306 114.866 134.582 99.5156 120.98C86.8804 109.889 83.1211 86.4521 64.116 84.0456C39.9942 81.0113 37.9057 113.133 22.0334 113.133C3.5504 113.133 0 86.2428 0 72.4315C0 58.3063 3.96809 39.0542 19.736 39.0542C38.1146 39.0542 39.1588 66.5722 62.132 65.1073C85.0007 63.5379 85.4184 34.8689 100.247 22.6271C113.195 12.0593 144 23.4641 144 71.6991Z" fill="#97FCE4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 497 B |
296
web/public/icons/nofx.svg
Normal file
@@ -0,0 +1,296 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 1024 1024" enable-background="new 0 0 1024 1024" xml:space="preserve">
|
||||
<path fill="#FDFDFD" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M500.000000,1025.000000
|
||||
C333.333344,1025.000000 167.166672,1025.000000 1.000000,1025.000000
|
||||
C1.000000,683.666687 1.000000,342.333344 1.000000,1.000000
|
||||
C342.333344,1.000000 683.666687,1.000000 1025.000000,1.000000
|
||||
C1025.000000,342.333344 1025.000000,683.666687 1025.000000,1025.000000
|
||||
C850.166687,1025.000000 675.333313,1025.000000 500.000000,1025.000000
|
||||
M534.031067,584.483826
|
||||
C534.031067,588.398560 534.031067,592.313354 534.031067,596.807495
|
||||
C545.055725,596.807495 555.191895,596.895569 565.324890,596.757568
|
||||
C568.675903,596.711914 570.108704,597.732422 570.067505,601.303345
|
||||
C569.908142,615.130981 569.858154,628.962708 570.028137,642.789673
|
||||
C570.077393,646.796997 568.523926,648.072266 564.742004,647.996033
|
||||
C556.247864,647.824707 547.744873,648.036621 539.253845,647.796204
|
||||
C535.514526,647.690369 533.817627,648.561218 533.996155,652.742493
|
||||
C534.308533,660.059753 533.817566,667.412292 534.157166,674.727051
|
||||
C534.375000,679.418884 533.066345,681.195984 528.085999,681.018494
|
||||
C517.601807,680.644897 507.094879,680.960999 496.599945,680.810852
|
||||
C493.243347,680.762817 491.914062,681.787109 491.941986,685.357300
|
||||
C492.094421,704.850281 492.122192,724.345581 491.971497,743.838440
|
||||
C491.941101,747.773010 493.228638,749.004578 497.124115,748.978394
|
||||
C518.949890,748.831421 540.777771,748.821838 562.603271,748.980225
|
||||
C566.173035,749.006165 567.215881,747.891907 567.153503,744.444214
|
||||
C566.966858,734.117310 567.222534,723.781128 566.953308,713.457703
|
||||
C566.847107,709.385132 568.577148,708.202698 572.203125,708.286438
|
||||
C578.188965,708.424683 584.192078,708.207458 590.160583,708.579163
|
||||
C594.476196,708.847839 596.032410,707.578918 595.856140,703.035217
|
||||
C595.503296,693.939453 595.751465,684.820312 595.751465,675.649231
|
||||
C596.925232,675.398560 597.552307,675.149048 598.180054,675.147583
|
||||
C615.339722,675.106445 632.499634,675.126099 649.658997,675.034668
|
||||
C652.399414,675.020081 653.028992,676.300964 653.009949,678.716370
|
||||
C652.946899,686.712646 653.113281,694.711670 652.973572,702.705811
|
||||
C652.917664,705.902710 653.994019,707.188904 657.276245,707.107605
|
||||
C663.269775,706.959106 669.278931,707.315979 675.266113,707.076050
|
||||
C679.086365,706.922974 680.126648,708.399414 680.068604,712.025635
|
||||
C679.897949,722.685486 680.140930,733.351624 679.989075,744.012024
|
||||
C679.937683,747.618469 681.041504,748.997803 684.821899,748.976501
|
||||
C708.314026,748.844055 731.807556,748.852051 755.299805,748.975403
|
||||
C758.820862,748.993835 759.868530,747.806763 759.844788,744.357910
|
||||
C759.709351,724.698547 759.709839,705.037598 759.818420,685.377991
|
||||
C759.837585,681.910950 758.672913,680.734314 755.189087,680.794067
|
||||
C744.195374,680.982727 733.194397,680.763184 722.201355,680.970520
|
||||
C718.237671,681.045227 716.403687,679.967041 716.487549,675.664490
|
||||
C716.636658,668.012939 716.185547,660.350403 716.298767,652.697144
|
||||
C716.354187,648.952393 714.940552,647.784058 711.308655,647.875549
|
||||
C703.149780,648.080994 694.974915,647.740051 686.821289,648.032654
|
||||
C682.197937,648.198547 680.731934,646.551575 680.854858,641.973938
|
||||
C681.136780,631.484619 680.943665,620.982361 680.937012,610.485352
|
||||
C680.928345,596.707458 680.925720,596.708313 694.772095,596.712830
|
||||
C701.716309,596.715027 708.660583,596.713257 716.156738,596.713257
|
||||
C716.156738,587.047791 716.274170,578.086670 716.100098,569.131104
|
||||
C716.028503,565.448486 717.396729,564.077209 721.057495,564.125061
|
||||
C731.718384,564.264526 742.382324,564.204712 753.044861,564.170166
|
||||
C755.140137,564.163330 757.234619,563.919800 759.374390,563.782654
|
||||
C759.374390,541.682739 759.374390,520.113953 759.374390,498.512848
|
||||
C758.314819,498.283966 757.679260,498.027313 757.043457,498.026611
|
||||
C732.552490,497.999329 708.061401,498.037842 683.570801,497.939880
|
||||
C680.143433,497.926178 679.874512,499.756226 679.904175,502.387390
|
||||
C680.015015,512.215210 679.874512,522.048523 680.153259,531.870544
|
||||
C680.266418,535.860596 679.224426,537.680786 674.914673,537.479248
|
||||
C669.263062,537.215027 663.581482,537.623352 657.925659,537.406860
|
||||
C654.311951,537.268616 652.858887,538.322510 652.991699,542.182861
|
||||
C653.266357,550.170227 652.971191,558.175842 653.164490,566.167786
|
||||
C653.254150,569.872559 651.856873,571.069702 648.200317,571.033142
|
||||
C632.374023,570.874939 616.544556,570.857117 600.718201,571.000671
|
||||
C597.009949,571.034241 595.702759,569.735596 595.771912,566.091858
|
||||
C595.923645,558.098816 595.724426,550.099670 595.818298,542.104797
|
||||
C595.854309,539.035889 594.871948,537.527527 591.554504,537.577759
|
||||
C585.402100,537.671082 579.222473,537.141418 573.097961,537.541626
|
||||
C567.969849,537.876709 566.857544,535.717102 566.994751,531.152100
|
||||
C567.259827,522.329468 567.088135,513.493835 567.107239,504.663605
|
||||
C567.121216,498.211700 567.128967,498.312378 560.831177,498.362671
|
||||
C539.515869,498.532898 518.197937,498.814789 496.885956,498.590637
|
||||
C492.527435,498.544800 491.869690,499.960571 491.895660,503.604095
|
||||
C492.027496,522.097412 492.121552,540.592773 491.953308,559.084961
|
||||
C491.917023,563.073425 493.143402,564.285767 497.052917,564.207214
|
||||
C507.545197,563.996338 518.045471,564.219666 528.540771,564.103271
|
||||
C532.401672,564.060364 534.411804,565.121643 534.089355,569.498840
|
||||
C533.747498,574.138367 534.024414,578.823547 534.031067,584.483826
|
||||
M253.353867,389.499359
|
||||
C253.353867,425.600555 253.353867,461.701721 253.353867,498.020203
|
||||
C273.984375,498.020203 294.234894,498.020203 314.879120,498.020203
|
||||
C314.879120,466.733032 314.879120,435.525452 314.879120,404.231567
|
||||
C325.188171,404.231567 335.102448,404.231567 345.353882,404.231567
|
||||
C345.353882,416.582062 345.353882,428.655182 345.353882,441.171600
|
||||
C356.752899,441.171600 367.816437,441.171600 379.144989,441.171600
|
||||
C379.144989,452.733887 379.144989,463.845337 379.144989,475.463928
|
||||
C387.484772,475.463928 395.556122,475.463928 404.001831,475.463928
|
||||
C404.001831,483.396393 404.001831,490.822571 404.001831,498.353760
|
||||
C424.266266,498.353760 444.136444,498.353760 464.049652,498.353760
|
||||
C464.049652,428.578674 464.049652,359.158661 464.049652,289.508362
|
||||
C443.934937,289.508362 424.057556,289.508362 403.805725,289.508362
|
||||
C403.805725,326.036926 403.805725,362.284485 403.805725,398.714264
|
||||
C393.694244,398.714264 383.958435,398.714264 373.754669,398.714264
|
||||
C373.754669,387.079407 373.754669,375.679657 373.754669,363.763580
|
||||
C364.226013,363.763580 355.157928,363.763580 345.754578,363.763580
|
||||
C345.754578,350.975952 345.754578,338.719330 345.754578,326.013428
|
||||
C335.334686,326.013428 325.264954,326.013428 314.727661,326.013428
|
||||
C314.727661,313.817993 314.727661,302.078217 314.727661,290.294006
|
||||
C294.024200,290.294006 273.800537,290.294006 253.353867,290.294006
|
||||
C253.353867,323.258392 253.353867,355.878815 253.353867,389.499359
|
||||
M253.324738,540.524414
|
||||
C253.324738,609.787170 253.324738,679.049988 253.324738,748.416748
|
||||
C273.913696,748.416748 293.998413,748.416748 314.709351,748.416748
|
||||
C314.709351,719.517822 314.709351,690.832581 314.709351,661.901611
|
||||
C350.772034,661.901611 386.208771,661.901611 421.853760,661.901611
|
||||
C421.853760,645.744873 421.853760,629.843628 421.853760,613.658813
|
||||
C386.514313,613.658813 351.406067,613.658813 315.981873,613.658813
|
||||
C315.981873,602.016785 315.981873,590.659973 315.981873,578.921387
|
||||
C365.640533,578.921387 414.883820,578.921387 464.020874,578.921387
|
||||
C464.020874,562.126465 464.020874,545.717834 464.020874,529.276367
|
||||
C393.615936,529.276367 323.565338,529.276367 253.319580,529.276367
|
||||
C253.319580,532.918762 253.319580,536.226440 253.324738,540.524414
|
||||
M492.003387,396.500000
|
||||
C492.003387,418.075806 492.003387,439.651642 492.003387,461.703796
|
||||
C504.388947,461.703796 516.107178,461.703796 528.313721,461.703796
|
||||
C528.313721,472.416870 528.313721,482.654999 528.313721,492.669708
|
||||
C565.543945,492.669708 602.271057,492.669708 639.460571,492.669708
|
||||
C639.460571,481.874329 639.460571,471.464661 639.460571,460.745056
|
||||
C652.522522,460.745056 665.122131,460.745056 677.734863,460.745056
|
||||
C677.734863,427.692261 677.734863,394.973724 677.734863,361.929138
|
||||
C664.783020,361.929138 652.186462,361.929138 639.150879,361.929138
|
||||
C639.150879,350.688141 639.150879,339.765076 639.150879,328.555176
|
||||
C601.986938,328.555176 565.268433,328.555176 527.973755,328.555176
|
||||
C527.973755,339.860962 527.973755,350.904510 527.973755,362.241943
|
||||
C515.652466,362.241943 503.905975,362.241943 492.002960,362.241943
|
||||
C492.002960,373.544617 492.002960,384.522308 492.003387,396.500000
|
||||
M680.952881,342.679718
|
||||
C699.195862,342.679718 717.438782,342.679718 735.967041,342.679718
|
||||
C735.967041,325.691925 736.031189,309.222900 735.806641,292.757812
|
||||
C735.793213,291.775330 733.476685,289.998322 732.215942,289.983917
|
||||
C715.887634,289.797485 699.555664,289.801270 683.227722,290.003845
|
||||
C682.113281,290.017670 680.069885,291.995911 680.064697,293.068268
|
||||
C679.986694,309.383881 680.176758,325.700775 680.952881,342.679718
|
||||
z"/>
|
||||
<path fill="#F9AF07" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M534.031128,583.986328
|
||||
C534.024414,578.823547 533.747498,574.138367 534.089355,569.498840
|
||||
C534.411804,565.121643 532.401672,564.060364 528.540771,564.103271
|
||||
C518.045471,564.219666 507.545197,563.996338 497.052917,564.207214
|
||||
C493.143402,564.285767 491.917023,563.073425 491.953308,559.084961
|
||||
C492.121552,540.592773 492.027496,522.097412 491.895660,503.604095
|
||||
C491.869690,499.960571 492.527435,498.544800 496.885956,498.590637
|
||||
C518.197937,498.814789 539.515869,498.532898 560.831177,498.362671
|
||||
C567.128967,498.312378 567.121216,498.211700 567.107239,504.663605
|
||||
C567.088135,513.493835 567.259827,522.329468 566.994751,531.152100
|
||||
C566.857544,535.717102 567.969849,537.876709 573.097961,537.541626
|
||||
C579.222473,537.141418 585.402100,537.671082 591.554504,537.577759
|
||||
C594.871948,537.527527 595.854309,539.035889 595.818298,542.104797
|
||||
C595.724426,550.099670 595.923645,558.098816 595.771912,566.091858
|
||||
C595.702759,569.735596 597.009949,571.034241 600.718201,571.000671
|
||||
C616.544556,570.857117 632.374023,570.874939 648.200317,571.033142
|
||||
C651.856873,571.069702 653.254150,569.872559 653.164490,566.167786
|
||||
C652.971191,558.175842 653.266357,550.170227 652.991699,542.182861
|
||||
C652.858887,538.322510 654.311951,537.268616 657.925659,537.406860
|
||||
C663.581482,537.623352 669.263062,537.215027 674.914673,537.479248
|
||||
C679.224426,537.680786 680.266418,535.860596 680.153259,531.870544
|
||||
C679.874512,522.048523 680.015015,512.215210 679.904175,502.387390
|
||||
C679.874512,499.756226 680.143433,497.926178 683.570801,497.939880
|
||||
C708.061401,498.037842 732.552490,497.999329 757.043457,498.026611
|
||||
C757.679260,498.027313 758.314819,498.283966 759.374390,498.512848
|
||||
C759.374390,520.113953 759.374390,541.682739 759.374390,563.782654
|
||||
C757.234619,563.919800 755.140137,564.163330 753.044861,564.170166
|
||||
C742.382324,564.204712 731.718384,564.264526 721.057495,564.125061
|
||||
C717.396729,564.077209 716.028503,565.448486 716.100098,569.131104
|
||||
C716.274170,578.086670 716.156738,587.047791 716.156738,596.713257
|
||||
C708.660583,596.713257 701.716309,596.715027 694.772095,596.712830
|
||||
C680.925720,596.708313 680.928345,596.707458 680.937012,610.485352
|
||||
C680.943665,620.982361 681.136780,631.484619 680.854858,641.973938
|
||||
C680.731934,646.551575 682.197937,648.198547 686.821289,648.032654
|
||||
C694.974915,647.740051 703.149780,648.080994 711.308655,647.875549
|
||||
C714.940552,647.784058 716.354187,648.952393 716.298767,652.697144
|
||||
C716.185547,660.350403 716.636658,668.012939 716.487549,675.664490
|
||||
C716.403687,679.967041 718.237671,681.045227 722.201355,680.970520
|
||||
C733.194397,680.763184 744.195374,680.982727 755.189087,680.794067
|
||||
C758.672913,680.734314 759.837585,681.910950 759.818420,685.377991
|
||||
C759.709839,705.037598 759.709351,724.698547 759.844788,744.357910
|
||||
C759.868530,747.806763 758.820862,748.993835 755.299805,748.975403
|
||||
C731.807556,748.852051 708.314026,748.844055 684.821899,748.976501
|
||||
C681.041504,748.997803 679.937683,747.618469 679.989075,744.012024
|
||||
C680.140930,733.351624 679.897949,722.685486 680.068604,712.025635
|
||||
C680.126648,708.399414 679.086365,706.922974 675.266113,707.076050
|
||||
C669.278931,707.315979 663.269775,706.959106 657.276245,707.107605
|
||||
C653.994019,707.188904 652.917664,705.902710 652.973572,702.705811
|
||||
C653.113281,694.711670 652.946899,686.712646 653.009949,678.716370
|
||||
C653.028992,676.300964 652.399414,675.020081 649.658997,675.034668
|
||||
C632.499634,675.126099 615.339722,675.106445 598.180054,675.147583
|
||||
C597.552307,675.149048 596.925232,675.398560 595.751465,675.649231
|
||||
C595.751465,684.820312 595.503296,693.939453 595.856140,703.035217
|
||||
C596.032410,707.578918 594.476196,708.847839 590.160583,708.579163
|
||||
C584.192078,708.207458 578.188965,708.424683 572.203125,708.286438
|
||||
C568.577148,708.202698 566.847107,709.385132 566.953308,713.457703
|
||||
C567.222534,723.781128 566.966858,734.117310 567.153503,744.444214
|
||||
C567.215881,747.891907 566.173035,749.006165 562.603271,748.980225
|
||||
C540.777771,748.821838 518.949890,748.831421 497.124115,748.978394
|
||||
C493.228638,749.004578 491.941101,747.773010 491.971497,743.838440
|
||||
C492.122192,724.345581 492.094421,704.850281 491.941986,685.357300
|
||||
C491.914062,681.787109 493.243347,680.762817 496.599945,680.810852
|
||||
C507.094879,680.960999 517.601807,680.644897 528.085999,681.018494
|
||||
C533.066345,681.195984 534.375000,679.418884 534.157166,674.727051
|
||||
C533.817566,667.412292 534.308533,660.059753 533.996155,652.742493
|
||||
C533.817627,648.561218 535.514526,647.690369 539.253845,647.796204
|
||||
C547.744873,648.036621 556.247864,647.824707 564.742004,647.996033
|
||||
C568.523926,648.072266 570.077393,646.796997 570.028137,642.789673
|
||||
C569.858154,628.962708 569.908142,615.130981 570.067505,601.303345
|
||||
C570.108704,597.732422 568.675903,596.711914 565.324890,596.757568
|
||||
C555.191895,596.895569 545.055725,596.807495 534.031067,596.807495
|
||||
C534.031067,592.313354 534.031067,588.398560 534.031128,583.986328
|
||||
z"/>
|
||||
<path fill="#020202" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M253.353867,388.999268
|
||||
C253.353867,355.878815 253.353867,323.258392 253.353867,290.294006
|
||||
C273.800537,290.294006 294.024200,290.294006 314.727661,290.294006
|
||||
C314.727661,302.078217 314.727661,313.817993 314.727661,326.013428
|
||||
C325.264954,326.013428 335.334686,326.013428 345.754578,326.013428
|
||||
C345.754578,338.719330 345.754578,350.975952 345.754578,363.763580
|
||||
C355.157928,363.763580 364.226013,363.763580 373.754669,363.763580
|
||||
C373.754669,375.679657 373.754669,387.079407 373.754669,398.714264
|
||||
C383.958435,398.714264 393.694244,398.714264 403.805725,398.714264
|
||||
C403.805725,362.284485 403.805725,326.036926 403.805725,289.508362
|
||||
C424.057556,289.508362 443.934937,289.508362 464.049652,289.508362
|
||||
C464.049652,359.158661 464.049652,428.578674 464.049652,498.353760
|
||||
C444.136444,498.353760 424.266266,498.353760 404.001831,498.353760
|
||||
C404.001831,490.822571 404.001831,483.396393 404.001831,475.463928
|
||||
C395.556122,475.463928 387.484772,475.463928 379.144989,475.463928
|
||||
C379.144989,463.845337 379.144989,452.733887 379.144989,441.171600
|
||||
C367.816437,441.171600 356.752899,441.171600 345.353882,441.171600
|
||||
C345.353882,428.655182 345.353882,416.582062 345.353882,404.231567
|
||||
C335.102448,404.231567 325.188171,404.231567 314.879120,404.231567
|
||||
C314.879120,435.525452 314.879120,466.733032 314.879120,498.020203
|
||||
C294.234894,498.020203 273.984375,498.020203 253.353867,498.020203
|
||||
C253.353867,461.701721 253.353867,425.600555 253.353867,388.999268
|
||||
z"/>
|
||||
<path fill="#020202" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M253.322159,540.029297
|
||||
C253.319580,536.226440 253.319580,532.918762 253.319580,529.276367
|
||||
C323.565338,529.276367 393.615936,529.276367 464.020874,529.276367
|
||||
C464.020874,545.717834 464.020874,562.126465 464.020874,578.921387
|
||||
C414.883820,578.921387 365.640533,578.921387 315.981873,578.921387
|
||||
C315.981873,590.659973 315.981873,602.016785 315.981873,613.658813
|
||||
C351.406067,613.658813 386.514313,613.658813 421.853760,613.658813
|
||||
C421.853760,629.843628 421.853760,645.744873 421.853760,661.901611
|
||||
C386.208771,661.901611 350.772034,661.901611 314.709351,661.901611
|
||||
C314.709351,690.832581 314.709351,719.517822 314.709351,748.416748
|
||||
C293.998413,748.416748 273.913696,748.416748 253.324738,748.416748
|
||||
C253.324738,679.049988 253.324738,609.787170 253.322159,540.029297
|
||||
z"/>
|
||||
<path fill="#020202" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M492.003174,396.000000
|
||||
C492.002960,384.522308 492.002960,373.544617 492.002960,362.241943
|
||||
C503.905975,362.241943 515.652466,362.241943 527.973755,362.241943
|
||||
C527.973755,350.904510 527.973755,339.860962 527.973755,328.555176
|
||||
C565.268433,328.555176 601.986938,328.555176 639.150879,328.555176
|
||||
C639.150879,339.765076 639.150879,350.688141 639.150879,361.929138
|
||||
C652.186462,361.929138 664.783020,361.929138 677.734863,361.929138
|
||||
C677.734863,394.973724 677.734863,427.692261 677.734863,460.745056
|
||||
C665.122131,460.745056 652.522522,460.745056 639.460571,460.745056
|
||||
C639.460571,471.464661 639.460571,481.874329 639.460571,492.669708
|
||||
C602.271057,492.669708 565.543945,492.669708 528.313721,492.669708
|
||||
C528.313721,482.654999 528.313721,472.416870 528.313721,461.703796
|
||||
C516.107178,461.703796 504.388947,461.703796 492.003387,461.703796
|
||||
C492.003387,439.651642 492.003387,418.075806 492.003174,396.000000
|
||||
M614.295959,428.499878
|
||||
C614.295959,410.563873 614.295959,392.627899 614.295959,374.437195
|
||||
C594.356567,374.437195 574.817383,374.437195 555.216553,374.437195
|
||||
C555.216553,399.301392 555.216553,423.880280 555.216553,448.470581
|
||||
C574.879578,448.470581 594.303711,448.470581 614.296204,448.470581
|
||||
C614.296204,442.071533 614.296204,435.785706 614.295959,428.499878
|
||||
z"/>
|
||||
<path fill="#F7AF0C" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M680.628540,342.348541
|
||||
C680.176758,325.700775 679.986694,309.383881 680.064697,293.068268
|
||||
C680.069885,291.995911 682.113281,290.017670 683.227722,290.003845
|
||||
C699.555664,289.801270 715.887634,289.797485 732.215942,289.983917
|
||||
C733.476685,289.998322 735.793213,291.775330 735.806641,292.757812
|
||||
C736.031189,309.222900 735.967041,325.691925 735.967041,342.679718
|
||||
C717.438782,342.679718 699.195862,342.679718 680.628540,342.348541
|
||||
z"/>
|
||||
<path fill="#FCFCFC" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M614.296082,428.999878
|
||||
C614.296204,435.785706 614.296204,442.071533 614.296204,448.470581
|
||||
C594.303711,448.470581 574.879578,448.470581 555.216553,448.470581
|
||||
C555.216553,423.880280 555.216553,399.301392 555.216553,374.437195
|
||||
C574.817383,374.437195 594.356567,374.437195 614.295959,374.437195
|
||||
C614.295959,392.627899 614.295959,410.563873 614.296082,428.999878
|
||||
z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
1
web/public/icons/qwen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z" fill="url(#lobe-icons-qwen-fill)" fill-rule="nonzero"></path><defs><linearGradient id="lobe-icons-qwen-fill" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#6336E7" stop-opacity=".84"></stop><stop offset="100%" stop-color="#6F69F7" stop-opacity=".84"></stop></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
BIN
web/public/images/logo.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
web/public/images/main.png
Normal file
|
After Width: | Height: | Size: 676 KiB |
302
web/src/App.tsx
@@ -2,10 +2,17 @@ import { useEffect, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { api } from './lib/api';
|
||||
import { EquityChart } from './components/EquityChart';
|
||||
import { AITradersPage } from './components/AITradersPage';
|
||||
import { LoginPage } from './components/LoginPage';
|
||||
import { RegisterPage } from './components/RegisterPage';
|
||||
import { CompetitionPage } from './components/CompetitionPage';
|
||||
import { LandingPage } from './pages/LandingPage';
|
||||
import AILearning from './components/AILearning';
|
||||
import { LanguageProvider, useLanguage } from './contexts/LanguageContext';
|
||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||
import { t, type Language } from './i18n/translations';
|
||||
import { useSystemConfig } from './hooks/useSystemConfig';
|
||||
import { Zap } from 'lucide-react';
|
||||
import type {
|
||||
SystemStatus,
|
||||
AccountInfo,
|
||||
@@ -15,10 +22,27 @@ import type {
|
||||
TraderInfo,
|
||||
} from './types';
|
||||
|
||||
type Page = 'competition' | 'trader';
|
||||
type Page = 'competition' | 'traders' | 'trader';
|
||||
|
||||
// 获取友好的AI模型名称
|
||||
function getModelDisplayName(modelId: string): string {
|
||||
switch (modelId.toLowerCase()) {
|
||||
case 'deepseek':
|
||||
return 'DeepSeek';
|
||||
case 'qwen':
|
||||
return 'Qwen';
|
||||
case 'claude':
|
||||
return 'Claude';
|
||||
default:
|
||||
return modelId.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
const { language, setLanguage } = useLanguage();
|
||||
const { user, token, logout, isLoading } = useAuth();
|
||||
const { config: systemConfig, loading: configLoading } = useSystemConfig();
|
||||
const [route, setRoute] = useState(window.location.pathname);
|
||||
|
||||
// 从URL hash读取初始页面状态(支持刷新保持页面)
|
||||
const getInitialPage = (): Page => {
|
||||
@@ -45,11 +69,11 @@ function App() {
|
||||
return () => window.removeEventListener('hashchange', handleHashChange);
|
||||
}, []);
|
||||
|
||||
// 切换页面时更新URL hash
|
||||
const navigateToPage = (page: Page) => {
|
||||
setCurrentPage(page);
|
||||
window.location.hash = page === 'competition' ? '' : 'trader';
|
||||
};
|
||||
// 切换页面时更新URL hash (当前通过按钮直接调用setCurrentPage,这个函数暂时保留用于未来扩展)
|
||||
// const navigateToPage = (page: Page) => {
|
||||
// setCurrentPage(page);
|
||||
// window.location.hash = page === 'competition' ? '' : 'trader';
|
||||
// };
|
||||
|
||||
// 获取trader列表
|
||||
const { data: traders } = useSWR<TraderInfo[]>('traders', api.getTraders, {
|
||||
@@ -133,59 +157,120 @@ function App() {
|
||||
|
||||
const selectedTrader = traders?.find((t) => t.trader_id === selectedTraderId);
|
||||
|
||||
// Handle routing
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
setRoute(window.location.pathname);
|
||||
};
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}, []);
|
||||
|
||||
// Show loading spinner while checking auth or config
|
||||
if (isLoading || configLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center" style={{ background: '#0B0E11' }}>
|
||||
<div className="text-center">
|
||||
<img src="/images/logo.png" alt="NoFx Logo" className="w-16 h-16 mx-auto mb-4 animate-pulse" />
|
||||
<p style={{ color: '#EAECEF' }}>{t('loading', language)}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show landing page for root route when not authenticated
|
||||
if (!systemConfig?.admin_mode && (!user || !token)) {
|
||||
if (route === '/login') {
|
||||
return <LoginPage />;
|
||||
}
|
||||
if (route === '/register') {
|
||||
return <RegisterPage />;
|
||||
}
|
||||
// Default to landing page when not authenticated
|
||||
return <LandingPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: '#0B0E11', color: '#EAECEF' }}>
|
||||
{/* Header - Binance Style */}
|
||||
<header className="glass sticky top-0 z-50 backdrop-blur-xl">
|
||||
<div className="max-w-[1920px] mx-auto px-3 sm:px-6 py-3 sm:py-4">
|
||||
{/* Mobile: Two rows, Desktop: Single row */}
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
{/* Left: Logo and Title */}
|
||||
<div className="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<div className="w-7 h-7 sm:w-8 sm:h-8 rounded-full flex items-center justify-center text-lg sm:text-xl" style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
|
||||
⚡
|
||||
<div className="max-w-[1920px] mx-auto px-6 py-4">
|
||||
<div className="relative flex items-center">
|
||||
{/* Left - Logo and Title */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
<img src="/icons/nofx.svg?v=2" alt="NOFX" className="w-8 h-8" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-base sm:text-xl font-bold leading-tight" style={{ color: '#EAECEF' }}>
|
||||
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
||||
{t('appTitle', language)}
|
||||
</h1>
|
||||
<p className="text-xs mono hidden sm:block" style={{ color: '#848E9C' }}>
|
||||
<p className="text-xs mono" style={{ color: '#848E9C' }}>
|
||||
{t('subtitle', language)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Controls - Wrap on mobile */}
|
||||
<div className="flex items-center gap-2 flex-wrap md:flex-nowrap">
|
||||
{/* GitHub Link - Hidden on mobile, icon only on tablet */}
|
||||
<a
|
||||
href="https://github.com/tinkle-community/nofx"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hidden sm:flex items-center gap-2 px-2 md:px-3 py-1.5 md:py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
style={{ background: '#1E2329', color: '#848E9C', border: '1px solid #2B3139' }}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = '#2B3139';
|
||||
e.currentTarget.style.color = '#EAECEF';
|
||||
e.currentTarget.style.borderColor = '#F0B90B';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = '#1E2329';
|
||||
e.currentTarget.style.color = '#848E9C';
|
||||
e.currentTarget.style.borderColor = '#2B3139';
|
||||
}}
|
||||
|
||||
{/* Center - Page Toggle (absolutely positioned) */}
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2 flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||
<button
|
||||
onClick={() => setCurrentPage('competition')}
|
||||
className={`px-3 py-2 rounded text-sm font-semibold transition-all`}
|
||||
style={currentPage === 'competition'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
<span className="hidden md:inline">GitHub</span>
|
||||
</a>
|
||||
{t('aiCompetition', language)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('traders')}
|
||||
className={`px-3 py-2 rounded text-sm font-semibold transition-all`}
|
||||
style={currentPage === 'traders'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
{t('aiTraders', language)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('trader')}
|
||||
className={`px-3 py-2 rounded text-sm font-semibold transition-all`}
|
||||
style={currentPage === 'trader'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
{t('tradingPanel', language)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Right - Actions */}
|
||||
<div className="ml-auto flex items-center gap-3">
|
||||
|
||||
{/* User Info - Only show if not in admin mode */}
|
||||
{!systemConfig?.admin_mode && user && (
|
||||
<div className="flex items-center gap-2 px-3 py-2 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
<div className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold" style={{ background: '#F0B90B', color: '#000' }}>
|
||||
{user.email[0].toUpperCase()}
|
||||
</div>
|
||||
<span className="text-sm" style={{ color: '#EAECEF' }}>{user.email}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Admin Mode Indicator */}
|
||||
{systemConfig?.admin_mode && (
|
||||
<div className="flex items-center gap-2 px-3 py-2 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
<Zap className="w-4 h-4" style={{ color: '#F0B90B' }} />
|
||||
<span className="text-sm font-semibold" style={{ color: '#F0B90B' }}>{t('adminMode', language)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Language Toggle */}
|
||||
<div className="flex gap-0.5 sm:gap-1 rounded p-0.5 sm:p-1" style={{ background: '#1E2329' }}>
|
||||
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||
<button
|
||||
onClick={() => setLanguage('zh')}
|
||||
className="px-2 sm:px-3 py-1 sm:py-1.5 rounded text-xs font-semibold transition-all"
|
||||
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
|
||||
style={language === 'zh'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
@@ -195,7 +280,7 @@ function App() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLanguage('en')}
|
||||
className="px-2 sm:px-3 py-1 sm:py-1.5 rounded text-xs font-semibold transition-all"
|
||||
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
|
||||
style={language === 'en'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
@@ -205,63 +290,15 @@ function App() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Page Toggle */}
|
||||
<div className="flex gap-0.5 sm:gap-1 rounded p-0.5 sm:p-1" style={{ background: '#1E2329' }}>
|
||||
{/* Logout Button - Only show if not in admin mode */}
|
||||
{!systemConfig?.admin_mode && (
|
||||
<button
|
||||
onClick={() => navigateToPage('competition')}
|
||||
className="px-2 sm:px-4 py-1.5 sm:py-2 rounded text-xs sm:text-sm font-semibold transition-all"
|
||||
style={currentPage === 'competition'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
onClick={logout}
|
||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D', border: '1px solid rgba(246, 70, 93, 0.2)' }}
|
||||
>
|
||||
{t('competition', language)}
|
||||
{t('logout', language)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigateToPage('trader')}
|
||||
className="px-2 sm:px-4 py-1.5 sm:py-2 rounded text-xs sm:text-sm font-semibold transition-all"
|
||||
style={currentPage === 'trader'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
{t('details', language)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Trader Selector (only show on trader page) */}
|
||||
{currentPage === 'trader' && traders && traders.length > 0 && (
|
||||
<select
|
||||
value={selectedTraderId}
|
||||
onChange={(e) => setSelectedTraderId(e.target.value)}
|
||||
className="rounded px-2 sm:px-3 py-1.5 sm:py-2 text-xs sm:text-sm font-medium cursor-pointer transition-colors flex-1 sm:flex-initial"
|
||||
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
>
|
||||
{traders.map((trader) => (
|
||||
<option key={trader.trader_id} value={trader.trader_id}>
|
||||
{trader.trader_name} ({trader.ai_model.toUpperCase()})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
|
||||
{/* Status Indicator (only show on trader page) */}
|
||||
{currentPage === 'trader' && status && (
|
||||
<div
|
||||
className="flex items-center gap-1.5 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 rounded"
|
||||
style={status.is_running
|
||||
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81', border: '1px solid rgba(14, 203, 129, 0.2)' }
|
||||
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D', border: '1px solid rgba(246, 70, 93, 0.2)' }
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${status.is_running ? 'pulse-glow' : ''}`}
|
||||
style={{ background: status.is_running ? '#0ECB81' : '#F6465D' }}
|
||||
/>
|
||||
<span className="font-semibold mono text-xs">
|
||||
{t(status.is_running ? 'running' : 'stopped', language)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -272,6 +309,13 @@ function App() {
|
||||
<main className="max-w-[1920px] mx-auto px-6 py-6">
|
||||
{currentPage === 'competition' ? (
|
||||
<CompetitionPage />
|
||||
) : currentPage === 'traders' ? (
|
||||
<AITradersPage
|
||||
onTraderSelect={(traderId) => {
|
||||
setSelectedTraderId(traderId);
|
||||
setCurrentPage('trader');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TraderDetailsPage
|
||||
selectedTrader={selectedTrader}
|
||||
@@ -282,6 +326,9 @@ function App() {
|
||||
stats={stats}
|
||||
lastUpdate={lastUpdate}
|
||||
language={language}
|
||||
traders={traders}
|
||||
selectedTraderId={selectedTraderId}
|
||||
onTraderSelect={setSelectedTraderId}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
@@ -291,12 +338,12 @@ function App() {
|
||||
<div className="max-w-[1920px] mx-auto px-6 py-6 text-center text-sm" style={{ color: '#5E6673' }}>
|
||||
<p>{t('footerTitle', language)}</p>
|
||||
<p className="mt-1">{t('footerWarning', language)}</p>
|
||||
<div className="mt-4 flex items-center justify-center gap-2">
|
||||
<div className="mt-4">
|
||||
<a
|
||||
href="https://github.com/tinkle-community/nofx"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
className="inline-flex items-center gap-2 px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
style={{ background: '#1E2329', color: '#848E9C', border: '1px solid #2B3139' }}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = '#2B3139';
|
||||
@@ -312,7 +359,7 @@ function App() {
|
||||
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
<span>Star on GitHub</span>
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -330,8 +377,14 @@ function TraderDetailsPage({
|
||||
decisions,
|
||||
lastUpdate,
|
||||
language,
|
||||
traders,
|
||||
selectedTraderId,
|
||||
onTraderSelect,
|
||||
}: {
|
||||
selectedTrader?: TraderInfo;
|
||||
traders?: TraderInfo[];
|
||||
selectedTraderId?: string;
|
||||
onTraderSelect: (traderId: string) => void;
|
||||
status?: SystemStatus;
|
||||
account?: AccountInfo;
|
||||
positions?: Position[];
|
||||
@@ -372,14 +425,35 @@ function TraderDetailsPage({
|
||||
<div>
|
||||
{/* Trader Header */}
|
||||
<div className="mb-6 rounded p-6 animate-scale-in" style={{ background: 'linear-gradient(135deg, rgba(240, 185, 11, 0.15) 0%, rgba(252, 213, 53, 0.05) 100%)', border: '1px solid rgba(240, 185, 11, 0.2)', boxShadow: '0 0 30px rgba(240, 185, 11, 0.15)' }}>
|
||||
<h2 className="text-2xl font-bold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<span className="w-10 h-10 rounded-full flex items-center justify-center text-xl" style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
|
||||
🤖
|
||||
</span>
|
||||
{selectedTrader.trader_name}
|
||||
</h2>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<h2 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<span className="w-10 h-10 rounded-full flex items-center justify-center text-xl" style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
|
||||
🤖
|
||||
</span>
|
||||
{selectedTrader.trader_name}
|
||||
</h2>
|
||||
|
||||
{/* Trader Selector */}
|
||||
{traders && traders.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm" style={{ color: '#848E9C' }}>{t('switchTrader', language)}:</span>
|
||||
<select
|
||||
value={selectedTraderId}
|
||||
onChange={(e) => onTraderSelect(e.target.value)}
|
||||
className="rounded px-3 py-2 text-sm font-medium cursor-pointer transition-colors"
|
||||
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
>
|
||||
{traders.map((trader) => (
|
||||
<option key={trader.trader_id} value={trader.trader_id}>
|
||||
{trader.trader_name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm" style={{ color: '#848E9C' }}>
|
||||
<span>AI Model: <span className="font-semibold" style={{ color: selectedTrader.ai_model === 'qwen' ? '#c084fc' : '#60a5fa' }}>{selectedTrader.ai_model.toUpperCase()}</span></span>
|
||||
<span>AI Model: <span className="font-semibold" style={{ color: selectedTrader.ai_model.includes('qwen') ? '#c084fc' : '#60a5fa' }}>{getModelDisplayName(selectedTrader.ai_model.split('_').pop() || selectedTrader.ai_model)}</span></span>
|
||||
{status && (
|
||||
<>
|
||||
<span>•</span>
|
||||
@@ -395,9 +469,9 @@ function TraderDetailsPage({
|
||||
{account && (
|
||||
<div className="mb-4 p-3 rounded text-xs font-mono" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
<div style={{ color: '#848E9C' }}>
|
||||
🔄 Last Update: {lastUpdate} | Total Equity: {account.total_equity?.toFixed(2) || '0.00'} |
|
||||
Available: {account.available_balance?.toFixed(2) || '0.00'} | P&L: {account.total_pnl?.toFixed(2) || '0.00'}{' '}
|
||||
({account.total_pnl_pct?.toFixed(2) || '0.00'}%)
|
||||
🔄 Last Update: {lastUpdate} | Total Equity: {account?.total_equity?.toFixed(2) || '0.00'} |
|
||||
Available: {account?.available_balance?.toFixed(2) || '0.00'} | P&L: {account?.total_pnl?.toFixed(2) || '0.00'}{' '}
|
||||
({account?.total_pnl_pct?.toFixed(2) || '0.00'}%)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -721,11 +795,13 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua
|
||||
);
|
||||
}
|
||||
|
||||
// Wrap App with LanguageProvider
|
||||
export default function AppWithLanguage() {
|
||||
// Wrap App with providers
|
||||
export default function AppWithProviders() {
|
||||
return (
|
||||
<LanguageProvider>
|
||||
<App />
|
||||
<AuthProvider>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
</LanguageProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import useSWR from 'swr';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { t } from '../i18n/translations';
|
||||
import { api } from '../lib/api';
|
||||
import { Brain, BarChart3, TrendingUp, TrendingDown, Sparkles, Coins, Trophy, ScrollText, Lightbulb } from 'lucide-react';
|
||||
|
||||
interface TradeOutcome {
|
||||
symbol: string;
|
||||
@@ -72,7 +73,9 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
if (!performance) {
|
||||
return (
|
||||
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
<div style={{ color: '#848E9C' }}>📊 {t('loading', language)}</div>
|
||||
<div className="flex items-center gap-2" style={{ color: '#848E9C' }}>
|
||||
<BarChart3 className="w-4 h-4" /> {t('loading', language)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -81,7 +84,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
return (
|
||||
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xl">🧠</span>
|
||||
<Brain className="w-5 h-5" style={{ color: '#8B5CF6' }} />
|
||||
<h2 className="text-lg font-bold" style={{ color: '#EAECEF' }}>{t('aiLearning', language)}</h2>
|
||||
</div>
|
||||
<div style={{ color: '#848E9C' }}>
|
||||
@@ -109,12 +112,12 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
filter: 'blur(60px)'
|
||||
}} />
|
||||
<div className="relative flex items-center gap-4">
|
||||
<div className="w-16 h-16 rounded-2xl flex items-center justify-center text-3xl" style={{
|
||||
<div className="w-16 h-16 rounded-2xl flex items-center justify-center" style={{
|
||||
background: 'linear-gradient(135deg, #8B5CF6 0%, #6366F1 100%)',
|
||||
boxShadow: '0 8px 24px rgba(139, 92, 246, 0.5)',
|
||||
border: '2px solid rgba(255, 255, 255, 0.1)'
|
||||
}}>
|
||||
🧠
|
||||
<Brain className="w-8 h-8" style={{ color: '#FFF' }} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold mb-1" style={{
|
||||
@@ -149,7 +152,9 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
<div className="text-4xl font-bold mono mb-1" style={{ color: '#E0E7FF' }}>
|
||||
{performance.total_trades}
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#6366F1' }}>📊 Trades</div>
|
||||
<div className="text-xs flex items-center gap-1" style={{ color: '#6366F1' }}>
|
||||
<BarChart3 className="w-3 h-3" /> Trades
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -199,7 +204,9 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
<div className="text-4xl font-bold mono mb-1" style={{ color: '#10B981' }}>
|
||||
+{(performance.avg_win || 0).toFixed(2)}
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#6EE7B7' }}>📈 USDT Average</div>
|
||||
<div className="text-xs flex items-center gap-1" style={{ color: '#6EE7B7' }}>
|
||||
<TrendingUp className="w-3 h-3" /> USDT Average
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -220,7 +227,9 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
<div className="text-4xl font-bold mono mb-1" style={{ color: '#F87171' }}>
|
||||
{(performance.avg_loss || 0).toFixed(2)}
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#FCA5A5' }}>📉 USDT Average</div>
|
||||
<div className="text-xs flex items-center gap-1" style={{ color: '#FCA5A5' }}>
|
||||
<TrendingDown className="w-3 h-3" /> USDT Average
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -239,11 +248,11 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
}} />
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" style={{
|
||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{
|
||||
background: 'rgba(139, 92, 246, 0.3)',
|
||||
border: '1px solid rgba(139, 92, 246, 0.5)'
|
||||
}}>
|
||||
🧬
|
||||
<Sparkles className="w-6 h-6" style={{ color: '#A78BFA' }} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-lg font-bold" style={{ color: '#C4B5FD' }}>夏普比率</div>
|
||||
@@ -307,11 +316,11 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
}} />
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" style={{
|
||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{
|
||||
background: 'rgba(240, 185, 11, 0.3)',
|
||||
border: '1px solid rgba(240, 185, 11, 0.5)'
|
||||
}}>
|
||||
💰
|
||||
<Coins className="w-6 h-6" style={{ color: '#FCD34D' }} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-lg font-bold" style={{ color: '#FCD34D' }}>
|
||||
@@ -373,7 +382,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
boxShadow: '0 4px 16px rgba(16, 185, 129, 0.1)'
|
||||
}}>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-2xl">🏆</span>
|
||||
<Trophy className="w-6 h-6" style={{ color: '#10B981' }} />
|
||||
<span className="text-sm font-semibold" style={{ color: '#6EE7B7' }}>{t('bestPerformer', language)}</span>
|
||||
</div>
|
||||
<div className="text-3xl font-bold mono mb-1" style={{ color: '#10B981' }}>
|
||||
@@ -395,7 +404,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
boxShadow: '0 4px 16px rgba(248, 113, 113, 0.1)'
|
||||
}}>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-2xl">📉</span>
|
||||
<TrendingDown className="w-6 h-6" style={{ color: '#F87171' }} />
|
||||
<span className="text-sm font-semibold" style={{ color: '#FCA5A5' }}>{t('worstPerformer', language)}</span>
|
||||
</div>
|
||||
<div className="text-3xl font-bold mono mb-1" style={{ color: '#F87171' }}>
|
||||
@@ -428,7 +437,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<h3 className="font-bold flex items-center gap-2 text-lg" style={{ color: '#E0E7FF' }}>
|
||||
📊 {t('symbolPerformance', language)}
|
||||
<BarChart3 className="w-5 h-5" /> {t('symbolPerformance', language)}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="overflow-y-auto" style={{ maxHeight: 'calc(100vh - 280px)' }}>
|
||||
@@ -488,7 +497,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-2xl">📜</span>
|
||||
<ScrollText className="w-6 h-6" style={{ color: '#FCD34D' }} />
|
||||
<div>
|
||||
<h3 className="font-bold text-lg" style={{ color: '#FCD34D' }}>{t('tradeHistory', language)}</h3>
|
||||
<p className="text-xs" style={{ color: '#94A3B8' }}>
|
||||
@@ -631,7 +640,9 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
})
|
||||
) : (
|
||||
<div className="p-6 text-center">
|
||||
<div className="text-4xl mb-2 opacity-50">📜</div>
|
||||
<div className="mb-2 flex justify-center opacity-50">
|
||||
<ScrollText className="w-10 h-10" style={{ color: '#94A3B8' }} />
|
||||
</div>
|
||||
<div style={{ color: '#94A3B8' }}>{t('noCompletedTrades', language)}</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -646,11 +657,11 @@ export default function AILearning({ traderId }: AILearningProps) {
|
||||
boxShadow: '0 4px 16px rgba(240, 185, 11, 0.1)'
|
||||
}}>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-10 h-10 rounded-lg flex items-center justify-center text-xl flex-shrink-0" style={{
|
||||
<div className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0" style={{
|
||||
background: 'rgba(240, 185, 11, 0.2)',
|
||||
border: '1px solid rgba(240, 185, 11, 0.3)'
|
||||
}}>
|
||||
💡
|
||||
<Lightbulb className="w-5 h-5" style={{ color: '#FCD34D' }} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold mb-3 text-base" style={{ color: '#FCD34D' }}>{t('howAILearns', language)}</h3>
|
||||
|
||||