quant-knowledge-extractor / src /SignalCompiler.jl
cyberkyne's picture
Upload 22 files
094a5f6 verified
"""
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