File size: 3,460 Bytes
094a5f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1a56c89
094a5f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"""
QuantEngine.jl β€” Top-level module. Only file that uses include().
Wires all submodules together by injecting dependencies explicitly.
Python imports this via juliacall.
"""
module QuantEngine

using Statistics, Random

# ── Include all submodules (ONLY here) ───────────────
include("Indicators.jl")
include("BacktestEngine.jl")
include("Optimizer.jl")
include("SignalCompiler.jl")

using .Indicators
using .BacktestEngine
using .Optimizer
using .SignalCompiler

export
    # Indicators
    sma, ema, wma, tema, dema,
    rsi, macd, stoch, cci, williams_r,
    atr, bbands, keltner, donchian, adx,
    vwap, obv, cmf, zscore, std_dev,
    momentum, roc, highest, lowest,
    crossover, crossunder,
    # Engine
    BacktestConfig, run_backtest,
    # High-level
    full_backtest_pipeline

"""
    full_backtest_pipeline(...) -> Dict{String,Any}

End-to-end: compile Julia strategy code β†’ walk-forward optimize
β†’ return plain Dict that crosses to Python cleanly.
"""
function full_backtest_pipeline(
    strategy_code::String, strategy_name::String,
    open_p::Vector{Float64}, high::Vector{Float64},
    low::Vector{Float64},    close::Vector{Float64},
    volume::Vector{Float64}, timeframe::String, symbol::String;
    n_windows::Int=5, is_ratio::Float64=0.70,
    min_trades::Int=30, min_sharpe::Float64=0.5,
    max_combos::Int=300,
    initial_equity::Float64=10_000.0,
    commission_pct::Float64=0.0002,
    risk_per_trade::Float64=0.01,
)::Dict{String,Any}

    # 1. Compile strategy β€” pass Indicators module explicitly
    compiled = SignalCompiler.compile_strategy(strategy_name, strategy_code, Indicators)
    if !compiled.is_valid
        return Dict{String,Any}("is_valid"=>false,"error"=>compiled.error,
            "strategy"=>strategy_name,"symbol"=>symbol,"timeframe"=>timeframe)
    end

    # 2. Walk-forward optimize β€” inject BacktestEngine functions to avoid circular deps
    cfg_fn = () -> BacktestConfig(
        initial_equity=initial_equity,
        commission_pct=commission_pct,
        risk_per_trade=risk_per_trade,
    )

    # Wrap run_backtest to inject atr function from Indicators
    run_bt_fn = (o,h,l,c,v,sigs,tf,cfg) ->
        BacktestEngine.run_backtest(o,h,l,c,v,sigs,tf,cfg, Indicators.atr)

    opt = Optimizer.walk_forward_optimize(
        compiled.generate_fn,
        compiled.param_grid_fn(),
        open_p, high, low, close, volume,
        timeframe, strategy_name, symbol;
        run_bt_fn=run_bt_fn,
        bt_cfg_fn=cfg_fn,
        n_windows=n_windows, is_ratio=is_ratio,
        min_trades=min_trades, min_sharpe=min_sharpe,
        max_combos=max_combos,
    )

    return Dict{String,Any}(
        "is_valid"        => true,
        "strategy"        => opt.strategy_name,
        "symbol"          => opt.symbol,
        "timeframe"       => opt.timeframe,
        "optimal_params"  => opt.optimal_params,
        "oos_sharpe_mean" => opt.oos_sharpe_mean,
        "oos_sharpe_std"  => opt.oos_sharpe_std,
        "oos_win_rate"    => opt.oos_win_rate,
        "oos_max_dd"      => opt.oos_max_dd,
        "oos_pf_mean"     => opt.oos_pf_mean,
        "oos_trades"      => opt.oos_trades,
        "wf_efficiency"   => opt.wf_efficiency,
        "robustness"      => opt.robustness,
        "is_viable"       => opt.is_viable,
        "reasons"         => opt.reasons,
        "oos_sharpes"     => opt.oos_sharpes,
    )
end

end # module QuantEngine