شغّل اختبارات PineScript التاريخية في CI (GitHub Actions، GitLab، أي شيء يدعم Docker)
كل commit إلى مستودع استراتيجيتك يُطلق اختباراً تاريخياً. المطابقة تُؤكَّد، الانحدارات تُسقط البناء. نفس صورة Docker التي تشغّلها محلّياً، تُنفَّذ في الـ runner لديك.
لماذا المطابقة في CI
الاستراتيجيات تنحرف مثل أي كود. إعادة تسمية معامل، إعادة هيكلة دالة مساعدة، تغيير في API لـ Pine v6 لم تلاحظه — أي من هذه يمكن أن يُغيّر الإشارات صامتاً دون كسر أي اختبار قائم. بدون بوّابة مطابقة في CI، commit صنعته قبل شهرين يمكن أن يُغيّر قائمة صفقات الأسبوع الماضي ولن تكتشف ذلك إلا حين يبدو P&L المباشر خاطئاً.
انضباط إلزام قائمة صفقاتك المرجعية بجانب مصدر استراتيجيتك هو نفس الانضباط الذي يجعل البرمجيات موثوقة: تصف المخرج المتوقَّع، ويفشل بناؤك إذا انحرف المخرج الفعلي. لاختبار تاريخي، المخرج المتوقَّع هو قائمة الصفقات — شمعة الدخول، شمعة الخروج، الاتجاه، الحجم، سعر التنفيذ — مقابل بيانات تاريخية مثبَّتة.
حين تعمل تلك المقارنة على كل commit، تحصل على آلية ضمان: السلوك التاريخي لاستراتيجيتك يستطيع التحسّن فقط، لا التراجع بالخطأ. تعرف exactly أي commit غيّر المخرج، لأنّ البناء فشل على ذلك commit. لديك git blame لمنحنى حقوق الملكية.
هذا أهمّ ممّا يدركه معظم الـ quants في البداية. الفجوة بين «اختبرت هذه الاستراتيجية» و«لديّ سجلّ قابل للاستنساخ ومحفوظ بإصدارات لكل إشارة تاريخية أنتجتها هذه الاستراتيجية» ضخمة. الأولى لقطة شاشة. الثانية مسار تدقيق. CI هو كيف تحافظ على الثانية بدون جهد يدوي على كل push.
PineForge يُتيح هذا لاستراتيجيات Pine لأنّ الـ runtime صورة Docker: تعمل أينما يعمل Docker، تُنتج مخرجاً حتمياً عند نفس المدخلات، وتُرجع مخطّط JSON مستقرّاً آمناً للتحليل في سكربتات shell. لا متصفّح، لا تدفّق مصادقة، لا حدّ معدّل على تنفيذ الاختبار التاريخي نفسه.
مثال GitHub Actions
سير العمل الكامل: استنسخ استراتيجيتك، حوّلها إلى كائن مشترك عبر codegen API، شغّل الاختبار التاريخي مقابل OHLCV CSV المثبَّت، حلّل تقرير JSON بـ jq، قارن مع الأساس المُلتزَم به، وأسقط البناء إذا تجاوز فرق صافي PnL عتبتك.
أضف مفتاح PineForge API كسرّ مستودع باسم PINEFORGE_API_KEY، ثم أنشئ .github/workflows/backtest.yml:
name: Backtest parity
on:
push:
branches: [main, "feat/**"]
pull_request:
jobs:
backtest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Transpile strategy to .so
run: |
curl -s https://api.pineforge.io/v1/codegen \
-H "Authorization: Bearer ${{ secrets.PINEFORGE_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"source": "'"$(cat strategy.pine | jq -Rs .)"'"}' \
| jq -r '.artifact_url' \
| xargs curl -sL -o strategy.so
- name: Pull PineForge runtime
run: docker pull ghcr.io/pineforge/runtime:latest
- name: Run backtest
run: |
docker run --rm \
-v "$PWD/strategy.so":/strategy.so \
-v "$PWD/data/ohlcv.csv":/data.csv \
ghcr.io/pineforge/runtime:latest \
run /strategy.so --data /data.csv --output json \
> report.json
- name: Assert parity
run: |
ACTUAL=$(jq '.summary.net_pnl' report.json)
BASELINE=$(jq '.summary.net_pnl' baseline/report.json)
DELTA=$(echo "$ACTUAL $BASELINE" | awk '{d=$1-$2; print (d<0)?-d:d}')
THRESHOLD="0.01"
if awk "BEGIN{exit !($DELTA > $THRESHOLD)}"; then
echo "Parity drift: net_pnl delta $DELTA exceeds threshold $THRESHOLD"
diff <(jq '.trades' baseline/report.json) \
<(jq '.trades' report.json)
exit 1
fi
echo "Parity OK — delta $DELTA (threshold $THRESHOLD)"احفظ تقرير الأساس في baseline/report.jsonفي المستودع، مُلتزَم به بجانب مصدر الاستراتيجية. حين يُغيّر PR سلوك الاستراتيجية عمداً، يُحدّث المؤلّف الأساس ضمن الـ PR. ذلك الفرق يصبح سجلاً دائماً لما تغيّر ولماذا.
نفس سير العمل يعمل بشكل متطابق على GitLab CI أو Bitbucket Pipelines أو أي runner يدعم Docker — بدّل بنية YAML، احتفظ بأوامر shell.
أكواد الخروج وتأكيد المطابقة
runtime PineForge يخرج بكود 0عند تشغيل نظيف وغير صفر عند أي خطأ في المحرّك (Pine معطوب، built-in غير مدعوم، فشل تحليل ملف بيانات). أنظمة CI تلتقط أكواد الخروج هذه تلقائياً — لا يلزم معالجة خاصّة لأخطاء على مستوى المحرّك.
تأكيد المطابقة شأن منفصل، يُنفَّذ في سير عملك. مخطّط تقرير JSON مستقرّ عبر إصدارات patch:
{
"summary": {
"total_trades": 47,
"net_pnl": 12483.50,
"max_drawdown": 3201.00,
"profit_factor": 1.82,
"sharpe_ratio": 1.34,
"win_rate": 0.617
},
"trades": [
{
"entry_bar": 142,
"exit_bar": 156,
"direction": "long",
"size": 1.0,
"entry_price": 42310.5,
"exit_price": 44820.0,
"pnl": 2509.50,
"exit_reason": "strategy.close"
}
]
}لفحص مطابقة عددي بسيط، قارن summary.net_pnl و summary.total_trades مع الأساس. لمطابقة صفقةً بصفقة صارمة، قارن مصفوفة tradesكاملة — فهارس شموع الدخول والخروج، الاتجاهات، وأسعار التنفيذ ينبغي أن تتطابق كلّها. حين لا تتطابق، مخرج المقارنة يحدّد بالضبط أي صفقة انحرفت، على أي شمعة كانت، وما هو فرق سعر التنفيذ.
عتبة عملية للحقول العددية: اقبل فروقات الفاصلة العائمة حتى 0.01(سنت واحد، أو نقطة أساس واحدة على سلسلة معيارية) وأسقط أي شيء أكبر. لمقارنات صفقةً بصفقة، أي عدم تطابق في entry_bar أو exit_barهو دائماً فشل قاطع بغضّ النظر عن قرب PnL — صفقة على شمعة مختلفة تعني أنّ منطق إشارتك تغيّر.
الفِرَق التي تتبنّى مطابقة CI تُشغّل عادةً تحديث الأساس كخطوة سير عمل منفصلة محروسة بعلامة PR. بهذه الطريقة، تحسين متعمَّد للاستراتيجية يُوثَّق في خط زمني الـ PR — فرق الأساس يظهر في مراجعة الكود بجانب تغيير Pine الذي تسبّب فيه.