diff --git a/README.md b/README.md index db96f92..6bc8756 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ if (owner == null) { 10. 2021-05-07 v1.6.2 新增: 转换保留字 assert 11. 2021-05-07 v1.6.3 新增: 行尾注释先去除再恢复; 新增转换保留字 del; 修复若干 bugs 12. 2021-05-07 v1.6.4 新增: 转换对象属性相关方法 hasattr, getattr, setattr; 界面上增加"文件名改驼峰式" - +13. 2021-05-08 v1.6.5 新增:实现文件名驼峰化选项;非py文件提示确认 ### TODO 待办事宜 1. Python 语言的列表推导式转换 diff --git a/default.aproj b/default.aproj index 0f9feca..870299e 100644 --- a/default.aproj +++ b/default.aproj @@ -1,5 +1,5 @@  - + diff --git a/dist/py2aardio.exe b/dist/py2aardio.exe index 9594f0a..e04fc85 100644 Binary files a/dist/py2aardio.exe and b/dist/py2aardio.exe differ diff --git a/main.aardio b/main.aardio index 4f0fc17..a5d475b 100644 --- a/main.aardio +++ b/main.aardio @@ -131,6 +131,20 @@ var copyFile = function(fileFullName, cfg) { } } +// 文件名驼峰化 +var camelizeFilename = function(fileFullName) { + var result = fileFullName; + var path = fsys.getParentDir(result); + var fname = fsys.getFileName(result); + var pattern = `([a-z\d\$])_([a-z])`; //以下划线'_'连接的小写字母文件名 + fname = string.replace(fname, pattern, function($1, $2) { + //owner = 原始字符串 + return $1 ++ string.upper($2); // file_name.ext -> fileName.ext + }); + result = path ++ fname; + //console.debug('camelizeFilename: result='++ result); + return result; +} var listener = thread.command(); listener.addSourceItem = function(sourceType, item) { @@ -172,7 +186,16 @@ mainForm.btnStart.oncommand = function(id, event) { if (sourceType == 'file') { thread.command.send('addSourceItem', 'file', srcPath); + var ext = fsys.getExtensionName(srcPath); + if(ext != 'py') { + var answer = mainForm.msgboxTest('该文件(*.'++ext++')不是 python 源文件, 是否继续?'); + if(answer != true) + return; + } cfg.targetFile = getTargetFullName(srcPath); // 通过cfg传入目标文件完全路径 + if(cfg.camelizeFile) { + cfg.targetFile = camelizeFilename(cfg.targetFile); + } convertSingleFile(srcPath, cfg); // 转换单个文件 } elseif(sourceType == 'dir') { @@ -181,20 +204,22 @@ mainForm.btnStart.oncommand = function(id, event) { fsys.enum(srcPath/*要遍历的目录*/, "*.*"/*指定查询文件名,支持windows掩码*/, function(dir, filename, fileFullName, findData) { //指定触发器 if (filename) { //若是文件 var ext = fsys.getExtensionName(filename); - console.log("src file:" ++ fileFullName); + //console.debug("src file:" ++ fileFullName); if (ext == 'py') { // 若是 py 文件 则转换格式 - //console.debug(fileFullName); count++; - //console.log("文件:"++filename, ', dir='++dir, ',fileFullName='++fileFullName); + //console.debug("文件:"++filename, ', dir='++dir, ',fileFullName='++fileFullName); thread.command.send('addSourceItem', 'file', fileFullName); - cfg.targetFile = getTargetFullName(fileFullName); // 通过cfg传入目标文件完全路径 + cfg.targetFile = getTargetFullName(fileFullName); // 通过cfg 将目标文件路径传入线程 + if(cfg.camelizeFile) { + cfg.targetFile = camelizeFilename(cfg.targetFile); + } thread.invoke(convertFn, dir, filename, fileFullName, cfg); } elseif (cfg.copyOthers) { // 否则,若需复制目录内的其它文件(用于整个工程的整体性转换) //console.debug(fileFullName); count++; thread.command.send('addSourceItem', 'file', fileFullName); - cfg.targetFile = getTargetFullName(fileFullName, false); // 通过cfg传入目标文件完全路径(false=不修改扩展名) + cfg.targetFile = getTargetFullName(fileFullName, false); // 通过cfg 将目标文件路径传入线程(false=不修改扩展名) thread.invoke(copyFile, fileFullName, cfg); } } diff --git a/test/jqFollower.aardio b/test/jqFollower.aardio new file mode 100644 index 0000000..65bac83 --- /dev/null +++ b/test/jqFollower.aardio @@ -0,0 +1,147 @@ +// -*- coding: utf-8 -*- +import re; +import datetime; var datetime = datetime.datetime; +import threading; var Thread = threading.Thread; + +import ez; var exceptions = ez.exceptions; +import ez.follower; var BaseFollower = ez.follower.BaseFollower; +import ez.log; var logger = ez.log.logger; + + +class JoinQuantFollower { + this = BaseFollower(...);// 继承的父类 <--请把这行移入类的构造函数 ctor 内! { + LOGIN_PAGE = "https://www.joinquant.com"; + LOGIN_API = "https://www.joinquant.com/user/login/doLogin?ajax=1"; + TRANSACTION_API =( + "https://www.joinquant.com/algorithm/live/transactionDetail" + ); + WEB_REFERER = "https://www.joinquant.com/user/login/index"; + WEB_ORIGIN = "https://www.joinquant.com"; + + createLoginParams = function(user, password, **kwargs) { + params = { + "CyLoginForm[username]": user, + "CyLoginForm[pwd]": password, + "ajax": 1, + }; + return params; + } + + checkLoginSuccess = function(rep) { + setCookie = rep.headers["set-cookie"]; + if (#(setCookie) < 50) { + error exceptions.NotLoginError("登录失败,请检查用户名和密码"); + } + this.s.headers.update({"cookie": setCookie}); + } + + follow = function( + this, + users, + strategies, + trackInterval=1, + tradeCmdExpireSeconds=120, + cmdCache=true, + entrustProp="limit", + sendInterval=0, +) { + /*跟踪joinquant对应的模拟交易,支持多用户多策略 + :param users: 支持ez的用户对象,支持使用 [] 指定多个用户 + :param strategies: joinquant 的模拟交易地址,支持使用 [] 指定多个模拟交易, + 地址类似 https://www.joinquant.com/algorithm/live/index?backtestId=xxx + :param trackInterval: 轮训模拟交易时间,单位为秒 + :param tradeCmdExpireSeconds: 交易指令过期时间, 单位为秒 + :param cmdCache: 是否读取存储历史执行过的指令,防止重启时重复执行已经交易过的指令 + :param entrustProp: 委托方式, 'limit' 为限价,'market' 为市价, 仅在银河实现 + :param sendInterval: 交易发送间隔, 默认为0s。调大可防止卖出买入时卖出单没有及时成交导致的买入金额不足 + */ + users = this.warpList(users); + strategies = this.warpList(strategies); + + if (cmdCache) { + this.loadExpiredCmdCache(); + } + + this.startTraderThread( + users, tradeCmdExpireSeconds, entrustProp, sendInterval + ); + + workers = {}; + for (strategyUrl in strategies) { + try { + strategyId = this.extractStrategyId(strategyUrl); + strategyName = this.extractStrategyName(strategyUrl); + } catch(e) { + logger.error("抽取交易id和策略名失败, 无效的模拟交易url: %s", strategyUrl); + error; + } + strategyWorker = Thread( + target=this.trackStrategyWorker, + args= {strategyId, strategyName}, + kwargs={"interval": trackInterval}, + ); + strategyWorker.start(); + workers.append(strategyWorker); + logger.info("开始跟踪策略: %s", strategyName); + } + for (worker in workers) { + worker.join(); + } + } + + // @staticmethod; + extractStrategyId = function(strategyUrl) { + return re.search("(?<=backtestId=)\w+", strategyUrl).group(); + } + + extractStrategyName = function(strategyUrl) { + rep = this.s.get(strategyUrl); + return this.reFind('(?<=title="点击修改策略名称"\>).*(?=\