Refocus on USD/JPY and add walk-forward backtester with regime filter
- Strip multi-pair plumbing; drop synthetic-data fallback - Add BacktestConfig + walk-forward replay through the live signal pipeline - Add metrics module (PF, Sharpe, Sortino, max DD, trade journal) - Add parallel grid optimizer with disk parquet cache - Add H4 ADX regime gate (--regime-filter, --regime-min-adx) — cuts chop losses ~84% in 2023 H1 while preserving trend edge Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,509 +1,457 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Forex Signal Intelligence Platform
|
||||
====================================
|
||||
Main entry point. Runs full analysis and outputs formatted signal reports.
|
||||
|
||||
Usage:
|
||||
python main.py # Analyze all major pairs
|
||||
python main.py --pairs EURUSD GBPUSD # Specific pairs
|
||||
python main.py --timeframe H4 # Different primary TF
|
||||
python main.py --json # JSON output
|
||||
python main.py --watch # Continuous monitoring mode
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import List
|
||||
|
||||
# Force UTF-8 output on Windows so box-drawing / Unicode chars render correctly
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
if hasattr(sys.stderr, "reconfigure"):
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
from config import (
|
||||
PlatformConfig, Timeframe, SignalStrength,
|
||||
MAJOR_PAIRS, CROSS_PAIRS,
|
||||
)
|
||||
from signal_aggregator import SignalAggregator, SignalReport
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# TERMINAL FORMATTING
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
class Colors:
|
||||
"""ANSI color codes for terminal output."""
|
||||
RESET = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
DIM = "\033[2m"
|
||||
UNDERLINE = "\033[4m"
|
||||
|
||||
RED = "\033[91m"
|
||||
GREEN = "\033[92m"
|
||||
YELLOW = "\033[93m"
|
||||
BLUE = "\033[94m"
|
||||
MAGENTA = "\033[95m"
|
||||
CYAN = "\033[96m"
|
||||
WHITE = "\033[97m"
|
||||
GRAY = "\033[90m"
|
||||
|
||||
BG_RED = "\033[41m"
|
||||
BG_GREEN = "\033[42m"
|
||||
BG_YELLOW = "\033[43m"
|
||||
BG_BLUE = "\033[44m"
|
||||
BG_MAGENTA = "\033[45m"
|
||||
BG_CYAN = "\033[46m"
|
||||
|
||||
|
||||
def signal_color(signal: SignalStrength) -> str:
|
||||
"""Get color for a signal strength."""
|
||||
return {
|
||||
SignalStrength.STRONG_BUY: Colors.GREEN + Colors.BOLD,
|
||||
SignalStrength.BUY: Colors.GREEN,
|
||||
SignalStrength.NEUTRAL: Colors.GRAY,
|
||||
SignalStrength.SELL: Colors.RED,
|
||||
SignalStrength.STRONG_SELL: Colors.RED + Colors.BOLD,
|
||||
}.get(signal, Colors.RESET)
|
||||
|
||||
|
||||
def quality_color(quality: str) -> str:
|
||||
"""Get color for signal quality grade."""
|
||||
return {
|
||||
"A+": Colors.GREEN + Colors.BOLD,
|
||||
"A": Colors.GREEN,
|
||||
"B": Colors.CYAN,
|
||||
"C": Colors.YELLOW,
|
||||
"D": Colors.GRAY,
|
||||
"F": Colors.DIM,
|
||||
}.get(quality, Colors.RESET)
|
||||
|
||||
|
||||
def score_bar(score: float, width: int = 30) -> str:
|
||||
"""Create a visual score bar from -100 to +100."""
|
||||
normalized = (score + 100) / 200 # 0 to 1
|
||||
filled = int(normalized * width)
|
||||
center = width // 2
|
||||
|
||||
bar = ""
|
||||
for i in range(width):
|
||||
if i == center:
|
||||
bar += "│"
|
||||
elif (score >= 0 and center < i <= filled) or \
|
||||
(score < 0 and filled <= i < center):
|
||||
bar += "█"
|
||||
else:
|
||||
bar += "░"
|
||||
|
||||
if score >= 0:
|
||||
color = Colors.GREEN if score > 20 else Colors.GRAY
|
||||
else:
|
||||
color = Colors.RED if score < -20 else Colors.GRAY
|
||||
|
||||
return f"{color}{bar}{Colors.RESET}"
|
||||
|
||||
|
||||
def print_header():
|
||||
"""Print the platform header."""
|
||||
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
width = 80
|
||||
|
||||
print(f"\n{Colors.CYAN}{'═' * width}{Colors.RESET}")
|
||||
print(f"{Colors.BOLD}{Colors.CYAN} ╔═══════════════════════════════════════════════════════╗{Colors.RESET}")
|
||||
print(f"{Colors.BOLD}{Colors.CYAN} ║ FOREX SIGNAL INTELLIGENCE PLATFORM ║{Colors.RESET}")
|
||||
print(f"{Colors.BOLD}{Colors.CYAN} ║ Multi-Layer Signal Analysis Engine ║{Colors.RESET}")
|
||||
print(f"{Colors.BOLD}{Colors.CYAN} ╚═══════════════════════════════════════════════════════╝{Colors.RESET}")
|
||||
print(f"{Colors.GRAY} Scan Time: {now}{Colors.RESET}")
|
||||
print(f"{Colors.CYAN}{'═' * width}{Colors.RESET}\n")
|
||||
|
||||
|
||||
def print_summary_table(reports: List[SignalReport]):
|
||||
"""Print a summary table of all pairs."""
|
||||
print(f"{Colors.BOLD}{Colors.WHITE} ┌─────────────┬───────────┬──────────────────────────────────┬────────┬────────────┐{Colors.RESET}")
|
||||
print(f"{Colors.BOLD}{Colors.WHITE} │ Pair │ Signal │ Score │ Grade │ Confidence │{Colors.RESET}")
|
||||
print(f"{Colors.BOLD}{Colors.WHITE} ├─────────────┼───────────┼──────────────────────────────────┼────────┼────────────┤{Colors.RESET}")
|
||||
|
||||
for r in reports:
|
||||
sig = r.final_signal.name.replace("_", " ")
|
||||
sc = signal_color(r.final_signal)
|
||||
qc = quality_color(r.signal_quality)
|
||||
bar = score_bar(r.final_score, 30)
|
||||
|
||||
pair = f"{r.display_name:<11}"
|
||||
signal_str = f"{sc}{sig:<9}{Colors.RESET}"
|
||||
grade_str = f"{qc}{r.signal_quality:<6}{Colors.RESET}"
|
||||
conf_str = f"{r.final_confidence:5.1f}%"
|
||||
|
||||
# Add warning indicator
|
||||
warn = ""
|
||||
if r.event_risk == "danger":
|
||||
warn = f" {Colors.RED}⚠{Colors.RESET}"
|
||||
elif r.event_risk == "caution":
|
||||
warn = f" {Colors.YELLOW}△{Colors.RESET}"
|
||||
|
||||
print(f" │ {pair} │ {signal_str} │ {bar} │ {grade_str} │ {conf_str:>8}{warn} │")
|
||||
|
||||
print(f"{Colors.BOLD}{Colors.WHITE} └─────────────┴───────────┴──────────────────────────────────┴────────┴────────────┘{Colors.RESET}")
|
||||
|
||||
# Legend
|
||||
print(f"\n{Colors.GRAY} Legend: "
|
||||
f"{Colors.GREEN}██ Bullish{Colors.GRAY} | "
|
||||
f"{Colors.RED}██ Bearish{Colors.GRAY} | "
|
||||
f"│ Center (0) | "
|
||||
f"{Colors.RED}⚠ Event Risk{Colors.GRAY} | "
|
||||
f"{Colors.YELLOW}△ Caution{Colors.RESET}")
|
||||
|
||||
|
||||
def print_detailed_report(report: SignalReport):
|
||||
"""Print detailed analysis for a single pair."""
|
||||
sc = signal_color(report.final_signal)
|
||||
qc = quality_color(report.signal_quality)
|
||||
sig_name = report.final_signal.name.replace("_", " ")
|
||||
|
||||
print(f"\n{Colors.CYAN}{'─' * 80}{Colors.RESET}")
|
||||
print(f" {Colors.BOLD}{Colors.WHITE}{report.display_name}{Colors.RESET}"
|
||||
f" │ Price: {Colors.BOLD}{report.current_price:.5f}{Colors.RESET}"
|
||||
f" │ Regime: {Colors.CYAN}{report.market_regime}{Colors.RESET}"
|
||||
f" │ Vol: {report.volatility_regime}")
|
||||
|
||||
print(f"\n {Colors.BOLD}SIGNAL:{Colors.RESET} {sc}{sig_name}{Colors.RESET}"
|
||||
f" │ Score: {sc}{report.final_score:+.1f}{Colors.RESET}"
|
||||
f" │ Grade: {qc}{report.signal_quality}{Colors.RESET}"
|
||||
f" │ Confidence: {report.final_confidence:.0f}%")
|
||||
|
||||
print(f" {Colors.BOLD}Action:{Colors.RESET} {report.action_recommendation}")
|
||||
|
||||
# Multi-timeframe breakdown
|
||||
if report.mtf_signals:
|
||||
print(f"\n {Colors.BOLD}Multi-Timeframe:{Colors.RESET}", end="")
|
||||
mtf_agree = f" {Colors.GREEN}✓ ALIGNED{Colors.RESET}" if report.mtf_agreement else f" {Colors.YELLOW}✗ DIVERGENT{Colors.RESET}"
|
||||
print(mtf_agree)
|
||||
for tf, sig in report.mtf_signals.items():
|
||||
tf_sc = signal_color(sig)
|
||||
tf_score = report.mtf_scores.get(tf, 0)
|
||||
print(f" {tf:>5}: {tf_sc}{sig.name.replace('_', ' '):<12}{Colors.RESET} ({tf_score:+.1f})")
|
||||
|
||||
# Indicators detail
|
||||
if report.primary_analysis:
|
||||
print(f"\n {Colors.BOLD}Indicators ({report.primary_analysis.timeframe}):{Colors.RESET}")
|
||||
for name, ind in report.primary_analysis.indicators.items():
|
||||
if name.startswith("_") or ind.weight == 0:
|
||||
continue
|
||||
isc = signal_color(ind.signal)
|
||||
ind_name = f"{ind.name:<18}"
|
||||
print(f" {ind_name} {isc}{ind.signal.name.replace('_', ' '):<12}{Colors.RESET}"
|
||||
f" ({ind.confidence:.0f}%) │ {Colors.GRAY}{ind.description}{Colors.RESET}")
|
||||
|
||||
# Bullish reasons
|
||||
if report.bullish_reasons:
|
||||
print(f"\n {Colors.GREEN}▲ Bullish Factors:{Colors.RESET}")
|
||||
for reason in report.bullish_reasons[:5]:
|
||||
print(f" {Colors.GREEN}+{Colors.RESET} {reason}")
|
||||
|
||||
# Bearish reasons
|
||||
if report.bearish_reasons:
|
||||
print(f"\n {Colors.RED}▼ Bearish Factors:{Colors.RESET}")
|
||||
for reason in report.bearish_reasons[:5]:
|
||||
print(f" {Colors.RED}-{Colors.RESET} {reason}")
|
||||
|
||||
# Warnings
|
||||
if report.warnings:
|
||||
print(f"\n {Colors.YELLOW}⚠ Warnings:{Colors.RESET}")
|
||||
for warning in report.warnings:
|
||||
print(f" {Colors.YELLOW}!{Colors.RESET} {warning}")
|
||||
|
||||
# News context
|
||||
if report.news_description and report.news_description != "Neutral news sentiment":
|
||||
print(f"\n {Colors.MAGENTA}📰 News:{Colors.RESET} {report.news_description}")
|
||||
if report.event_description and report.event_risk != "clear":
|
||||
print(f" {Colors.YELLOW}📅 Event:{Colors.RESET} {report.event_description}")
|
||||
|
||||
# Trade setup
|
||||
if report.trade_setup:
|
||||
ts = report.trade_setup
|
||||
dir_color = Colors.GREEN if ts.direction == "BUY" else Colors.RED
|
||||
print(f"\n {Colors.BOLD}Trade Setup:{Colors.RESET}")
|
||||
print(f" Direction: {dir_color}{ts.direction}{Colors.RESET}")
|
||||
print(f" Entry: {ts.entry_price:.5f}")
|
||||
print(f" Stop Loss: {Colors.RED}{ts.stop_loss:.5f}{Colors.RESET} ({ts.risk_pips:.1f} pips)")
|
||||
print(f" Take Profit 1: {Colors.GREEN}{ts.take_profit_1:.5f}{Colors.RESET} ({ts.reward_pips:.1f} pips)")
|
||||
print(f" Take Profit 2: {Colors.GREEN}{ts.take_profit_2:.5f}{Colors.RESET}")
|
||||
print(f" Risk:Reward: 1:{ts.risk_reward:.1f}")
|
||||
|
||||
# Key levels
|
||||
if report.primary_analysis and report.primary_analysis.key_levels:
|
||||
levels = report.primary_analysis.key_levels
|
||||
print(f"\n {Colors.BOLD}Key Levels:{Colors.RESET}")
|
||||
level_names = ["r3", "r2", "r1", "pivot", "s1", "s2", "s3"]
|
||||
for lvl in level_names:
|
||||
if lvl in levels:
|
||||
val = levels[lvl]
|
||||
if val > report.current_price:
|
||||
color = Colors.RED # resistance
|
||||
else:
|
||||
color = Colors.GREEN # support
|
||||
label = lvl.upper()
|
||||
print(f" {label:>6}: {color}{val:.5f}{Colors.RESET}")
|
||||
|
||||
|
||||
def print_event_calendar(reports: List[SignalReport]):
|
||||
"""Print upcoming economic events affecting watched pairs."""
|
||||
events_shown = set()
|
||||
has_events = False
|
||||
|
||||
for report in reports:
|
||||
if report.event_risk != "clear" and report.event_description not in events_shown:
|
||||
if not has_events:
|
||||
print(f"\n{Colors.YELLOW}{Colors.BOLD} 📅 ECONOMIC CALENDAR ALERTS{Colors.RESET}")
|
||||
has_events = True
|
||||
events_shown.add(report.event_description)
|
||||
risk_color = Colors.RED if report.event_risk == "danger" else Colors.YELLOW
|
||||
print(f" {risk_color}● {report.event_description}{Colors.RESET} → affects {report.display_name}")
|
||||
|
||||
if not has_events:
|
||||
print(f"\n{Colors.GRAY} 📅 No high-impact events in the near term{Colors.RESET}")
|
||||
|
||||
|
||||
def report_to_dict(report: SignalReport) -> dict:
|
||||
"""Convert a SignalReport to a JSON-serializable dict."""
|
||||
d = {
|
||||
"symbol": report.symbol,
|
||||
"display_name": report.display_name,
|
||||
"timestamp": report.timestamp,
|
||||
"current_price": report.current_price,
|
||||
"signal": report.final_signal.name,
|
||||
"score": round(report.final_score, 2),
|
||||
"confidence": round(report.final_confidence, 2),
|
||||
"quality": report.signal_quality,
|
||||
"action": report.action_recommendation,
|
||||
"market_regime": report.market_regime,
|
||||
"volatility_regime": report.volatility_regime,
|
||||
"mtf_agreement": report.mtf_agreement,
|
||||
"mtf_signals": {k: v.name for k, v in report.mtf_signals.items()},
|
||||
"event_risk": report.event_risk,
|
||||
"event_description": report.event_description,
|
||||
"news_sentiment": round(report.news_sentiment, 3),
|
||||
"bullish_reasons": report.bullish_reasons,
|
||||
"bearish_reasons": report.bearish_reasons,
|
||||
"warnings": report.warnings,
|
||||
}
|
||||
if report.trade_setup:
|
||||
ts = report.trade_setup
|
||||
d["trade_setup"] = {
|
||||
"direction": ts.direction,
|
||||
"entry": ts.entry_price,
|
||||
"stop_loss": ts.stop_loss,
|
||||
"take_profit_1": ts.take_profit_1,
|
||||
"take_profit_2": ts.take_profit_2,
|
||||
"risk_reward": ts.risk_reward,
|
||||
"risk_pips": round(ts.risk_pips, 1),
|
||||
"reward_pips": round(ts.reward_pips, 1),
|
||||
}
|
||||
if report.primary_analysis:
|
||||
d["indicators"] = {}
|
||||
for name, ind in report.primary_analysis.indicators.items():
|
||||
if not name.startswith("_"):
|
||||
d["indicators"][name] = {
|
||||
"signal": ind.signal.name,
|
||||
"confidence": round(ind.confidence, 1),
|
||||
"description": ind.description,
|
||||
}
|
||||
d["key_levels"] = {
|
||||
k: round(v, 5) for k, v in report.primary_analysis.key_levels.items()
|
||||
}
|
||||
return d
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Forex Signal Intelligence Platform",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python main.py Analyze all major pairs (H1)
|
||||
python main.py --pairs EURUSD GBPUSD Analyze specific pairs
|
||||
python main.py --timeframe H4 Use 4-hour as primary TF
|
||||
python main.py --all Include cross pairs
|
||||
python main.py --json Output as JSON
|
||||
python main.py --detail Show detailed analysis per pair
|
||||
python main.py --watch Continuous monitoring (5min refresh)
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pairs", nargs="+", default=None,
|
||||
help="Specific pairs to analyze (e.g., EURUSD GBPUSD)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeframe", "-tf", default="H1",
|
||||
choices=["M5", "M15", "M30", "H1", "H4", "D1"],
|
||||
help="Primary analysis timeframe (default: H1)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--all", action="store_true",
|
||||
help="Include cross pairs in analysis",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json", action="store_true",
|
||||
help="Output results as JSON",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--detail", action="store_true",
|
||||
help="Show detailed analysis for each pair",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--top", type=int, default=None,
|
||||
help="Show only top N signals by strength",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--watch", action="store_true",
|
||||
help="Continuous monitoring mode (refreshes every 5 min)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min-grade", default="F",
|
||||
choices=["A+", "A", "B", "C", "D", "F"],
|
||||
help="Minimum signal quality to display",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose", "-v", action="store_true",
|
||||
help="Enable debug logging",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def symbol_from_name(name: str) -> str:
|
||||
"""Convert pair name like 'EURUSD' to Yahoo symbol 'EURUSD=X'."""
|
||||
name = name.upper().replace("/", "").replace("_", "")
|
||||
if not name.endswith("=X"):
|
||||
name += "=X"
|
||||
return name
|
||||
|
||||
|
||||
def run_analysis(args) -> List[SignalReport]:
|
||||
"""Run a single analysis cycle."""
|
||||
config = PlatformConfig()
|
||||
|
||||
# Configure timeframe
|
||||
tf_map = {
|
||||
"M5": Timeframe.M5, "M15": Timeframe.M15, "M30": Timeframe.M30,
|
||||
"H1": Timeframe.H1, "H4": Timeframe.H4, "D1": Timeframe.D1,
|
||||
}
|
||||
config.primary_timeframe = tf_map[args.timeframe]
|
||||
|
||||
# Configure confirmation timeframes based on primary
|
||||
tf_confirmations = {
|
||||
"M5": [Timeframe.M1, Timeframe.M15],
|
||||
"M15": [Timeframe.M5, Timeframe.H1],
|
||||
"M30": [Timeframe.M15, Timeframe.H1],
|
||||
"H1": [Timeframe.M15, Timeframe.H4],
|
||||
"H4": [Timeframe.H1, Timeframe.D1],
|
||||
"D1": [Timeframe.H4, Timeframe.W1],
|
||||
}
|
||||
config.confirmation_timeframes = tf_confirmations.get(
|
||||
args.timeframe, [Timeframe.M15, Timeframe.H4]
|
||||
)
|
||||
|
||||
# Configure pairs
|
||||
if args.pairs:
|
||||
config.watch_pairs = [symbol_from_name(p) for p in args.pairs]
|
||||
elif args.all:
|
||||
config.watch_pairs = MAJOR_PAIRS + CROSS_PAIRS
|
||||
else:
|
||||
config.watch_pairs = MAJOR_PAIRS
|
||||
|
||||
# Run analysis
|
||||
aggregator = SignalAggregator(config)
|
||||
reports = aggregator.analyze_all_pairs()
|
||||
|
||||
# Filter by grade
|
||||
grade_order = ["A+", "A", "B", "C", "D", "F"]
|
||||
min_idx = grade_order.index(args.min_grade) if args.min_grade in grade_order else len(grade_order) - 1
|
||||
reports = [r for r in reports if grade_order.index(r.signal_quality) <= min_idx]
|
||||
|
||||
# Top N
|
||||
if args.top:
|
||||
reports = reports[:args.top]
|
||||
|
||||
return reports
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
# Setup logging
|
||||
log_level = logging.DEBUG if args.verbose else logging.WARNING
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
)
|
||||
|
||||
if args.watch:
|
||||
# Continuous monitoring mode
|
||||
print(f"{Colors.BOLD}Starting continuous monitoring mode (Ctrl+C to stop)...{Colors.RESET}")
|
||||
while True:
|
||||
try:
|
||||
reports = run_analysis(args)
|
||||
if args.json:
|
||||
output = [report_to_dict(r) for r in reports]
|
||||
print(json.dumps(output, indent=2))
|
||||
else:
|
||||
print("\033[2J\033[H") # clear screen
|
||||
print_header()
|
||||
print_summary_table(reports)
|
||||
print_event_calendar(reports)
|
||||
if args.detail:
|
||||
for r in reports:
|
||||
print_detailed_report(r)
|
||||
print(f"\n{Colors.GRAY} Next refresh in 5 minutes...{Colors.RESET}")
|
||||
|
||||
time.sleep(300)
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{Colors.YELLOW}Monitoring stopped.{Colors.RESET}")
|
||||
break
|
||||
else:
|
||||
# Single run
|
||||
print(f"{Colors.GRAY} Fetching market data and running analysis...{Colors.RESET}\n")
|
||||
reports = run_analysis(args)
|
||||
|
||||
if args.json:
|
||||
output = [report_to_dict(r) for r in reports]
|
||||
print(json.dumps(output, indent=2))
|
||||
else:
|
||||
print_header()
|
||||
if reports:
|
||||
print_summary_table(reports)
|
||||
print_event_calendar(reports)
|
||||
|
||||
if args.detail:
|
||||
for r in reports:
|
||||
print_detailed_report(r)
|
||||
else:
|
||||
# Show detail for top signals only
|
||||
actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B")]
|
||||
if actionable:
|
||||
print(f"\n{Colors.BOLD}{Colors.WHITE} ═══ ACTIONABLE SIGNALS (Grade B or above) ═══{Colors.RESET}")
|
||||
for r in actionable:
|
||||
print_detailed_report(r)
|
||||
else:
|
||||
print(f"\n{Colors.GRAY} No high-grade signals detected. "
|
||||
f"Use --detail for full breakdown or --min-grade C to see more.{Colors.RESET}")
|
||||
else:
|
||||
print(f"{Colors.YELLOW} No data available. Check your internet connection.{Colors.RESET}")
|
||||
|
||||
print(f"\n{Colors.CYAN}{'═' * 80}{Colors.RESET}")
|
||||
print(f"{Colors.GRAY} Disclaimer: This tool is for educational/research purposes only.{Colors.RESET}")
|
||||
print(f"{Colors.GRAY} Past performance ≠ future results. Always use proper risk management.{Colors.RESET}")
|
||||
print(f"{Colors.CYAN}{'═' * 80}{Colors.RESET}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ForexBot2 — USD/JPY Signal Intelligence (single-pair)
|
||||
=======================================================
|
||||
Entry point for both live signal analysis and walk-forward backtesting.
|
||||
|
||||
Usage:
|
||||
python main.py # Live signal on H1
|
||||
python main.py --timeframe H4 --detail # Detailed H4 signal
|
||||
python main.py --json # JSON output
|
||||
python main.py --backtest # Walk-forward backtest
|
||||
python main.py --backtest --optimize # Parallel grid search
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, List
|
||||
|
||||
# Force UTF-8 output on Windows so box-drawing chars render
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
if hasattr(sys.stderr, "reconfigure"):
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
from config import PlatformConfig, Timeframe, SignalStrength, USDJPY, DISPLAY
|
||||
from signal_aggregator import SignalAggregator, SignalReport
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# TERMINAL FORMATTING
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
class Colors:
|
||||
RESET = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
DIM = "\033[2m"
|
||||
UNDERLINE = "\033[4m"
|
||||
RED = "\033[91m"
|
||||
GREEN = "\033[92m"
|
||||
YELLOW = "\033[93m"
|
||||
BLUE = "\033[94m"
|
||||
MAGENTA = "\033[95m"
|
||||
CYAN = "\033[96m"
|
||||
WHITE = "\033[97m"
|
||||
GRAY = "\033[90m"
|
||||
|
||||
|
||||
def signal_color(signal: SignalStrength) -> str:
|
||||
return {
|
||||
SignalStrength.STRONG_BUY: Colors.GREEN + Colors.BOLD,
|
||||
SignalStrength.BUY: Colors.GREEN,
|
||||
SignalStrength.NEUTRAL: Colors.GRAY,
|
||||
SignalStrength.SELL: Colors.RED,
|
||||
SignalStrength.STRONG_SELL: Colors.RED + Colors.BOLD,
|
||||
}.get(signal, Colors.RESET)
|
||||
|
||||
|
||||
def quality_color(q: str) -> str:
|
||||
return {
|
||||
"A+": Colors.GREEN + Colors.BOLD,
|
||||
"A": Colors.GREEN,
|
||||
"B": Colors.CYAN,
|
||||
"C": Colors.YELLOW,
|
||||
"D": Colors.GRAY,
|
||||
"F": Colors.DIM,
|
||||
}.get(q, Colors.RESET)
|
||||
|
||||
|
||||
def print_header(title: str = "USD/JPY SIGNAL INTELLIGENCE"):
|
||||
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
print(f"\n{Colors.CYAN}{'═' * 80}{Colors.RESET}")
|
||||
print(f"{Colors.BOLD}{Colors.CYAN} {title}{Colors.RESET}")
|
||||
print(f"{Colors.GRAY} Time: {now}{Colors.RESET}")
|
||||
print(f"{Colors.CYAN}{'═' * 80}{Colors.RESET}\n")
|
||||
|
||||
|
||||
# ── Live signal renderer ───────────────────────────────────────────
|
||||
|
||||
def print_signal_report(report: SignalReport, detail: bool = True):
|
||||
sc = signal_color(report.final_signal)
|
||||
qc = quality_color(report.signal_quality)
|
||||
sig_name = report.final_signal.name.replace("_", " ")
|
||||
|
||||
print(f" {Colors.BOLD}{Colors.WHITE}{report.display_name}{Colors.RESET}"
|
||||
f" │ Price: {Colors.BOLD}{report.current_price:.3f}{Colors.RESET}"
|
||||
f" │ Regime: {Colors.CYAN}{report.market_regime}{Colors.RESET}"
|
||||
f" │ Vol: {report.volatility_regime}")
|
||||
|
||||
print(f"\n {Colors.BOLD}SIGNAL:{Colors.RESET} {sc}{sig_name}{Colors.RESET}"
|
||||
f" │ Score: {sc}{report.final_score:+.1f}{Colors.RESET}"
|
||||
f" │ Grade: {qc}{report.signal_quality}{Colors.RESET}"
|
||||
f" │ Confidence: {report.final_confidence:.0f}%")
|
||||
|
||||
print(f" {Colors.BOLD}Action:{Colors.RESET} {report.action_recommendation}")
|
||||
|
||||
if report.mtf_signals:
|
||||
mtf_status = (f" {Colors.GREEN}✓ ALIGNED{Colors.RESET}"
|
||||
if report.mtf_agreement
|
||||
else f" {Colors.YELLOW}✗ DIVERGENT{Colors.RESET}")
|
||||
print(f"\n {Colors.BOLD}Multi-Timeframe:{Colors.RESET}{mtf_status}")
|
||||
for tf, sig in report.mtf_signals.items():
|
||||
tf_sc = signal_color(sig)
|
||||
tf_score = report.mtf_scores.get(tf, 0)
|
||||
print(f" {tf:>5}: {tf_sc}{sig.name.replace('_',' '):<12}{Colors.RESET}"
|
||||
f" ({tf_score:+.1f})")
|
||||
|
||||
if detail and report.primary_analysis:
|
||||
print(f"\n {Colors.BOLD}Indicators ({report.primary_analysis.timeframe}):{Colors.RESET}")
|
||||
for name, ind in report.primary_analysis.indicators.items():
|
||||
if name.startswith("_") or ind.weight == 0:
|
||||
continue
|
||||
isc = signal_color(ind.signal)
|
||||
print(f" {ind.name:<18} {isc}{ind.signal.name.replace('_',' '):<12}{Colors.RESET}"
|
||||
f" ({ind.confidence:.0f}%) │ {Colors.GRAY}{ind.description}{Colors.RESET}")
|
||||
|
||||
if report.bullish_reasons:
|
||||
print(f"\n {Colors.GREEN}▲ Bullish Factors:{Colors.RESET}")
|
||||
for r in report.bullish_reasons[:5]:
|
||||
print(f" {Colors.GREEN}+{Colors.RESET} {r}")
|
||||
if report.bearish_reasons:
|
||||
print(f"\n {Colors.RED}▼ Bearish Factors:{Colors.RESET}")
|
||||
for r in report.bearish_reasons[:5]:
|
||||
print(f" {Colors.RED}-{Colors.RESET} {r}")
|
||||
|
||||
if report.warnings:
|
||||
print(f"\n {Colors.YELLOW}⚠ Warnings:{Colors.RESET}")
|
||||
for w in report.warnings:
|
||||
print(f" {Colors.YELLOW}!{Colors.RESET} {w}")
|
||||
|
||||
if report.news_description and report.news_description != "Neutral news sentiment":
|
||||
print(f"\n {Colors.MAGENTA}📰 News:{Colors.RESET} {report.news_description}")
|
||||
if report.event_description and report.event_risk != "clear":
|
||||
print(f" {Colors.YELLOW}📅 Event:{Colors.RESET} {report.event_description}")
|
||||
|
||||
if report.trade_setup:
|
||||
ts = report.trade_setup
|
||||
dir_color = Colors.GREEN if ts.direction == "BUY" else Colors.RED
|
||||
print(f"\n {Colors.BOLD}Trade Setup:{Colors.RESET}")
|
||||
print(f" Direction: {dir_color}{ts.direction}{Colors.RESET}")
|
||||
print(f" Entry: {ts.entry_price:.3f}")
|
||||
print(f" Stop Loss: {Colors.RED}{ts.stop_loss:.3f}{Colors.RESET} ({ts.risk_pips:.1f} pips)")
|
||||
print(f" Take Profit 1: {Colors.GREEN}{ts.take_profit_1:.3f}{Colors.RESET} ({ts.reward_pips:.1f} pips)")
|
||||
print(f" Take Profit 2: {Colors.GREEN}{ts.take_profit_2:.3f}{Colors.RESET}")
|
||||
print(f" Risk:Reward: 1:{ts.risk_reward:.1f}")
|
||||
|
||||
if report.primary_analysis and report.primary_analysis.key_levels:
|
||||
levels = report.primary_analysis.key_levels
|
||||
print(f"\n {Colors.BOLD}Key Levels:{Colors.RESET}")
|
||||
for lvl in ["r3", "r2", "r1", "pivot", "s1", "s2", "s3"]:
|
||||
if lvl in levels:
|
||||
val = levels[lvl]
|
||||
color = Colors.RED if val > report.current_price else Colors.GREEN
|
||||
print(f" {lvl.upper():>6}: {color}{val:.3f}{Colors.RESET}")
|
||||
|
||||
|
||||
def report_to_dict(report: SignalReport) -> dict:
|
||||
d = {
|
||||
"symbol": report.symbol,
|
||||
"display_name": report.display_name,
|
||||
"timestamp": report.timestamp,
|
||||
"current_price": report.current_price,
|
||||
"signal": report.final_signal.name,
|
||||
"score": round(report.final_score, 2),
|
||||
"confidence": round(report.final_confidence, 2),
|
||||
"quality": report.signal_quality,
|
||||
"action": report.action_recommendation,
|
||||
"market_regime": report.market_regime,
|
||||
"volatility_regime": report.volatility_regime,
|
||||
"mtf_agreement": report.mtf_agreement,
|
||||
"mtf_signals": {k: v.name for k, v in report.mtf_signals.items()},
|
||||
"event_risk": report.event_risk,
|
||||
"event_description": report.event_description,
|
||||
"news_sentiment": round(report.news_sentiment, 3),
|
||||
"bullish_reasons": report.bullish_reasons,
|
||||
"bearish_reasons": report.bearish_reasons,
|
||||
"warnings": report.warnings,
|
||||
}
|
||||
if report.trade_setup:
|
||||
ts = report.trade_setup
|
||||
d["trade_setup"] = {
|
||||
"direction": ts.direction,
|
||||
"entry": ts.entry_price,
|
||||
"stop_loss": ts.stop_loss,
|
||||
"take_profit_1": ts.take_profit_1,
|
||||
"take_profit_2": ts.take_profit_2,
|
||||
"risk_reward": ts.risk_reward,
|
||||
"risk_pips": round(ts.risk_pips, 1),
|
||||
"reward_pips": round(ts.reward_pips, 1),
|
||||
}
|
||||
if report.primary_analysis:
|
||||
d["indicators"] = {
|
||||
name: {
|
||||
"signal": ind.signal.name,
|
||||
"confidence": round(ind.confidence, 1),
|
||||
"description": ind.description,
|
||||
}
|
||||
for name, ind in report.primary_analysis.indicators.items()
|
||||
if not name.startswith("_")
|
||||
}
|
||||
d["key_levels"] = {
|
||||
k: round(v, 5) for k, v in report.primary_analysis.key_levels.items()
|
||||
}
|
||||
return d
|
||||
|
||||
|
||||
# ── Backtest renderer ──────────────────────────────────────────────
|
||||
|
||||
def print_backtest_report(result, params: dict = None, verbose_journal: bool = False):
|
||||
m = result.metrics
|
||||
print(f" {Colors.BOLD}{Colors.WHITE}Backtest Summary — USD/JPY{Colors.RESET}")
|
||||
if params:
|
||||
params_str = ", ".join(f"{k}={v}" for k, v in params.items())
|
||||
print(f" {Colors.GRAY}Params: {params_str}{Colors.RESET}")
|
||||
print(f" {Colors.GRAY}{'─' * 76}{Colors.RESET}")
|
||||
|
||||
initial = result.initial_balance
|
||||
final = result.final_balance
|
||||
ret_pct = m.get("total_return_pct", 0)
|
||||
ret_color = Colors.GREEN if ret_pct >= 0 else Colors.RED
|
||||
|
||||
print(f" Trades: {Colors.BOLD}{m.get('total_trades', 0)}{Colors.RESET}"
|
||||
f" ({m.get('wins', 0)}W / {m.get('losses', 0)}L)")
|
||||
print(f" Win Rate: {m.get('win_rate', 0):.1f}%")
|
||||
print(f" Profit Factor: {m.get('profit_factor', 0):.2f}")
|
||||
pf_inf = m.get('profit_factor', 0) == float('inf')
|
||||
if pf_inf:
|
||||
print(f" (no losing trades — infinite PF)")
|
||||
print(f" Expectancy: {m.get('expectancy_pips', 0):+.2f} pips/trade"
|
||||
f" ({m.get('expectancy_per_trade', 0):+.2f} per trade)")
|
||||
print(f" Initial Balance: ${initial:,.2f}")
|
||||
print(f" Final Balance: {ret_color}${final:,.2f}{Colors.RESET}")
|
||||
print(f" Total Return: {ret_color}{ret_pct:+.2f}%{Colors.RESET}")
|
||||
print(f" CAGR: {m.get('cagr', 0):+.2f}%")
|
||||
print(f" Max Drawdown: {Colors.RED}{m.get('max_drawdown_pct', 0):.2f}%{Colors.RESET}")
|
||||
print(f" Sharpe Ratio: {m.get('sharpe', 0):.2f}")
|
||||
print(f" Sortino Ratio: {m.get('sortino', 0):.2f}")
|
||||
print(f" Avg Duration: {m.get('avg_trade_duration_bars', 0):.1f} bars")
|
||||
print(f" Win Streak: {m.get('longest_winning_streak', 0)} | "
|
||||
f"Loss Streak: {m.get('longest_losing_streak', 0)}")
|
||||
print(f" {Colors.GRAY}{'─' * 76}{Colors.RESET}")
|
||||
|
||||
if verbose_journal and result.trades:
|
||||
print(f"\n {Colors.BOLD}Trade Journal (last 10):{Colors.RESET}")
|
||||
print(f" {'#':<4} {'Entry Time':<22} {'Dir':<5} {'Entry':<9} "
|
||||
f"{'Exit':<9} {'Reason':<6} {'Pips':>8} {'P&L%':>7}")
|
||||
for i, t in enumerate(result.trades[-10:], start=max(1, len(result.trades) - 9)):
|
||||
color = Colors.GREEN if t.pnl_currency > 0 else Colors.RED
|
||||
entry_str = str(t.entry_time)[:19]
|
||||
print(f" {i:<4} {entry_str:<22} {t.direction:<5} {t.entry_price:<9.3f} "
|
||||
f"{t.exit_price:<9.3f} {t.exit_reason:<6} "
|
||||
f"{color}{t.pnl_pips:>+8.1f}{Colors.RESET} "
|
||||
f"{color}{t.pnl_pct:>+6.2f}%{Colors.RESET}")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# CLI
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="ForexBot2 — USD/JPY Signal Intelligence + Backtester",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python main.py Live signal (H1, summary)
|
||||
python main.py --detail Live signal with full breakdown
|
||||
python main.py --timeframe H4 --detail H4 timeframe
|
||||
python main.py --json JSON output
|
||||
python main.py --backtest Walk-forward backtest (full history)
|
||||
python main.py --backtest --bars 2000 Backtest on last 2000 bars
|
||||
python main.py --backtest --optimize Parallel grid search across CPU cores
|
||||
""",
|
||||
)
|
||||
parser.add_argument("--timeframe", "-tf", default="H1",
|
||||
choices=["M5", "M15", "M30", "H1", "H4", "D1"],
|
||||
help="Primary analysis timeframe (default: H1)")
|
||||
parser.add_argument("--detail", action="store_true",
|
||||
help="Show full indicator breakdown (live mode)")
|
||||
parser.add_argument("--json", action="store_true",
|
||||
help="Output as JSON")
|
||||
parser.add_argument("--verbose", "-v", action="store_true",
|
||||
help="Debug logging")
|
||||
|
||||
# Backtest options
|
||||
parser.add_argument("--backtest", action="store_true",
|
||||
help="Run walk-forward backtest instead of live signal")
|
||||
parser.add_argument("--optimize", action="store_true",
|
||||
help="Run parallel parameter grid search (implies --backtest)")
|
||||
parser.add_argument("--bars", type=int, default=5000,
|
||||
help="Max bars of history (default 5000 — TD ceiling)")
|
||||
parser.add_argument("--start", default=None,
|
||||
help="Backtest window start (YYYY-MM-DD)")
|
||||
parser.add_argument("--end", default=None,
|
||||
help="Backtest window end (YYYY-MM-DD)")
|
||||
parser.add_argument("--min-grade", default="B",
|
||||
choices=["A+", "A", "B", "C", "D", "F"],
|
||||
help="Minimum grade to open backtest trade (default B)")
|
||||
parser.add_argument("--journal", action="store_true",
|
||||
help="Print last 10 trades from backtest journal")
|
||||
parser.add_argument("--journal-csv", default=None,
|
||||
help="Write full trade journal to CSV path")
|
||||
parser.add_argument("--regime-filter", action="store_true",
|
||||
help="Gate signals on higher-TF ADX trend confirmation")
|
||||
parser.add_argument("--regime-min-adx", type=float, default=30.0,
|
||||
help="Min ADX for regime filter (default 30, requires --regime-filter)")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def build_config(args) -> PlatformConfig:
|
||||
config = PlatformConfig()
|
||||
|
||||
tf_map = {
|
||||
"M5": Timeframe.M5, "M15": Timeframe.M15, "M30": Timeframe.M30,
|
||||
"H1": Timeframe.H1, "H4": Timeframe.H4, "D1": Timeframe.D1,
|
||||
}
|
||||
config.primary_timeframe = tf_map[args.timeframe]
|
||||
|
||||
tf_confirmations = {
|
||||
"M5": [Timeframe.M1, Timeframe.M15],
|
||||
"M15": [Timeframe.M5, Timeframe.H1],
|
||||
"M30": [Timeframe.M15, Timeframe.H1],
|
||||
"H1": [Timeframe.M15, Timeframe.H4],
|
||||
"H4": [Timeframe.H1, Timeframe.D1],
|
||||
"D1": [Timeframe.H4, Timeframe.W1],
|
||||
}
|
||||
config.confirmation_timeframes = tf_confirmations.get(
|
||||
args.timeframe, [Timeframe.M15, Timeframe.H4]
|
||||
)
|
||||
|
||||
# Backtest config
|
||||
config.backtest.start_date = args.start
|
||||
config.backtest.end_date = args.end
|
||||
config.backtest.max_bars = args.bars
|
||||
config.backtest.min_trade_grade = args.min_grade
|
||||
config.backtest.optimize = args.optimize
|
||||
config.backtest.regime_filter = args.regime_filter
|
||||
config.backtest.regime_filter_min_adx = args.regime_min_adx
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def run_live(args, config: PlatformConfig):
|
||||
print(f"{Colors.GRAY} Fetching market data and running analysis...{Colors.RESET}\n")
|
||||
aggregator = SignalAggregator(config)
|
||||
report = aggregator.analyze_pair()
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(report_to_dict(report), indent=2))
|
||||
return
|
||||
|
||||
print_header("USD/JPY SIGNAL INTELLIGENCE")
|
||||
print_signal_report(report, detail=args.detail)
|
||||
print(f"\n{Colors.CYAN}{'═' * 80}{Colors.RESET}")
|
||||
print(f"{Colors.GRAY} Disclaimer: For educational/research purposes only.{Colors.RESET}")
|
||||
print(f"{Colors.CYAN}{'═' * 80}{Colors.RESET}\n")
|
||||
|
||||
|
||||
def run_backtest(args, config: PlatformConfig):
|
||||
from backtest import WalkForwardBacktester
|
||||
from metrics import trades_to_dataframe
|
||||
|
||||
print_header("USD/JPY WALK-FORWARD BACKTEST")
|
||||
print(f"{Colors.GRAY} Loading data and replaying bars...{Colors.RESET}\n")
|
||||
|
||||
backtester = WalkForwardBacktester(config)
|
||||
result = backtester.run()
|
||||
|
||||
if args.json:
|
||||
out = {
|
||||
"metrics": result.metrics,
|
||||
"initial_balance": result.initial_balance,
|
||||
"final_balance": result.final_balance,
|
||||
"trades": [
|
||||
{
|
||||
"entry_time": str(t.entry_time),
|
||||
"exit_time": str(t.exit_time),
|
||||
"direction": t.direction,
|
||||
"entry_price": t.entry_price,
|
||||
"exit_price": t.exit_price,
|
||||
"exit_reason": t.exit_reason,
|
||||
"pnl_pips": t.pnl_pips,
|
||||
"pnl_pct": t.pnl_pct,
|
||||
"grade": t.grade,
|
||||
}
|
||||
for t in result.trades
|
||||
],
|
||||
}
|
||||
print(json.dumps(out, indent=2))
|
||||
else:
|
||||
print_backtest_report(result, verbose_journal=args.journal)
|
||||
|
||||
if args.journal_csv and result.trades:
|
||||
df = trades_to_dataframe(result.trades)
|
||||
df.to_csv(args.journal_csv, index=False)
|
||||
print(f"\n {Colors.GREEN}✓ Trade journal written to {args.journal_csv}{Colors.RESET}")
|
||||
|
||||
|
||||
def run_optimize(args, config: PlatformConfig):
|
||||
from backtest import Optimizer
|
||||
|
||||
print_header("USD/JPY BACKTEST PARAMETER OPTIMIZER")
|
||||
grid = config.backtest.param_grid
|
||||
n_combos = 1
|
||||
for v in grid.values():
|
||||
n_combos *= len(v)
|
||||
print(f"{Colors.GRAY} Grid: {n_combos} combos × {config.backtest.parallel_workers} workers{Colors.RESET}\n")
|
||||
|
||||
optimizer = Optimizer(config)
|
||||
results = optimizer.run_grid(grid)
|
||||
|
||||
print(f"\n {Colors.BOLD}Top 10 Parameter Combinations (by total return):{Colors.RESET}\n")
|
||||
print(f" {'#':<3} {'Return%':>8} {'PF':>6} {'WinR':>6} {'Trades':>7} "
|
||||
f"{'MaxDD%':>7} {'Sharpe':>7} Params")
|
||||
print(f" {Colors.GRAY}{'─' * 90}{Colors.RESET}")
|
||||
|
||||
for i, r in enumerate(results[:10], start=1):
|
||||
m = r.metrics
|
||||
ret = m.get("total_return_pct", 0)
|
||||
ret_color = Colors.GREEN if ret >= 0 else Colors.RED
|
||||
params_str = ", ".join(f"{k}={v}" for k, v in r.params.items())
|
||||
pf = m.get("profit_factor", 0)
|
||||
pf_str = "inf" if pf == float("inf") else f"{pf:.2f}"
|
||||
print(f" {i:<3} {ret_color}{ret:>+7.2f}%{Colors.RESET} {pf_str:>6} "
|
||||
f"{m.get('win_rate', 0):>5.1f}% {m.get('total_trades', 0):>7} "
|
||||
f"{m.get('max_drawdown_pct', 0):>+6.2f}% {m.get('sharpe', 0):>7.2f}"
|
||||
f" {Colors.GRAY}{params_str}{Colors.RESET}")
|
||||
|
||||
print(f"\n {Colors.BOLD}Best combination details:{Colors.RESET}\n")
|
||||
print_backtest_report(results[0], params=results[0].params, verbose_journal=False)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
log_level = logging.DEBUG if args.verbose else logging.WARNING
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
)
|
||||
|
||||
config = build_config(args)
|
||||
|
||||
if args.optimize:
|
||||
run_optimize(args, config)
|
||||
elif args.backtest:
|
||||
run_backtest(args, config)
|
||||
else:
|
||||
run_live(args, config)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user