""" SignalCompiler.jl — Compile AI-generated Julia strategy code. No includes. Indicators functions injected explicitly into sandbox. """ module SignalCompiler using Statistics, Random export compile_strategy, CompiledStrategy struct CompiledStrategy name :: String generate_fn :: Function param_grid_fn :: Function is_valid :: Bool error :: String end CompiledStrategy(name::String; error::String="") = CompiledStrategy(name, (o,h,l,c,v,p)->zeros(Int,length(c)), ()->Dict{String,Vector{Float64}}(), false, error) """ compile_strategy(name, code, indicator_module) -> CompiledStrategy indicator_module is the Indicators module, passed from QuantEngine. """ function compile_strategy(name::String, code::String, ind_mod::Module)::CompiledStrategy safe = replace(replace(name," "=>"_"), r"[^\w]"=>"x") sandbox = Module(Symbol("S_"*safe*"_"*string(rand(UInt16),base=16))) # Inject all exported Indicators functions for fn_name in names(ind_mod; all=false) fn_name === :Indicators && continue try Core.eval(sandbox, Expr(:const, Expr(:(=), fn_name, getfield(ind_mod, fn_name)))) catch; end end # Inject Statistics for sym in (:mean,:std,:var,:median,:cor,:cov) try Core.eval(sandbox, Expr(:const, Expr(:(=),sym,getfield(Statistics,sym)))); catch; end end # Inject safe Base for sym in (:length,:size,:zeros,:ones,:fill,:similar, :sum,:prod,:diff,:cumsum,:cumprod, :max,:min,:abs,:sqrt,:log,:exp,:floor,:ceil,:round,:clamp, :isnan,:isinf,:isfinite,:sign, :sort,:sortperm,:reverse,:unique,:findall,:findfirst, :push!,:append!,:pop!,:first,:last,:eachindex, :map,:filter,:any,:all,:count, :Int,:Int64,:Float64,:Bool, :Dict,:Vector,:Tuple,:Set, :NaN,:Inf,:pi,:true,:false, :println,:string,:get) try Core.eval(sandbox, Expr(:const, Expr(:(=),sym,getfield(Base,sym)))); catch try Core.eval(sandbox, Expr(:const, Expr(:(=),sym,eval(sym)))); catch; end end end parsed = try Meta.parseall(code) catch e; return CompiledStrategy(name; error="Parse: $(sprint(showerror,e))"); end try Core.eval(sandbox, parsed) catch e; return CompiledStrategy(name; error="Eval: $(sprint(showerror,e))"); end isdefined(sandbox,:get_param_grid) || return CompiledStrategy(name; error="Missing: get_param_grid()") isdefined(sandbox,:generate_signals) || return CompiledStrategy(name; error="Missing: generate_signals(o,h,l,c,v,params)") gen_fn = getfield(sandbox, :generate_signals) grid_fn = getfield(sandbox, :get_param_grid) err = _smoke(gen_fn, grid_fn) err != "" && return CompiledStrategy(name; error=err) return CompiledStrategy(name, gen_fn, grid_fn, true, "") end function _smoke(gen_fn, grid_fn)::String try grid=grid_fn() grid isa Dict || return "get_param_grid() must return Dict" params=Dict{String,Float64}(k=>Float64(v isa Vector && !isempty(v) ? v[1] : 0) for (k,v) in grid) n=200; c=100.0.*exp.(cumsum(randn(n).*0.005)) h=c.*(1.0.+abs.(randn(n)).*0.003); l=c.*(1.0.-abs.(randn(n)).*0.003) o=c.*(1.0.+randn(n).*0.001); v=abs.(randn(n)).*1000.0.+500.0 sigs=gen_fn(o,h,l,c,v,params) sigs isa Vector || return "generate_signals must return Vector, got $(typeof(sigs))" length(sigs)!=n && return "Signal length $(length(sigs)) ≠ $n" any(s->!(s in (-1,0,1)), sigs) && return "Values must be in {-1,0,1}" catch e; return "Smoke: $(sprint(showerror,e))"; end return "" end end # module SignalCompiler