#!/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'{lang["label"]}') return '
' + "".join(parts) + '
' # ===== HTML模板 ===== HTML_TPL = '\n\n\n\n\n{title} - {sname}\n\n\n\n\n{hreflang}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n{lang_bar}\n
{nav_back} | {tools_nav}
\n
{content}
\n
{tags_html}
\n
{share_label}TwitterTelegram
\n

{news_title}

{news_desc}

\n

{cta_title}

{cta_desc}

{cta_btn} →Telegram
\n
👤
LiuDecaiFounder, GFIL

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.

\n\n\n\n' INDEX_TPL = '\n\n\n\n{sname} - Trading Insights\n\n\n\n{hreflang}\n\n\n\n\n\n\n{lang_bar}\n
{sname} | {tools_nav}
\n

{sname}

{nav_sub}

\n

{tools_title}

{tools_desc}

{tools_try} →Position SizePip Value
\n{articles}\n

{cta_title}

{cta_desc}

{cta_btn} →Telegram
\n
👤
LiuDecaiFounder, GFIL

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.

\n\n\n' # ===== 生成函数 ===== 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'

{title}

\n{content}' desc = art["desc"] kw = ", ".join(art["tags"]) tags_html = " ".join(f'{t}' 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'\n' hl += f'' 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'

{t}

{a["desc"]}

{" ".join(f"{x}" for x in a["tags"])} | {a["date"]}
\n' hl = "" for l in LANGUAGES: d = "/"+l["dir"]+"/" if l["dir"] else "/" hl += f'\n' hl += f'' 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' {SITE_URL}/{p}{today}\n' tools_dir = os.path.join(BASE_DIR, 'tools') total = 0 # --- sitemap-tools.xml: all calculator/tool pages --- sm_tools = '\n\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 += '' with open(os.path.join(OUTPUT_DIR,"sitemap-tools.xml"),"w",encoding="utf-8") as f: f.write(sm_tools) tc = sm_tools.count('') total += tc print(f' sitemap-tools.xml ({tc} URLs)') # --- sitemap-posts.xml: blog articles (4 languages) --- sm_posts = '\n\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 += '' with open(os.path.join(OUTPUT_DIR,"sitemap-posts.xml"),"w",encoding="utf-8") as f: f.write(sm_posts) pc = sm_posts.count('') 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 = '\n\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 += '' with open(os.path.join(OUTPUT_DIR,"sitemap-geo.xml"),"w",encoding="utf-8") as f: f.write(sm_geo) gc = sm_geo.count('') total += gc print(f' sitemap-geo.xml ({gc} URLs)') # --- sitemap_index.xml: master index --- idx = '\n\n' for name, count in [('tools', tc), ('posts', pc), ('geo', gc)]: idx += f' {SITE_URL}/sitemap-{name}.xml{today}\n' idx += '' 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'{a["title"]}{SITE_URL}/{a["filename"]}{a["desc"]}{art_date}\n' with open(os.path.join(OUTPUT_DIR,"rss.xml"),"w",encoding="utf-8") as f: f.write(f'\n{SITE_NAME}{SITE_URL}Trading insights and free tools{items}') # ===== 上传函数 ===== 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''' {display} {subtitle} blog.quant-view.xyz ''' # 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 ===')