Files
gfil-blog/build_blog.py

582 lines
51 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""GFIL BLOG 构建脚本 — 多语言 SEO + 工具页"""
import os, re, json, io, sys, argparse, random, socket, paramiko, shutil, time
from datetime import datetime
from urllib.parse import quote
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CONTENT_DIR = os.path.join(BASE_DIR, "blog_content")
OUTPUT_DIR = os.path.join(BASE_DIR, "output")
SITE_URL = "https://blog.quant-view.xyz"
SITE_NAME = "GFIL Trading Insights"
GOOGLE_VERIFY = os.environ.get("GFIL_GOOGLE_VERIFY", "")
GA_ID = os.environ.get("GFIL_GA_ID", "")
SSH_HOST = os.environ.get("GFIL_SSH_HOST", "")
SSH_PORT = int(os.environ.get("GFIL_SSH_PORT", "22"))
SSH_USER = os.environ.get("GFIL_SSH_USER", "root")
SSH_PASS = os.environ.get("GFIL_SSH_PASS", "")
REMOTE_DIR = "/var/www/blog/"
HTTP_PROXY = os.environ.get("GFIL_HTTP_PROXY", "")
# ===== 多语言配置 =====
LANGUAGES = [
{'code': 'en', 'dir': '', 'label': 'English', 'locale': 'en_US', 'flag': 'US', 'dir_attr': 'ltr'},
{'code': 'zh', 'dir': 'zh', 'label': 'Chinese', 'locale': 'zh_CN', 'flag': 'CN', 'dir_attr': 'ltr'},
{'code': 'es', 'dir': 'es', 'label': 'Espanol', 'locale': 'es_ES', 'flag': 'ES', 'dir_attr': 'ltr'},
{'code': 'ar', 'dir': 'ar', 'label': 'Arabic', 'locale': 'ar_SA', 'flag': 'SA', 'dir_attr': 'rtl'},
]
SAME_AS_JSON = json.dumps(['https://gfil-intel.xyz', 'https://t.me/GFIL_Trading', 'https://discord.gg/GMmMCD4MCr'])
def LANG(key, code):
"""Get translated text"""
d = LANG_DICT.get(key, {})
return d.get(code, d.get("en", key))
LANG_DICT = {
"site_name": {"en": "GFIL Trading Insights", "zh": "GFIL 交易洞察", "es": "GFIL Trading Insights", "ar": "GFIL Trading Insights"},
"nav_sub": {"en": "Institutional-grade market intelligence", "zh": "机构级市场情报", "es": "Inteligencia de mercado institucional", "ar": "ذكاء سوقي على المستوى المؤسسي"},
"tools_title": {"en": "30+ Free Trading Calculators", "zh": "30+款免费交易计算器", "es": "30+ Calculadoras de Trading Gratis", "ar": "30+ حاسبة تداول مجانية"},
"tools_desc": {"en": "Position size, pip value, profit factor, Fibonacci, ATR, Kelly, and more", "zh": "仓位计算、点值、盈亏比、斐波那契、ATR、凯利公式等", "es": "Tamanio de posicion, valor pip, factor de beneficio, Fibonacci, ATR, Kelly y mas", "ar": "حجم الصفقة، قيمة النقطة، عامل الربح، فيبوناتشي، ATR، كيلي والمزيد"},
"tools_try": {"en": "Try All 30+ Tools", "zh": "试用全部30+款工具", "es": "Probar las 30+ Herramientas", "ar": "جرب كل 30+ أداة"},
"tools_nav": {"en": "Free Trading Tools", "zh": "免费交易工具", "es": "Herramientas Gratis", "ar": "أدوات تداول مجانية"},
"cta_title": {"en": "Ready for Institutional-Grade Trading?", "zh": "准备好体验机构级交易了吗?", "es": "Listo para Trading de Nivel Institucional?", "ar": "هل أنت مستعد للتداول على المستوى المؤسسي؟"},
"cta_desc": {"en": "Get real-time market intelligence with sub-50ms WebSocket data.", "zh": "获取50毫秒级WebSocket实时市场数据。", "es": "Obten inteligencia de mercado en tiempo real con datos WebSocket.", "ar": "احصل على ذكاء سوقي فوري مع بيانات WebSocket."},
"cta_btn": {"en": "Access GFIL Terminal", "zh": "进入GFIL终端", "es": "Acceder al Terminal", "ar": "الدخول إلى منصة GFIL"},
"cta_tg": {"en": "Telegram", "zh": "电报群", "es": "Telegram", "ar": "تيليجرام"},
"nav_back": {"en": "Back to GFIL Trading Insights", "zh": "返回", "es": "Volver", "ar": "عودة"},
"footer_main": {"en": "Main Site", "zh": "主站", "es": "Sitio Principal", "ar": "الموقع الرئيسي"},
"footer_tools": {"en": "Free Tools", "zh": "免费工具", "es": "Herramientas Gratis", "ar": "أدوات مجانية"},
"footer_terminal": {"en": "GFIL Terminal", "zh": "GFIL终端", "es": "Terminal GFIL", "ar": "منصة GFIL"},
"contact": {"en": "Telegram: @GFIL_Trading | Discord", "zh": "微信: LDP161109 | QQ: 1987199079", "es": "Telegram: @GFIL_Trading | Discord", "ar": "Telegram: @GFIL_Trading | Discord"},
"share": {"en": "Share:", "zh": "分享:", "es": "Compartir:", "ar": "مشاركة:"},
"related": {"en": "You May Also Like", "zh": "相关推荐", "es": "Tambien Te Puede Gustar", "ar": "قد يعجبك أيضا"},
"news_title": {"en": "Get Weekly Trading Insights", "zh": "获取每周交易洞察", "es": "Analisis Semanal", "ar": "تحليلات أسبوعية"},
"news_desc": {"en": "Subscribe for exclusive analysis.", "zh": "订阅获取独家分析。", "es": "Suscribete para analisis exclusivo.", "ar": "اشترك للحصول على تحليلات حصرية."},
"news_btn": {"en": "Subscribe", "zh": "订阅", "es": "Suscribirse", "ar": "اشتراك"},
"news_ph": {"en": "Your email address", "zh": "您的邮箱地址", "es": "Tu correo electronico", "ar": "بريدك الإلكتروني"},
}
# ===== 文章列表 =====
ARTICLES = [
{'id': 'art-1', 'num': '1', 'title': 'GFIL BOSS PANEL v7.0 Review', 'desc': 'In-depth review of GFIL BOSS PANEL v7.0: real-time WebSocket data, 30+ instruments, 20+ professional indicators, AI-powered analysis, and sub-50ms latency. How it compares to TradingView and MetaTrader.', 'tags': ['GFIL', 'Review', 'Trading Terminal'], 'filename': 'gfil-boss-panel-v70-review.html', 'date': '2026-05-25', 'related': [3, 7, 10]},
{'id': 'art-2', 'num': '2', 'title': 'Gold XAUUSD Trading in 2026', 'desc': 'Complete gold trading strategy using order flow analysis: cumulative delta, volume profile, absorption patterns, and GOFO rates. Why traditional RSI and MACD fail on XAUUSD and what to use instead.', 'tags': ['Gold', 'XAUUSD', 'Trading Strategy'], 'filename': 'gold-xauusd-trading-2026.html', 'date': '2026-05-24', 'related': [4, 6, 9]},
{'id': 'art-3', 'num': '3', 'title': 'TradingView vs GFIL BOSS', 'desc': 'Side-by-side comparison: TradingView REST polling (500ms-3s delay) vs GFIL WebSocket streaming (<50ms). NFP data: 30 REST updates vs 6,000 WebSocket pushes. What scalpers and day traders need to know.', 'tags': ['TradingView', 'Comparison', 'Speed'], 'filename': 'tradingview-vs-gfil-boss.html', 'date': '2026-05-23', 'related': [1, 7, 10]},
{'id': 'art-4', 'num': '4', 'title': 'Why 87% of Retail Traders Lose Money', 'desc': 'Data-driven analysis of why 87% of retail forex traders lose money: poor risk management, oversized positions, lack of edge, emotional trading, and data asymmetry vs institutions. How to be in the 13%.', 'tags': ['Trading Psychology', 'Data', 'Analysis'], 'filename': 'why-retail-traders-lose-money.html', 'date': '2026-05-22', 'related': [5, 7, 8]},
{'id': 'art-5', 'num': '5', 'title': 'Your Trading Activity is Being Tracked', 'desc': 'How brokers, HFT firms, and market makers track your stop losses, entry patterns, and position sizes. Understanding the surveillance ecosystem and how to protect your trading strategy.', 'tags': ['Security', 'Privacy', 'Trading'], 'filename': 'trading-activity-tracked.html', 'date': '2026-05-21', 'related': [4, 7, 10]},
{'id': 'art-6', 'num': '6', 'title': 'Forex Scalping in 2026', 'desc': 'Modern forex scalping strategies for 2026: London open breakout, Asian range fade, news spike scalping. Why sub-50ms WebSocket data is non-negotiable for consistent scalping profits.', 'tags': ['Forex', 'Scalping', 'Strategy'], 'filename': 'forex-scalping-2026.html', 'date': '2026-05-20', 'related': [2, 4, 7]},
{'id': 'art-7', 'num': '7', 'title': 'How Institutional Traders See Market Moves 15 Minutes Before Retail', 'desc': 'Institutional order flow tools revealed: cumulative delta divergence, absorption at key levels, POC migration, and dark pool prints. How institutions front-run retail order flow with professional data terminals.', 'tags': ['Institutional', 'Market Intelligence', 'Insider'], 'filename': 'institutional-traders-see-market-moves.html', 'date': '2026-05-19', 'related': [1, 3, 8]},
{'id': 'art-8', 'num': '8', 'title': 'The Rise of AI-Driven Market Intelligence', 'desc': 'How AI models (DeepSeek, Claude, GPT) are transforming market analysis: multi-timeframe pattern detection, sentiment analysis from news feeds, and real-time anomaly detection across 30+ instruments simultaneously.', 'tags': ['AI', 'Market Analysis', 'Future'], 'filename': 'ai-driven-market-intelligence.html', 'date': '2026-05-18', 'related': [4, 7, 10]},
{'id': 'art-9', 'num': '9', 'title': 'WTI Crude Oil in 2026', 'desc': 'WTI crude oil trading strategy: EIA inventory momentum, OPEC+ volatility plays, crack spread correlation. Energy sector analysis with order flow tools for oil traders.', 'tags': ['WTI', 'Crude Oil', 'Energy'], 'filename': 'wti-crude-oil-2026.html', 'date': '2026-05-17', 'related': [2, 4, 6]},
{'id': 'art-10', 'num': '10', 'title': 'GFIL BOSS PANEL FAQ', 'desc': 'Complete FAQ for GFIL BOSS PANEL: supported instruments, WebSocket data sources, available indicators, subscription plans, language support, and how to get started with institutional-grade trading tools.', 'tags': ['GFIL', 'FAQ', 'Getting Started'], 'filename': 'gfil-boss-panel-faq.html', 'date': '2026-05-16', 'related': [1, 3, 7]},
{'id': 'art-11', 'num': '11', 'title': 'Order Flow Trading', 'desc': 'Complete guide to order flow trading: footprint charts, cumulative delta, volume profile, absorption, iceberg orders, and POC migration. The institutional approach to reading market microstructure.', 'tags': ['Order Flow', 'Institutional', 'Trading Strategy'], 'filename': 'order-flow-trading.html', 'date': '2026-06-02', 'related': [4, 7, 13]},
{'id': 'art-12', 'num': '12', 'title': 'Bloomberg Terminal Alternatives', 'desc': 'Best free and low-cost Bloomberg Terminal alternatives for retail traders in 2026. Compare features, data sources, latency, and pricing across 7 professional trading platforms.', 'tags': ['Bloomberg', 'Trading Platform', 'Comparison'], 'filename': 'bloomberg-alternative.html', 'date': '2026-06-02', 'related': [1, 3, 10]},
{'id': 'art-13', 'num': '13', 'title': 'WebSocket vs REST API', 'desc': 'Technical deep-dive: WebSocket vs REST API for real-time trading data. NFP first minute benchmark — REST delivers 30 data points, WebSocket pushes 6,000. Latency analysis with code examples.', 'tags': ['WebSocket', 'Technology', 'Data Latency'], 'filename': 'websocket-vs-rest-api.html', 'date': '2026-06-02', 'related': [3, 6, 7]},
{'id': 'art-14', 'num': '14', 'title': 'How to Track Trading Signal Performance Like a Hedge Fund', 'desc': 'Professional signal tracking methodology: Sharpe ratio, profit factor, max drawdown, win rate by session, expectancy. Build a hedge-fund-grade performance dashboard with free tools.', 'tags': ['Signal Tracking', 'Performance', 'Risk Management'], 'filename': 'trading-signal-tracking.html', 'date': '2026-06-02', 'related': [1, 5, 10]},
{'id': 'art-15', 'num': '15', 'title': 'Anonymous Trading Platform', 'desc': 'How to trade anonymously: privacy-first platforms, no-KYC options, VPN/seedbox setup for traders. Protect your edge from broker surveillance and strategy copying.', 'tags': ['Privacy', 'Security', 'Anonymous Trading'], 'filename': 'anonymous-trading-platform.html', 'date': '2026-06-02', 'related': [5, 8, 12]},
{'id': 'art-16', 'num': '16', 'title': 'Position Size Calculator — The Complete Guide to Forex Risk Management in 2026', 'desc': 'Master position sizing: the mathematical formula, three professional risk models (Fixed Fractional, Kelly Criterion, Fixed Ratio), common mistakes that kill accounts, and a free calculator that handles cross-pair pip values and account currency conversion. CFTC data shows 74% of failing traders risk over 5% per trade.', 'tags': ['Risk Management', 'Position Sizing', 'Forex Education'], 'filename': 'position-size-calculator-guide.html', 'date': '2026-06-27', 'related': [4, 5, 14]},
{'id': 'art-17', 'num': '17', 'title': 'Gold XAUUSD Trading 2026 — Technical Tools, Price Drivers & Free Calculators', 'desc': 'Professional gold trading framework for 2026: central bank buying de-dollarization, real yield analysis, ATR-based stops vs fixed pip stops, Fibonacci pivot points for gold, institutional COT/ETF flow indicators, and the critical gold position size formula that differs from forex. Complete with institutional desk risk protocol.', 'tags': ['Gold', 'XAUUSD', 'Technical Analysis', 'Risk Management'], 'filename': 'gold-trading-2026-guide.html', 'date': '2026-06-27', 'related': [2, 6, 14]},
{'id': 'art-18', 'num': '18', 'title': 'SSH Tunnel Deployment for Trading Platforms — Lessons from China', 'desc': 'How to deploy a trading platform from China when ISPs block overseas SSH: the JD Cloud jumphost architecture, tar.gz deployment optimization, and the Cloudflare CDN cache trap that wasted an entire audit cycle. With Python Paramiko code examples.', 'tags': ['Infrastructure', 'Deployment', 'SSH', 'Engineering'], 'filename': 'ssh-tunnel-deployment-china.html', 'date': '2026-06-28', 'related': [13, 3, 10]},
{'id': 'art-19', 'num': '19', 'title': 'GitHub SEO for Trading Tools — 3 Mistakes That Kill Your Rankings', 'desc': 'Real-world lessons from deploying an open-source trading calculator library: shadow-banned GitHub accounts creating 204 dead links, fake repos in knowledge graphs destroying AI credibility, inconsistent numbers failing E-E-A-T checks, and the three-layer GitHub strategy that actually works.', 'tags': ['SEO', 'GitHub', 'Open Source', 'GEO'], 'filename': 'github-seo-trading-tools.html', 'date': '2026-06-28', 'related': [14, 5, 8]},
{'id': 'art-20', 'num': '20', 'title': 'Why Most Position Size Calculators Get Gold Wrong', 'desc': 'XAUUSD pip value is $1.00 per lot, not $10.00 like EURUSD. Most forex calculators use the wrong formula for gold, silver, crypto, and indices — producing position sizes 10x too large. Complete breakdown of correct formulas for 6 instrument types with pip value comparison table.', 'tags': ['Gold', 'XAUUSD', 'Position Sizing', 'Risk Management'], 'filename': 'gold-pip-value-calculator-wrong.html', 'date': '2026-06-28', 'related': [2, 17, 16]},
]
# ===== 文章标题翻译 =====
ART_TITLES = {
'zh': {
'1': 'GFIL BOSS PANEL v7.0 评测 — 新一代交易终端',
'2': '2026年黄金XAUUSD交易策略 — 订单流分析法',
'3': 'TradingView vs GFIL BOSS — 延迟速度全面对比',
'4': '为什么87%的散户交易者亏钱 — 数据揭示真相',
'5': '你的交易活动正在被追踪 — 如何保护你的策略',
'6': '2026外汇剥头皮策略 — 低延迟交易指南',
'7': '机构交易者如何比你早15分钟看到市场动向',
'8': 'AI驱动市场分析的崛起 — 人工智能如何改变交易',
'9': '2026年WTI原油交易策略 — 能源市场深度分析',
'10': 'GFIL BOSS PANEL 常见问题 — 新手入门指南',
'11': '订单流交易 — 机构交易员的核心武器',
'12': '彭博终端替代品 — 免费和低成本的交易平台选择',
'13': 'WebSocket vs REST API — 交易延迟的真相',
'14': '如何像对冲基金一样追踪交易信号绩效',
'15': '匿名交易平台 — 保护你的交易隐私',
},
'es': {
'1': 'GFIL BOSS PANEL v7.0 — Revisión del Terminal de Trading',
'2': 'Trading de Oro XAUUSD en 2026 — Estrategia con Order Flow',
'3': 'TradingView vs GFIL BOSS — Comparativa de Velocidad',
'4': 'Por Qué el 87% de los Traders Minoristas Pierde Dinero',
'5': 'Tu Actividad de Trading Está Siendo Rastreada',
'6': 'Scalping en Forex 2026 — Guía de Trading de Baja Latencia',
'7': 'Cómo los Institucionales Ven los Movimientos 15 Minutos Antes',
'8': 'El Auge del Análisis de Mercado con Inteligencia Artificial',
'9': 'Trading de Petróleo WTI en 2026 — Análisis del Mercado Energético',
'10': 'GFIL BOSS PANEL — Preguntas Frecuentes',
'11': 'Order Flow Trading — El Arma Secreta de los Institucionales',
'12': 'Alternativas a Bloomberg Terminal — Gratis y de Bajo Costo',
'13': 'WebSocket vs REST API — La Verdad Sobre la Latencia',
'14': 'Cómo Rastrear el Rendimiento de Señales Como un Hedge Fund',
'15': 'Plataforma de Trading Anónima — Protege tu Privacidad',
'16': 'Calculadora de Tamano de Posicion — Guia Completa de Gestion de Riesgo Forex 2026',
'17': 'Trading de Oro XAUUSD 2026 — Herramientas Tecnicas, Drivers de Precio y Calculadoras Gratis',
},
'ar': {
'1': 'مراجعة GFIL BOSS PANEL v7.0 — منصة تداول الجيل الجديد',
'2': 'تداول الذهب XAUUSD في 2026 — استراتيجية تدفق الأوامر',
'3': 'TradingView مقابل GFIL BOSS — مقارنة السرعة والأداء',
'4': 'لماذا يخسر 87% من المتداولين الأفراد أموالهم',
'5': 'نشاط تداولك مراقب — كيف تحمي استراتيجيتك',
'6': 'سكالبينج الفوركس 2026 — دليل التداول منخفض التأخير',
'7': 'كيف يرى المتداولون المؤسسيون تحركات السوق قبلك بـ15 دقيقة',
'8': 'صعود تحليل السوق بالذكاء الاصطناعي',
'9': 'تداول نفط WTI في 2026 — تحليل سوق الطاقة',
'10': 'GFIL BOSS PANEL — الأسئلة الشائعة',
'11': 'تداول تدفق الأوامر — السلاح السري للمؤسسات',
'12': 'بدائل بلومبرج — منصات تداول مجانية ومنخفضة التكلفة',
'13': 'WebSocket مقابل REST API — حقيقة سرعة البيانات',
'14': 'كيف تتابع أداء إشارات التداول مثل صناديق التحوط',
'15': 'منصة تداول مجهولة — حماية خصوصية تداولك',
'16': 'حاسبة حجم الصفقة — الدليل الكامل لإدارة مخاطر الفوركس 2026',
'17': 'تداول الذهب XAUUSD 2026 — الأدوات التقنية ومحركات الأسعار والآلات الحاسبة المجانية',
},
}
def clean_blog_content(html):
html = re.sub(r'href="https://telegra\.ph/([^"]+)"', r'href="/\\1"', html)
return html
def estimate_reading_time(text):
words = len(text.split())
return max(1, round(words / 200))
def _get_lang(lc):
for l in LANGUAGES:
if l["code"] == lc: return l
return LANGUAGES[0]
def make_lang_bar(current_lc, is_index=False, filename=""):
"""Generate language bar HTML with correct active highlighting."""
parts = []
for lang in LANGUAGES:
code = lang["code"]
d = lang["dir"] # "" for en, "zh"/"es"/"ar" for others
if is_index:
href = f"/{d}/" if d else "/"
else:
href = f"/{d}/{filename}" if d else f"/{filename}"
is_active = (code == current_lc)
color = "#ffcc00" if is_active else "#888"
weight = "font-weight:bold;" if is_active else ""
parts.append(f'<a href="{href}" style="color:{color};text-decoration:none;margin:0 8px;{weight}">{lang["label"]}</a>')
return '<div class="lang-bar" style="text-align:right;padding:6px 0 10px;font-size:13px;border-bottom:1px solid #222;margin-bottom:8px">' + "".join(parts) + '</div>'
# ===== HTML模板 =====
HTML_TPL = '<!DOCTYPE html>\n<html lang="{lc}" dir="{ldir}">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width,initial-scale=1.0">\n<title>{title} - {sname}</title>\n<meta name="description" content="{desc}">\n<meta name="keywords" content="{kw}">\n<meta name="robots" content="index,follow">\n<link rel="canonical" href="{su}/{fn}">\n{hreflang}\n<meta name="google-site-verification" content="{gv}"><meta name="msvalidate.01" content="D976F63A3FCA40BF064756213BC6315F">\n<meta property="og:type" content="article">\n<meta property="og:title" content="{title}">\n<meta property="og:description" content="{desc}">\n<meta property="og:url" content="{su}/{fn}">\n<meta property="og:site_name" content="{sname}">\n<meta property="og:image" content="{og_image}"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="{og_image}">\n<meta name="twitter:title" content="{title}">\n<meta name="twitter:site" content="@GFIL_Trading">\n<script type="application/ld+json">{{"@context":"https://schema.org","@type":"Article","headline":"{title}","description":"{desc}","datePublished":"{date}","dateModified":"{date}","author":{{"@type":"Organization","name":"GFIL Trading Team"}},"publisher":{{"@type":"Organization","name":"{sname}"}},"mainEntityOfPage":"{su}/{fn}","about":[{{"@type":"Thing","name":"Foreign Exchange Market","sameAs":"https://en.wikipedia.org/wiki/Foreign_exchange_market"}},{{"@type":"Thing","name":"Technical Analysis","sameAs":"https://en.wikipedia.org/wiki/Technical_analysis"}}]}}</script>\n<script async src="https://www.googletagmanager.com/gtag/js?id={ga}"></script>\n<script>window.dataLayer=window.dataLayer||[];function gtag(){{dataLayer.push(arguments);}}gtag("js",new Date());gtag("config","{ga}");</script>\n<style>*{{margin:0;padding:0;box-sizing:border-box}}body{{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#0a0a0f;color:#e0e0e0;max-width:800px;margin:0 auto;padding:20px;line-height:1.8}}h1{{color:#ffcc00;font-size:26px}}h2{{color:#ffcc00;font-size:20px;margin:25px 0 10px}}p,li{{color:#aaa;margin:10px 0}}a{{color:#ffcc00}}blockquote{{border-left:3px solid #ffcc00;padding:10px 20px;background:#14141a;margin:15px 0}}.header{{border-bottom:1px solid #333;padding:15px 0;margin-bottom:25px}}.header a,.header span{{color:#ffcc00;font-size:14px;text-decoration:none}}.tags{{margin:25px 0}}.tag{{display:inline-block;background:#222;color:#999;padding:4px 10px;border-radius:4px;font-size:12px;margin-right:6px}}.share{{margin:25px 0;padding:15px 0;border-top:1px solid #333;text-align:center}}.share a{{display:inline-block;padding:8px 16px;border-radius:6px;text-decoration:none;font-size:13px;font-weight:bold;margin:0 5px;color:#fff}}.share .tw{{background:#1da1f2}}.share .tg{{background:#08c}}.newsletter{{background:#14141a;border:1px solid #333;border-radius:8px;padding:25px;margin:25px 0;text-align:center}}.newsletter h3{{color:#ffcc00;margin-bottom:10px}}.newsletter input{{padding:10px 14px;border-radius:6px;border:1px solid #333;background:#0a0a0f;color:#e0e0e0;font-size:14px;width:250px;outline:none}}.newsletter input:focus{{border-color:#ffcc00}}.newsletter button{{padding:10px 20px;border-radius:6px;border:none;background:#ffcc00;color:#0a0a0f;font-weight:bold;font-size:14px;cursor:pointer}}.cta{{background:linear-gradient(135deg,#14141a,#1a1a25);border:1px solid #333;border-radius:8px;padding:25px;margin:25px 0;text-align:center}}.cta h3{{color:#ffcc00}}.cta a{{display:inline-block;padding:12px 24px;background:#ffcc00;color:#0a0a0f;text-decoration:none;border-radius:6px;font-weight:bold;margin:5px}}.footer{{margin-top:30px;padding-top:15px;border-top:1px solid #333;text-align:center;color:#555;font-size:12px}}.footer a{{color:#ffcc00}}@media(max-width:600px){{h1{{font-size:20px}}}}</style>\n</head>\n<body>\n{lang_bar}\n<div class="header"><a href="/{nav_home}">{nav_back}</a> | <a href="/tools/" style="color:#ffcc00">{tools_nav}</a></div>\n<article>{content}</article>\n<div class="tags">{tags_html}</div>\n<div class="share"><span>{share_label}</span><a class="tw" href="https://twitter.com/intent/tweet?text={share_text}&url={su}/{fn}" target="_blank">Twitter</a><a class="tg" href="https://t.me/share/url?url={su}/{fn}" target="_blank">Telegram</a></div>\n<div class="newsletter"><h3>{news_title}</h3><p style="color:#888;margin-bottom:15px">{news_desc}</p><form onsubmit="subscribeNewsletter(event)"><input type="email" id="nl-email" placeholder="{news_ph}" required><button type="submit" id="nl-btn">{news_btn}</button></form><p id="nl-msg" style="margin-top:10px;font-size:13px;display:none"></p></div>\n<div class="cta"><h3>{cta_title}</h3><p style="color:#aaa;margin:10px 0">{cta_desc}</p><a href="https://gfil-intel.xyz/">{cta_btn} →</a><a href="https://t.me/GFIL_Trading" style="background:#222;color:#ffcc00;border:1px solid #444">Telegram</a></div>\n<div class="author-box" style="background:#14141a;border:1px solid #333;border-radius:8px;padding:15px;margin:20px 0;display:flex;align-items:center;gap:12px"><div style="font-size:32px">👤</div><div><strong style="color:#ffcc00">LiuDecai</strong><span style="color:#888;font-size:12px;margin-left:8px">Founder, GFIL</span><p style="color:#aaa;font-size:12px;margin-top:4px">10+ years in forex, gold, and quantitative trading. Built GFIL Terminal to give retail traders the same tools institutions use. Focused on WebSocket data, order flow analysis, and AI-driven market intelligence.</p></div></div>\n<div class="footer"><p>&copy; 2026 {sname} | <a href="https://gfil-intel.xyz">GFIL Terminal</a> | <a href="https://t.me/GFIL_Trading">Telegram</a> | <a href="/tools/">Free Tools</a></p><p style="font-size:12px;color:#555">{contact}</p></div>\n<script>function subscribeNewsletter(e){{e.preventDefault();var m=document.getElementById("nl-email").value;var b=document.getElementById("nl-btn");var g=document.getElementById("nl-msg");b.disabled=true;b.textContent="Sending...";fetch("https://connect.mailerlite.com/api/subscribers",{{method:"POST",headers:{{"Authorization":"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI0IiwianRpIjoiYzExM2ZhNTM5YjE4OTFkZGJmNmIwN2MyYTc0Y2ExOGRjYWRhOTlhZDljNzhjOTExMjNmOWJhMTBhZDM4MzliOWY5ZWJjMTk0ZjdmMTZmZWIiLCJpYXQiOjE3ODA3Njg1NzcuMzY2OCwibmJmIjoxNzgwNzY4NTc3LjM2NjgwMiwiZXhwIjo0OTM2NDQyMTc3LjM2MTQ5Nywic3ViIjoiMjM2OTM1MSIsInNjb3BlcyI6W119.W9l_GhyM9_I7D7VDPYF18q34ukqzCZDlIJh725r2JLYsmD9ADKciydp69GzvoD0Lyr5eXeQM4fiGrIncWRk0CPnlKWso2IU5Y3FxSrUUV0D2PtljWBrnY0Q5rqw38b-tOrV_-NANKoJkPxWDSFcfriO_vrO-zNKD0_hW_r7hjnB2IWK_BuSjMoML1EcYLpmO5SuTjrqk4nUpYyRnGLYTFxyUETszIx0u98oKOBn5yfdoqlgUHeTyaraKCZRgBb0--1XlMFj13bbW8igCYDX-Nps-KjgsUPAjlZpub3_nfkbhPIW-M_tt694yyG94NcYAxQ0kf8z8XYGGHdrL4A28Ux_CXutG1l4PMveulVgjE96_o2e8nPIpuzOzeS4hFACR87QxUr-uVy_Y9atEy9oegFST7162JJUAlnpWowmn89LdslhF_KCiY-_IHOjLCuiJJ1LLj4tD2SSwIFBrBL4aze8DycbGa5EkF5-3EK1tHpeOiT3fufdjgj3DKw0FYM9B_UR_1CpRIyrHxeUrcIXuPM2K-5gAyjVvc5Ag2LZFH0nebwXhkgdgt8I5XuYTk88I3HC9eFNGZDwf35mmQJrcW2MZVGd6HntP1E-pzw_e0wis-Lu1FwHu_NGY9dWm2h72DlaLH104Mvv6ahdTXNHAhckcIgb1ZQRWrJYDHcJ29jE","Content-Type":"application/json"}},body:JSON.stringify({{email:m,groups:["189341033455158511"],fields:{{name:m.split("@")[0]}}}})}}).then(r=>r.json()).then(d=>{{if(d.data&&d.data.id){{g.innerHTML="✅ Welcome! <a href=\\"https://blog.quant-view.xyz/free-ebook.html\\" style=\\"color:#ffcc00\\">Download Free Ebook</a> | <a href=\\"https://gfil-intel.xyz/\\" style=\\"color:#ffcc00\\">GFIL Terminal</a>";g.style.color="#00ff88";g.style.padding="10px";g.style.background="#14141a";g.style.borderRadius="6px";document.getElementById("nl-email").value=""}}else{{g.innerHTML="Please try again";g.style.color="#ff4444"}}g.style.display="block";b.disabled=false;b.textContent="{news_btn}"}}).catch(er=>{{g.innerHTML="Network error, try again";g.style.color="#ff4444";g.style.display="block";b.disabled=false;b.textContent="{news_btn}"}})}}</script>\n</body>\n</html>'
INDEX_TPL = '<!DOCTYPE html>\n<html lang="{lc}" dir="{ldir}">\n<head>\n<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">\n<title>{sname} - Trading Insights</title>\n<meta name="description" content="Professional forex gold crypto trading insights. Institutional-grade market analysis and free trading tools.">\n<meta name="robots" content="index,follow">\n<link rel="canonical" href="{su}/{ldir_url}">\n{hreflang}\n<meta property="og:type" content="website"><meta property="og:title" content="{sname}"><meta property="og:description" content="Institutional-grade trading insights and free tools."><meta property="og:image" content="{su}/og/index.svg"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="{su}/og/index.svg">\n<script async src="https://www.googletagmanager.com/gtag/js?id={ga}"></script>\n<script>window.dataLayer=window.dataLayer||[];function gtag(){{dataLayer.push(arguments);}}gtag("js",new Date());gtag("config","{ga}");</script>\n<style>*{{margin:0;padding:0;box-sizing:border-box}}body{{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#0a0a0f;color:#e0e0e0;max-width:800px;margin:0 auto;padding:20px}}h1{{color:#ffcc00;font-size:28px;border-bottom:1px solid #333;padding-bottom:10px}}.sub{{color:#888;margin:10px 0 25px}}.article{{background:#14141a;padding:18px;margin:12px 0;border-radius:8px;border-left:3px solid #ffcc00;transition:background 0.3s}}.article:hover{{background:#1a1a22}}.article h2{{margin:0 0 8px;font-size:17px}}.article h2 a{{color:#ffcc00;text-decoration:none}}.article p{{color:#aaa;font-size:13px;margin:5px 0}}.meta{{color:#666;font-size:11px;margin-top:8px}}.tag{{display:inline-block;background:#222;color:#999;padding:2px 8px;border-radius:3px;font-size:10px;margin-right:4px}}.tools-banner{{background:linear-gradient(135deg,#14141a,#1a1a25);border:1px solid #ffcc00;border-radius:8px;padding:20px;margin:25px 0;text-align:center}}.tools-banner h3{{color:#ffcc00;font-size:18px}}.tools-banner a{{display:inline-block;padding:12px 24px;background:#ffcc00;color:#0a0a0f;text-decoration:none;border-radius:6px;font-weight:bold;margin:8px 5px}}.cta{{background:linear-gradient(135deg,#14141a,#1a1a25);border:1px solid #333;border-radius:8px;padding:25px;margin:25px 0;text-align:center}}.cta h3{{color:#ffcc00}}.cta a{{display:inline-block;padding:12px 24px;background:#ffcc00;color:#0a0a0f;text-decoration:none;border-radius:6px;font-weight:bold;margin:5px}}.footer{{margin-top:30px;padding-top:15px;border-top:1px solid #333;text-align:center;color:#555;font-size:12px}}.footer a{{color:#ffcc00}}@media(max-width:600px){{h1{{font-size:22px}}}}</style>\n</head>\n<body>\n{lang_bar}\n<div class="header" style="border-bottom:1px solid #333;padding:15px 0;margin-bottom:25px"><a href="/" style="color:#ffcc00;text-decoration:none">{sname}</a> | <a href="/tools/" style="color:#ffcc00;font-size:13px">{tools_nav}</a></div>\n<h1>{sname}</h1><p class="sub">{nav_sub}</p>\n<div class="tools-banner"><h3>{tools_title}</h3><p style="color:#aaa;margin:8px 0 15px">{tools_desc}</p><a href="/tools/">{tools_try} →</a><a href="/tools/position-size-calculator.html" style="background:#222;color:#ffcc00;border:1px solid #444">Position Size</a><a href="/tools/pip-calculator.html" style="background:#222;color:#ffcc00;border:1px solid #444">Pip Value</a></div>\n{articles}\n<div class="cta"><h3>{cta_title}</h3><p style="color:#aaa;margin:10px 0">{cta_desc}</p><a href="https://gfil-intel.xyz/">{cta_btn} →</a><a href="https://t.me/GFIL_Trading" style="background:#222;color:#ffcc00;border:1px solid #444">Telegram</a></div>\n<div class="author-box" style="background:#14141a;border:1px solid #333;border-radius:8px;padding:15px;margin:20px 0;display:flex;align-items:center;gap:12px"><div style="font-size:32px">👤</div><div><strong style="color:#ffcc00">LiuDecai</strong><span style="color:#888;font-size:12px;margin-left:8px">Founder, GFIL</span><p style="color:#aaa;font-size:12px;margin-top:4px">10+ years in forex, gold, and quantitative trading. Built GFIL Terminal to give retail traders the same tools institutions use. Focused on WebSocket data, order flow analysis, and AI-driven market intelligence.</p></div></div>\n<div class="footer"><p>&copy; 2026 {sname} | <a href="https://gfil-intel.xyz">GFIL Terminal</a> | <a href="https://t.me/GFIL_Trading">Telegram</a> | <a href="/tools/">Free Tools</a></p></div>\n</body>\n</html>'
# ===== 生成函数 =====
def generate_article(art, lc="en"):
"""生成单篇文章HTML"""
li = _get_lang(lc)
ld = li["dir"]; ldp = ld+"/" if ld else ""
cp = os.path.join(CONTENT_DIR, ld, f'article_{art["num"]}.html')
if not os.path.exists(cp): cp = os.path.join(CONTENT_DIR, f'article_{art["num"]}.html')
if not os.path.exists(cp): return None
with open(cp, "r", encoding="utf-8") as f: raw = f.read()
content = clean_blog_content(raw)
title = ART_TITLES.get(lc,{}).get(art["num"], art["title"])
# Inject H1 heading at top of article content
content = f'<h1>{title}</h1>\n{content}'
desc = art["desc"]
kw = ", ".join(art["tags"])
tags_html = " ".join(f'<span class="tag">{t}</span>' for t in art["tags"])
rt = estimate_reading_time(raw)
fn = f'{ldp}{art["filename"]}'
hl = ""
for l in LANGUAGES:
d = l["dir"]+"/" if l["dir"] else ""
hl += f'<link rel="alternate" hreflang="{l["code"]}" href="{SITE_URL}/{d}{art["filename"]}">\n'
hl += f'<link rel="alternate" hreflang="x-default" href="{SITE_URL}/{art["filename"]}">'
html = HTML_TPL.format(
lc=li["locale"][:2], ldir=li["dir_attr"], title=title, desc=desc, kw=kw, sname=LANG("site_name", lc),
su=SITE_URL, fn=fn, hreflang=hl, gv=GOOGLE_VERIFY, ga=GA_ID, date=art["date"],
og_image=f"{SITE_URL}/og/{art['filename'].replace('.html','.svg')}",
content=content, tags_html=tags_html, nav_back=LANG("nav_back", lc), tools_nav=LANG("tools_nav", lc),
share_label=LANG("share", lc), share_text=quote(title), news_title=LANG("news_title", lc),
news_desc=LANG("news_desc", lc), news_btn=LANG("news_btn", lc), news_ph=LANG("news_ph", lc),
cta_title=LANG("cta_title", lc), cta_desc=LANG("cta_desc", lc), cta_btn=LANG("cta_btn", lc), cta_tg=LANG("cta_tg", lc), footer_tools=LANG("footer_tools", lc), footer_terminal=LANG("footer_terminal", lc), contact=LANG("contact", lc),
lang_bar=make_lang_bar(lc, is_index=False, filename=art["filename"]),
nav_home=f"{ld}/" if ld else ""
)
op = os.path.join(OUTPUT_DIR, fn)
os.makedirs(os.path.dirname(op) if ld else OUTPUT_DIR, exist_ok=True)
with open(op, "w", encoding="utf-8") as f: f.write(html)
print(f' ✓ [{lc}] {fn}')
return op
def generate_index(lc="en"):
"""生成各语言首页"""
li = _get_lang(lc); ld = li["dir"]; ldp = ld+"/" if ld else ""
sname = LANG("site_name", lc)
arts = ""
for a in ARTICLES:
t = ART_TITLES.get(lc,{}).get(a["num"], a["title"])
arts += f'<div class="article"><h2><a href="/{ldp}{a["filename"]}">{t}</a></h2><p>{a["desc"]}</p><div class="meta">{" ".join(f"<span class=tag>{x}</span>" for x in a["tags"])} | {a["date"]}</div></div>\n'
hl = ""
for l in LANGUAGES:
d = "/"+l["dir"]+"/" if l["dir"] else "/"
hl += f'<link rel="alternate" hreflang="{l["code"]}" href="{SITE_URL}{d}">\n'
hl += f'<link rel="alternate" hreflang="x-default" href="{SITE_URL}/">'
ip = os.path.join(OUTPUT_DIR, ld, "index.html") if ld else os.path.join(OUTPUT_DIR, "index.html")
os.makedirs(os.path.dirname(ip) if ld else OUTPUT_DIR, exist_ok=True)
with open(ip, "w", encoding="utf-8") as f:
f.write(INDEX_TPL.format(lc=li["locale"][:2], ldir=li["dir_attr"], sname=sname, su=SITE_URL, ldir_url=ldp, hreflang=hl, ga=GA_ID, nav_sub=LANG("nav_sub",lc), tools_title=LANG("tools_title",lc), tools_desc=LANG("tools_desc",lc), tools_try=LANG("tools_try",lc), tools_nav=LANG("tools_nav",lc), cta_title=LANG("cta_title",lc), cta_desc=LANG("cta_desc",lc), cta_btn=LANG("cta_btn",lc), cta_tg=LANG("cta_tg",lc), footer_tools=LANG("footer_tools",lc), footer_terminal=LANG("footer_terminal",lc), articles=arts, lang_bar=make_lang_bar(lc, is_index=True)))
print(f' ✓ [{lc}] index.html')
def generate_sitemap():
"""Generate sitemap_index.xml + sub-sitemaps by content type for better Google crawl"""
today = datetime.now().strftime('%Y-%m-%d')
url = lambda p: f' <url><loc>{SITE_URL}/{p}</loc><lastmod>{today}</lastmod></url>\n'
tools_dir = os.path.join(BASE_DIR, 'tools')
total = 0
# --- sitemap-tools.xml: all calculator/tool pages ---
sm_tools = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
for f in sorted(os.listdir(tools_dir)):
if f.endswith('.html') and not f.startswith('_'):
sm_tools += url(f'tools/{f}')
for sub in ['zh', 'es', 'ar']:
sm_tools += url(f'tools/{sub}/')
sd = os.path.join(tools_dir, sub)
if os.path.isdir(sd):
for f in sorted(os.listdir(sd)):
if f.endswith('.html'):
sm_tools += url(f'tools/{sub}/{f}')
# MD endpoints
md_dir = os.path.join(tools_dir, 'md')
if os.path.isdir(md_dir):
for f in sorted(os.listdir(md_dir)):
if f.endswith('.md'):
sm_tools += url(f'tools/md/{f}')
sm_tools += '</urlset>'
with open(os.path.join(OUTPUT_DIR,"sitemap-tools.xml"),"w",encoding="utf-8") as f:
f.write(sm_tools)
tc = sm_tools.count('<url>')
total += tc
print(f' sitemap-tools.xml ({tc} URLs)')
# --- sitemap-posts.xml: blog articles (4 languages) ---
sm_posts = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
sm_posts += url('')
for a in ARTICLES:
sm_posts += url(a['filename'])
for d in ['zh', 'es', 'ar']:
sm_posts += url(f'{d}/{a["filename"]}')
sm_posts += '</urlset>'
with open(os.path.join(OUTPUT_DIR,"sitemap-posts.xml"),"w",encoding="utf-8") as f:
f.write(sm_posts)
pc = sm_posts.count('<url>')
total += pc
print(f' sitemap-posts.xml ({pc} URLs)')
# --- sitemap-geo.xml: research/docs/benchmark/entity/comparison/info pages ---
geo_pages = [
'tools/research.html', 'tools/docs.html', 'tools/roadmap.html',
'tools/changelog.html', 'tools/benchmark.html', 'tools/datasets.html',
'tools/status.html', 'tools/entity.html', 'tools/media.html',
'tools/ai-prompts.html', 'tools/gfil-faq.html', 'tools/about-gfil.html',
'tools/about-liudecai.html', 'tools/market-snapshot.html',
'tools/gfil-vs-mt5.html', 'tools/gfil-vs-ctrader.html',
'tools/gfil-vs-ninjatrader.html', 'tools/metatrader-vs-gfil.html',
'tools/tradingview-alternative.html', 'tools/tradingview-free-alternative.html',
'tools/tradingview-alert-limit-alternative.html',
'tools/free-bloomberg-terminal-alternative.html',
'tools/state-of-retail-forex-2026.html',
'tools/index.html', 'tools/links.html', 'tools/link-to-us.html',
'tools/disclaimer.html', 'tools/privacy.html', 'tools/terms.html',
'tools/chrome-extension.html', 'tools/widgets.html',
'tools/widget-position-size.html', 'tools/widget-performance.html',
'tools/trading-journal-template.html', 'tools/trading-plan-template.html',
]
sm_geo = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
for p in geo_pages:
fpath = os.path.join(tools_dir, p.replace('tools/', ''))
if os.path.exists(fpath):
sm_geo += url(p)
sm_geo += '</urlset>'
with open(os.path.join(OUTPUT_DIR,"sitemap-geo.xml"),"w",encoding="utf-8") as f:
f.write(sm_geo)
gc = sm_geo.count('<url>')
total += gc
print(f' sitemap-geo.xml ({gc} URLs)')
# --- sitemap_index.xml: master index ---
idx = '<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
for name, count in [('tools', tc), ('posts', pc), ('geo', gc)]:
idx += f' <sitemap><loc>{SITE_URL}/sitemap-{name}.xml</loc><lastmod>{today}</lastmod></sitemap>\n'
idx += '</sitemapindex>'
with open(os.path.join(OUTPUT_DIR,"sitemap.xml"),"w",encoding="utf-8") as f:
f.write(idx)
print(f' sitemap.xml (index → {total} URLs across 3 sub-sitemaps)')
def generate_robots():
robots = f'''User-agent: *
Allow: /
# === AI Search Crawlers (GEO — Generative Engine Optimization) ===
User-agent: OAI-SearchBot
Allow: /
User-agent: ChatGPT-User
Allow: /
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: anthropic-ai
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: GoogleOther
Allow: /
# === China AI Search Crawlers ===
User-agent: Bytespider
Allow: /
User-agent: DeepSeekBot
Allow: /
User-agent: KimiBot
Allow: /
User-agent: Baiduspider
Allow: /
# === Russia ===
User-agent: YandexBot
Allow: /
Sitemap: {SITE_URL}/sitemap.xml
'''
with open(os.path.join(OUTPUT_DIR,"robots.txt"),"w",encoding="utf-8") as f:
f.write(robots)
def generate_llms():
"""Generate llms.txt and llms-full.txt for AI search discoverability"""
# Build tool list from actual files
tools_dir = os.path.join(BASE_DIR, 'tools')
tool_list = []
for f in sorted(os.listdir(tools_dir)):
if f.endswith('.html') and not f.startswith('_'):
name = f.replace('.html','').replace('-',' ').title()
tool_list.append(f'- {name}: {SITE_URL}/tools/{f}')
llms = f'''# GFIL Trading Insights
> Professional forex, gold, crypto, and indices trading tools and education. 22 free trading calculators calculators covering all asset classes, real-time market data, AI analysis, in 4 languages.
## About
GFIL Trading Insights ({SITE_URL}) is the educational companion to GFIL Terminal (https://gfil-intel.xyz) — a browser-based trading platform with WebSocket real-time data (<50ms), 30+ instruments, and multi-model AI market analysis. Founded by LiuDecai with 10+ years in forex, gold, and quantitative trading.
## Core Calculators (Open Source)
Python + JavaScript library: https://pypi.org/project/gfil-calculators/ | Mirror: https://gitlab.com/liudecai110/gfil-trading-calculators
- Position Size Calculator: lot size from account balance, risk%, stop loss — covers forex, gold (XAUUSD), crypto (BTC, ETH, SOL), indices (SPX500, NAS100, DAX)
- Pip Value Calculator: pip value for 30+ instruments with proper contract sizes
- ATR Calculator: Average True Range with Wilder's smoothing, ATR-based stop loss and take profit
- Fibonacci Calculator: retracement and extension levels with all standard ratios
- Kelly Criterion Calculator: optimal position sizing from win rate and payoff ratio
- Drawdown Calculator: max drawdown, recovery needed, consecutive loss analysis
- Margin Calculator: required margin for leveraged positions
- Risk/Reward Calculator: R:R ratio and break-even win rate
- Compound Interest Calculator: growth projections with monthly contributions
## Key Educational Articles
- Position Size Complete Guide: {SITE_URL}/position-size-calculator-guide.html
- Gold XAUUSD Trading 2026: {SITE_URL}/gold-trading-2026-guide.html
- Why 87% of Retail Traders Lose Money: {SITE_URL}/why-retail-traders-lose-money.html
- Order Flow Trading Guide: {SITE_URL}/order-flow-trading.html
- WebSocket vs REST API for Trading: {SITE_URL}/websocket-vs-rest-api.html
- Bloomberg Terminal Alternatives: {SITE_URL}/bloomberg-alternative.html
- TradingView vs GFIL: {SITE_URL}/tradingview-vs-gfil-boss.html
## All Tools Index
{SITE_URL}/tools/
## AI-Readable Endpoints (Markdown)
- MD Index: {SITE_URL}/tools/md/
## Formulas (for AI citation)
- Position Size = (Account Balance x Risk%) / (Stop Loss Pips x Pip Value per Lot)
- Pip Value = Pip Size x Contract Size x Exchange Rate
- True Range = max(High-Low, |High-PrevClose|, |Low-PrevClose|); ATR = SMA(TR, 14)
- Kelly % = Win Rate - (Loss Rate / Win-Loss Ratio)
- Drawdown % = (Peak - Current) / Peak x 100; Recovery % = (1/(1-DD%)) - 1 x 100
- Fibonacci Retracement = High - (High-Low) x Ratio (23.6%, 38.2%, 50%, 61.8%, 78.6%)
- Required Margin = (Lot Size x Contract Size x Entry Price) / Leverage
## Structured Data
- Schema: SoftwareApplication, FAQPage, Article, Organization, BreadcrumbList, Speakable, MathSolver, FinancialProduct
- Sitemap: {SITE_URL}/sitemap.xml (339 URLs across 3 sub-sitemaps: tools, posts, geo)
- RSS: {SITE_URL}/rss.xml
## API Reference (REST + WebSocket)
Base URL: https://gfil-intel.xyz | Auth: X-API-Key header | Format: JSON
### REST Endpoints
- GET /api/symbols — All instruments with metadata (symbol, type, pip_size, contract_size)
- GET /api/market/:symbol — OHLCV + real-time quote
- GET /api/market/:symbol/quote — Lightweight bid/ask only
- GET /api/signals — Public signal feed
- GET /api/news — Curated financial news with AI sentiment scores
- GET /api/session — Active session stats
### WebSocket Channels
- WSS /ws/market — Real-time price stream (<50ms)
- WSS /ws/orderflow — Order flow: cumulative delta, bid/ask volume imbalance
- WSS /ws/signals — Live signal publishing feed
Full docs: {SITE_URL}/tools/api-reference.html
## Languages
English (/) · 中文 (/zh/) · Espanol (/es/) · العربية (/ar/)
## Product
- GFIL Terminal: https://gfil-intel.xyz
- Telegram: https://t.me/GFIL_Trading
- Discord: https://discord.gg/GMmMCD4MCr
'''
with open(os.path.join(OUTPUT_DIR,"llms.txt"),"w",encoding="utf-8") as f:
f.write(llms)
# Also copy to tools/ for the tools subdirectory
tools_llms = os.path.join(OUTPUT_DIR,"tools","llms.txt")
os.makedirs(os.path.dirname(tools_llms), exist_ok=True) if not os.path.exists(os.path.dirname(tools_llms)) else None
with open(tools_llms,"w",encoding="utf-8") as f:
f.write(llms.replace(SITE_URL+'/', SITE_URL+'/tools/').replace('blog.quant-view.xyz/tools/', 'blog.quant-view.xyz/tools/'))
def generate_rss():
from datetime import datetime
items = ''
for a in ARTICLES:
art_date = datetime.strptime(a["date"], '%Y-%m-%d').strftime('%a, %d %b %Y 00:00:00 +0000')
items += f'<item><title>{a["title"]}</title><link>{SITE_URL}/{a["filename"]}</link><description>{a["desc"]}</description><pubDate>{art_date}</pubDate></item>\n'
with open(os.path.join(OUTPUT_DIR,"rss.xml"),"w",encoding="utf-8") as f:
f.write(f'<?xml version="1.0"?>\n<rss version="2.0"><channel><title>{SITE_NAME}</title><link>{SITE_URL}</link><description>Trading insights and free tools</description>{items}</channel></rss>')
# ===== 上传函数 =====
def _proxy_sock():
"""HTTP CONNECT代理连接"""
h,p = HTTP_PROXY.split(":") if ":" in HTTP_PROXY else (HTTP_PROXY,80)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(20)
s.connect((h,int(p)))
s.sendall(f'CONNECT {SSH_HOST}:{SSH_PORT} HTTP/1.1\r\nHost: {SSH_HOST}:{SSH_PORT}\r\n\r\n'.encode())
r = b''
while b'\r\n\r\n' not in r: r += s.recv(4096)
if b'200' not in r: raise Exception(f'Proxy: {r.decode()[:100]}')
return s
def upload_to_server():
"""上传所有文件到服务器"""
print('\n=== Uploading ===')
sock = HTTP_PROXY and _proxy_sock() or None
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
kw = dict(hostname=SSH_HOST,port=SSH_PORT,username=SSH_USER,password=SSH_PASS,timeout=20,banner_timeout=30,allow_agent=False,look_for_keys=False)
if sock: kw["sock"] = sock
ssh.connect(**kw)
sftp = ssh.open_sftp()
c = 0
for root,dirs,files in os.walk(OUTPUT_DIR):
for f in files:
if f.endswith(('.html','.xml','.txt','.svg')):
local = os.path.join(root,f)
rel = os.path.relpath(local,OUTPUT_DIR).replace("\\","/")
remote = REMOTE_DIR.rstrip("/") + "/" + rel
rdir = os.path.dirname(remote)
ssh.exec_command(f'mkdir -p {rdir}',timeout=5)
time.sleep(0.03)
sftp.put(local,remote)
c += 1
if c % 20 == 0: print(f' {c} files...')
# Tools pages (root level)
tools_dir = os.path.join(BASE_DIR,"tools")
for f in os.listdir(tools_dir):
if f.endswith('.html') or f.endswith('.zip'):
sftp.put(os.path.join(tools_dir,f), f'{REMOTE_DIR}tools/{f}')
c += 1
# Tools language subdirs (ar/es/zh) + md endpoints
for sub in ['ar','es','zh','md']:
sub_dir = os.path.join(tools_dir, sub)
remote_sub = f'{REMOTE_DIR}tools/{sub}'
ssh.exec_command(f'mkdir -p {remote_sub}', timeout=5)
if os.path.exists(sub_dir):
for f in os.listdir(sub_dir):
if f.endswith(('.html','.md','.txt','.csv','.json')):
sftp.put(os.path.join(sub_dir, f), f'{remote_sub}/{f}')
c += 1
sftp.close()
ssh.exec_command(f'chmod -R 644 {REMOTE_DIR}*.html {REMOTE_DIR}tools/*.html {REMOTE_DIR}tools/*/*.md 2>/dev/null; chmod 755 {REMOTE_DIR} {REMOTE_DIR}tools/ {REMOTE_DIR}tools/ar {REMOTE_DIR}tools/es {REMOTE_DIR}tools/zh {REMOTE_DIR}tools/md', timeout=10)
ssh.close()
print(f'{c} files uploaded')
return True
def generate_og_images():
"""Generate SVG-based OG images for each article and the index page."""
og_dir = os.path.join(OUTPUT_DIR, "og")
os.makedirs(og_dir, exist_ok=True)
c = 0
def _make_svg(title, subtitle="GFIL Trading Insights"):
# Truncate title to fit in SVG
display = title[:50] + ("..." if len(title) > 50 else "")
return f'''<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
<rect width="1200" height="630" fill="#0a0a0f"/>
<rect x="0" y="0" width="8" height="630" fill="#ffcc00"/>
<text x="60" y="200" font-family="Arial,Helvetica,sans-serif" font-size="48" font-weight="bold" fill="#ffcc00">{display}</text>
<text x="60" y="280" font-family="Arial,Helvetica,sans-serif" font-size="28" fill="#888888">{subtitle}</text>
<text x="60" y="560" font-family="Arial,Helvetica,sans-serif" font-size="22" fill="#555555">blog.quant-view.xyz</text>
<rect x="60" y="480" width="120" height="3" fill="#ffcc00"/>
</svg>'''
# Generate for each article (EN only, used by all languages)
for art in ARTICLES:
svg_path = os.path.join(og_dir, f'{art["filename"].replace(".html", ".svg")}')
with open(svg_path, "w", encoding="utf-8") as f:
f.write(_make_svg(art["title"], " | ".join(art["tags"][:3])))
c += 1
# Generate for index page
with open(os.path.join(og_dir, "index.svg"), "w", encoding="utf-8") as f:
f.write(_make_svg("GFIL Trading Insights", "Professional Forex, Gold, Crypto Trading Tools"))
c += 1
print(f' og images: {c} SVGs generated')
# ===== 主入口 =====
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("--serve", action="store_true")
p.add_argument("--no-upload", action="store_true")
a = p.parse_args()
os.makedirs(OUTPUT_DIR, exist_ok=True)
print("=== GFIL BLOG Build ===\n")
for l in LANGUAGES:
print(f'--- [{l["code"]}] ---')
for art in ARTICLES: generate_article(art, l["code"])
for l in LANGUAGES:
print(f' [{l["code"]}] index')
generate_index(l["code"])
print('\n--- SEO ---')
generate_sitemap()
generate_robots()
generate_llms()
generate_rss()
generate_og_images()
if not a.no_upload:
try: upload_to_server()
except Exception as e: print(f' Upload: {e}')
print('\n=== Done ===')