Python/Django期权交易模拟:求支持无状态数学函数的用户自定义方案
安全实现用户自定义期权数学函数的几种方案
刚好做过类似的安全自定义表达式需求,给你几个实用的方向,既满足用户自定义期权逻辑的需求,又能严格避免执行恶意代码的风险:
方案1:基于Python AST的自定义表达式解析器(最可控)
Python的ast模块可以帮你直接解析用户输入的表达式字符串,然后遍历语法树验证每个节点是否都是允许的操作(比如基础算术、有限的数学函数、指定变量),完全杜绝执行任意代码的可能。
举个简单的实现思路:
- 接收用户输入的表达式(比如
max(Asset_price - 150, 0)) - 用
ast.parse将字符串转为抽象语法树 - 遍历语法树,检查所有节点:
- 只允许
Constant(数值)、Name(仅允许Asset_price这个变量) - 只允许
BinOp(加减乘除幂等算术操作)、Call(仅允许指定的安全函数,比如max、min、math.sqrt等)
- 只允许
- 验证通过后,编译并执行这个表达式,或者手动遍历语法树求值(更安全)
示例代码片段:
import ast import math ALLOWED_VARIABLES = {'Asset_price'} ALLOWED_FUNCTIONS = {'max', 'min', 'sqrt', 'log', 'exp'} ALLOWED_OPS = { ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.FloorDiv, ast.Mod } def validate_expression(expr): tree = ast.parse(expr, mode='eval') for node in ast.walk(tree): if isinstance(node, ast.Name): if node.id not in ALLOWED_VARIABLES: raise ValueError(f"不允许使用变量: {node.id}") elif isinstance(node, ast.Call): if isinstance(node.func, ast.Name) and node.func.id not in ALLOWED_FUNCTIONS: raise ValueError(f"不允许调用函数: {node.func.id}") # 还可以检查函数参数数量是否合法 elif isinstance(node, ast.BinOp): if type(node.op) not in ALLOWED_OPS: raise ValueError(f"不允许的操作符: {type(node.op).__name__}") # 禁止其他类型的节点,比如赋值、循环、导入等 elif not isinstance(node, (ast.Constant, ast.Expression, ast.Eval)): raise ValueError(f"不允许的语法节点: {type(node).__name__}") return tree def evaluate_option(asset_price, user_expr): try: tree = validate_expression(user_expr) # 构建安全的执行上下文 ctx = { 'Asset_price': asset_price, 'max': max, 'min': min, 'sqrt': math.sqrt, 'log': math.log, 'exp': math.exp } return eval(compile(tree, '<user_expr>', 'eval'), {}, ctx) except Exception as e: return f"表达式无效: {str(e)}"
方案2:使用受限的表达式求值库
如果不想自己写AST解析,可以用现成的安全表达式求值库,比如simpleeval——它允许你明确指定用户能使用的变量和函数,默认会阻止任何危险操作。
示例代码:
from simpleeval import SimpleEval import math def evaluate_option(asset_price, user_expr): evaluator = SimpleEval() # 只暴露允许的变量 evaluator.names['Asset_price'] = asset_price # 只允许安全的数学函数 evaluator.functions = { 'max': max, 'min': min, 'sqrt': math.sqrt, 'log': math.log, 'exp': math.exp, 'abs': abs } try: return evaluator.eval(user_expr) except Exception as e: return f"表达式错误: {str(e)}"
这个方案的优点是代码量极少,而且simpleeval已经帮你处理了大部分安全限制,适合快速开发。
方案3:自定义极简领域特定语言(DSL)
如果需要更严格的语法控制(比如只允许特定的操作格式),可以用PEG语法定义一个专属的极简数学语言,然后用lark或parsimonious这样的库来解析和求值。
比如定义一个只支持Asset_price变量、基础算术和指定函数的语法:
start: expression expression: additive additive: multiplicative (("+" | "-") multiplicative)* multiplicative: primary (("*" | "/") primary)* primary: number | variable | "(" expression ")" | function_call function_call: func_name "(" expression ("," expression)* ")" func_name: "max" | "min" | "sqrt" | "log" variable: "Asset_price" number: SIGNED_NUMBER %import common.SIGNED_NUMBER %import common.WS %ignore WS
然后用lark解析用户输入的字符串,生成语法树后手动求值,这样完全没有执行任意代码的可能,安全性拉满,但开发成本比前两个方案高一些。
方案4:沙箱化执行(谨慎选择)
如果确实需要支持更复杂的逻辑(不推荐,因为期权逻辑本质是纯数学函数),可以考虑用沙箱技术,比如:
- 使用PyPy的沙箱功能
- 把用户代码放到隔离的Docker容器中执行
但这种方案复杂度高,运维成本大,而且沙箱也不是100%安全(存在逃逸风险),对于小型资产模拟应用来说,前三个方案足够满足需求,没必要冒这个风险。
总结
推荐优先选择方案1或方案2:
- 方案1完全可控,适合对安全性要求极高的场景
- 方案2开发快速,适合小型项目快速落地
两者都能完美满足“无状态变更的纯数学函数”需求,同时杜绝安全风险。
内容的提问来源于stack exchange,提问作者tma
相关产品推荐
相关产品推荐

