大体第二十九

大体第二十九

本文“不引绳之外,不推绳之内,不急法之外,不缓法之内”几句在《韩非子》的其他篇章中也曾出现过,但这里的“法”与“道”、“成理”、“自然”等概念相重合,已不再是其他篇章中具体的“法”了。在思想上本文更接近于《解老》、《喻老》二篇,原始道家色彩较浓,因而关键词“大体”几乎就是“道”的韩非版。从中可以进一步看出韩非对原始道家的继承。

古之全全面把握大体大体,事物的整体和根本。全大体,犹顾全大局者:望天地,观江海,因顺应。以下从“山谷”到“云布风动”都是“因”的宾语山谷,日月所照,四时所行,云布风动句谓顺应和利用自然界 不以智累心,不以私累己;寄治乱于法术,托是非于赏罚句指赏罚随是非,属托付轻重于权衡;不逆天理,不伤情性;不吹毛而求小疵,不洗垢而察难知难知,事物的隐微之处;不引绳喻法度之外,不推绳之内;不急法之外,不缓法之内四句皆言严格依法而行,语已见《安危》;守成理,因自然;祸福生乎道法,而不出乎爱恶;荣辱之责在乎己,而不在乎人。故至安之世,法如朝露,纯朴不散散乱。梁启雄云:“谓法如朝露那么纯洁淳朴,普及而不散”,心无结怨,口无烦言。故车马不疲弊疲弊,陈奇猷谓二字衍一字。当系因注文而衍于远路句谓无远征他国之劳,旌旗不乱于大泽古代造反者常以大泽为屏障与官军对抗。句谓无平定内乱的恶战,万民不失命失命,与下文“创寿”同为不得其死之意于寇戎寇戎,强盗和战争,雄骏通“俊”,雄骏,勇士不创寿于旗幢亦旗帜。旗幢,喻指战阵之事,豪杰不著名于图书图书,代史书,不录功于盘盂盘盂,都是器皿,上古时有记功于青铜器皿上的习惯,故云,记年之牒谱牒,即簿册空虚以上与《解老》、《喻老》所引老子“却走马以粪”之义近。故曰:“利莫长于简,福莫久于安。”使匠石匠石,古巧匠名以千岁之寿操钩石工工具,视规矩,举绳墨,而正整治太山;使贲、育带干将干将,古宝剑名而齐治理万民;虽尽力于巧,极盛于寿句谓特别长寿,太山不正,民不能齐。故曰:古之牧统治天下者,不使匠石极作动词,尽情施展巧以败太山之体,不使贲、育尽威以伤万民之性。因道全保全法,君子乐而大奸止。澹dàn,安静然闲静,因天命,持大体。故使人无离通“罹”,这里是触犯法之罪,鱼无失水之祸。如此,故天下少不可少不可,很少有行不通的事情

上不天则下不遍覆,心不地则物不必通“毕”,尽,全载。太山不立好恶,故能成其高;江海不择小助,故能成其富。故大人大人,此“大人”显然具原始道家色彩寄形于天地而万物备,历王先慎据《群书治要》作“措”心于山海历心于山海,谓从山海当中得到启迪而国家富。上无忿怒之毒,下无伏怨之患,上下交朴句谓君臣上下都非常纯朴,以道为舍栖息之处,归宿。故长利积,大功立,名成于前,德垂于后,治之至也。

 
基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的Koopman算子的递归神经网络模型线性化”展开,旨在研究纳米定位系统的预测控制方。通过结合数据驱动技术与Koopman算子理论,将非线性系统动态近似为高维线性系统,进而利用递归神经网络(RNN)建模并实现系统行为的精确预测。文中详细阐述了模型构建流程、线性化策略及在预测控制中的集成应用,并提供了完整的Matlab代码实现,便于科研人员复现实验、优化算并拓展至其他精密控制系统。该方有效提升了纳米级定位系统的控制精度与动态响应性能。; 适合人群:具备自动控制、机器学习或信号处理背景,熟悉Matlab编程,从事精密仪器控制、智能制造或先进控制算研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①实现非线性动态系统的数据驱动线性化建模;②提升纳米定位平台的轨迹跟踪与预测控制性能;③为高精度控制系统提供可复现的Koopman-RNN融合解决方案; 阅读建议:建议结合Matlab代码逐段理解算实现细节,重点关注Koopman观测矩阵构造、RNN训练流程与模型预测控制器(MPC)的集成方式,鼓励在实际硬件平台上验证并调整参数以适应具体应用场景。
<think>我们正在处理一个通达信DLL开发任务,目标是根据输入的日期数组,计算该交易日所有股票日涨幅的中位数,并将结果赋值给pfout数组。我们将使用通达信DLL标准接口。 关键点: 1. 通达信DLL函数原型:__declspec(dllexport) void __stdcall TDXDLL3(int DataLen, float* pfOUT, float* pfINa, float* pfINb, float* pfINc); 参数说明: DataLen: 数据长度(K线数量) pfOUT: 输出数组(我们需要将每个K线对应的涨幅中位数放在这里) pfINa: 输入数组1,这里我们用来传递日期(DATE数组) pfINb: 输入数组2,这里我们用来传递刷新标志(0=使用存,1=强制刷新) pfINc: 输入数组3,保留 2. 我们需要计算自2012年以来所有股票日涨幅的中位数。因此,我们需要读取通达信本地数据(VIPOC数据)来获取每个交易日所有股票的涨跌幅数据。 3. 由于计算全市场股票数据量较大,我们需要设计存机制,避免每次重复计算。 4. 日期处理:通达信公式中的DATE是一个浮点数,表示YYYYMMDD格式的日期,但以浮点数传递时可能会有精度问题,我们需要将其转换为整数日期(DWORD类型)。 5. 数据读取:通达信DLL开发中,如何读取本地VIPOC数据?通达信提供了TDX_DataReader.dll,我们可以用它来读取历史数据。但是,由于同用户安装路径同,我们需要通过注册表获取通达信安装路径,然后加载该DLL。 6. 步骤: a. 在DLL初始化时,尝试加载TDX_DataReader.dll(通过注册表获取路径)。 b. 定义函数指针,用于调用TDX_DataReader.dll中的函数(如OpenTdxDayData, ReadTdxDaysData等)。 c. 对于每个传入的日期(pfINa数组中的每个元素),我们首先检查存中是否有该日的中位数结果。如果有,则直接使用;如果没有,则读取该日所有股票的日线数据,计算涨幅中位数,并存入存。 d. 将计算出的中位数赋值给pfOUT数组的对应位置。 7. 存设计:使用一个std::map<DWORD, float>,键为整数日期(YYYYMMDD),值为该日涨幅中位数。 8. 强制刷新:当pfINb[0]为0时,表示强制刷新存,则清除存重新加载。 9. 注意:由于全市场股票数量多,计算中位数需要排序,所以性能是关键。我们应尽量减少重复读取数据。 10. 读取单日所有股票数据的步骤: a. 获取所有股票代码列表(通过TDX_DataReader.dll提供的函数)。 b. 对于每只股票,读取该日涨跌幅(如果该日有数据)。 c. 收集所有股票的涨跌幅,然后计算中位数。 11. 计算中位数:将涨跌幅数据排序,如果数据量为奇数,取中间值;偶数,取中间两个的平均值。 12. 注意:涨跌幅数据可能为0(停牌等),我们可能需要过滤掉停牌的股票?或者过滤,因为中位数本身包含所有正常交易的股票。 13. 性能优化:由于需要计算全市场所有股票,我们可以按日期存整个交易日的数据,避免同一日期重复计算。 14. 数据读取函数的使用(以TDX_DataReader.dll为例): - 首先,需要初始化数据读取器:OpenTdxDayData(市场代码, 股票代码, 文件路径) 或者使用更高级的函数直接读取某日数据。 - 但是,通达信的TDX_DataReader.dll并没有直接提供读取某一天所有股票数据的函数,所以我们需要自己遍历所有股票。 15. 获取股票列表:我们可以通过读取通达信安装目录下的T0002/hq_cache目录下的文件(如shase.tnf, sznse.tnf)来获取股票列表,或者使用TDX_DataReader.dll中的相关函数。 16. 由于直接使用TDX_DataReader.dll比较复杂,而且同版本可能同,这里我们采用另一种方式:通达信提供了专业财务数据读取函数,但这里我们只需要日线数据。因此,我们使用通达信安装目录下的“vipdoc”目录结构,即按市场/股票代码存储的日线文件(.day文件)。 17. 文件结构: shase\day\sh600000.day sznse\day\sz000001.day 18. 读取.day文件:每个.day文件包含该股票的日线数据(每32字节一条记录),结构如下: struct DayData { DWORD date; // 日期(YYYYMMDD) DWORD open; // 开盘价*1000 DWORD high; // 最高价*1000 DWORD low; // 最低价*1000 DWORD close; // 收盘价*1000 DWORD amount; // 成交额(元) DWORD vol; // 成交量(手) DWORD reserve; // 保留 }; 19. 涨跌幅计算:涨跌幅 = (今日收盘价 - 前一日收盘价) / 前一日收盘价 * 100 但是注意:我们无从单条日线记录中得到前一日收盘价,所以需要读取该股票的前一天数据?这会导致性能急剧下降。因此,我们考虑使用复权数据?或者,通达信的.day文件中,每条记录是否包含涨跌幅?实际上,.day文件直接存储涨跌幅,需要计算。 20. 但是,计算单日涨跌幅需要前一天的数据,这会导致我们需要读取每只股票的两条记录(当天和前一天的收盘价)。这非常耗时。 21. 替代方案:通达信的日线数据在vipdoc目录下,我们可以使用通达信提供的专业财务数据接口(Professional.dll)?但这里我们使用更直接的方:使用通达信DLL开发包中的函数(如果可用)。然而,官方并未公开这些接口。 22. 权衡:由于性能要求,我们可能需要预先计算并存储每个交易日的全市场涨跌幅数据。但DLL中无预先计算所有日期,所以只能按需计算(即当传入日期时计算)。 23. 优化思路:我们可以为每只股票维护一个内存映射,存其日线数据,这样在计算多个日期的中位数时,可以快速读取任意一天的收盘价。但这样内存占用会很大。 24. 另一种思路:由于我们需要的是每个交易日的中位数,我们可以按日期组织数据,即一次性读取所有股票在该交易日的收盘价,然后计算涨跌幅(需要知道前收盘)。但注意,停牌的股票没有数据,所以涨跌幅计算只考虑当日有交易的股票。 25. 计算涨跌幅需要前收盘,而前收盘就是前一交易日的收盘价。因此,对于每个交易日,我们都需要每只股票的前一交易日收盘价。这导致我们必须读取每只股票的前一天数据。 26. 考虑到性能,我们可能需要建立每只股票的日线数据索引(按日期排序),这样我们可以用二分查找快速找到前一天的数据。但初始化所有股票的日线数据索引非常耗时。 27. 鉴于以上复杂性,我们可以采用以下折中方案: a. 在DLL加载时,立即初始化所有数据,而是按需加载(当遇到某个日期时,再加载该日所有股票的数据,并计算中位数)。 b. 对于每只股票,我们只存最近一段时间的日线数据(例如,当前日期前推一年),这样在计算连续日期时,可以快速获取前一天的数据。 28. 但是,由于我们需要从2012年开始的数据,所以存所有股票的所有日线数据内存够。因此,我们只能按日期计算:对于每个传入的日期,我们遍历所有股票,读取该股票在该日及前一日的数据(如果存在),然后计算涨跌幅。 29. 步骤(对于单个日期): - 遍历所有股票(从市场目录中获取股票列表)。 - 对于每只股票,读取其.day文件,查找指定日期(以及前一个交易日)的数据。 - 如果找到该日数据,并且有前一个交易日的数据,则计算涨跌幅。 - 收集所有涨跌幅数据,排序,计算中位数。 30. 性能问题:假设有4000只股票,每个.day文件我们使用二分查找(如果已建立索引)需要约10次文件读取(每次读取32字节),那么每个日期需要4000*10=40000次文件读取操作。这非常慢。 31. 优化:我们可以为每只股票存其日线数据(以日期为键的map),这样第一次读取该股票时加载整个文件,后续只需要内存查找。但内存占用很大(4000只股票*3000个交易日*32字节≈384MB),这还包括索引的开销。但是,现代计算机可以承受。 32. 因此,我们设计如下: - 全局存:一个map,键为股票代码,值为该股票的日线数据(另一个map,键为日期(DWORD),值为当日收盘价(float))。 - 在DLL加载时,这个存为空。当需要某只股票的数据时,如果存中没有,则加载整个.day文件,并建立日期到收盘价的映射(同时存储前复权因子?这里我们使用后复权因子?通达信.day文件是后复权数据吗?实际上,vipdoc目录下的是未复权的数据。我们需要复权数据吗?涨跌幅计算应该使用复权后的价格,否则会受除权影响。但中位数计算应该使用复权后的涨跌幅。 33. 通达信DLL开发包中提供了复权计算函数,但这里我们为了简化,使用后复权数据。我们可以从通达信目录的“T0002/hq_cache”目录下获取复权因子文件,然后计算复权价格。但这样更复杂。 34. 考虑到中位数计算对复权敏感(因为每只股票都复权,所以涨跌幅是连续可比的),我们使用后复权数据。但.day文件是未复权的,所以我们需要读取复权因子文件(权息文件)并计算复权价格。 35. 这极大地增加了复杂度。因此,我们采用另一种方式:通达信公式中已经提供了涨跌幅(即我们使用未复权的涨跌幅?)。但未复权的涨跌幅在除权日会连续。所以,我们最好使用前复权涨跌幅。 36. 由于时间关系,我们假设使用未复权数据(即直接从日线数据计算涨跌幅)可能也能接受,因为中位数反映的是当日大多数股票的涨跌情况,除权日只是少数。但严格来说,应该使用复权数据。 37. 折中:我们使用通达信vipdoc目录下的lday目录(复权数据)?通达信的复权数据文件在vipdoc目录下没有提供,而是通过专业财务数据接口提供。 38. 因此,我们决定使用未复权数据计算涨跌幅,并忽略除权日的影响(因为除权日通常停牌,或者即使交易,涨跌幅也会很大,但中位数会排除这些异常值吗?会,所以还是有问题)。 39. 考虑到任务复杂度,我们假设使用未复权数据。如果需要复权,则需要额外的工作。 40. 实现步骤(简化): a. 在DLL中,获取通达信安装路径(通过注册表:HKEY_LOCAL_MACHINE\SOFTWARE\TdxW\InstallPath)。 b. 遍历两个市场(shase, sznse)的day目录,获取所有股票代码。 c. 为每只股票构建一个日线数据存(日期->收盘价),并存储在全局的map中(按股票代码索引)。 d. 对于每个传入的日期(YYYYMMDD),遍历所有股票,从存中获取该日收盘价,以及该日的前一个交易日的收盘价(注意:前一个交易日一定是前一天,需要找到最近的前一个交易日)。 e. 计算涨跌幅:涨幅 = (当日收盘价 - 前一日收盘价) / 前一日收盘价 * 100 f. 收集所有股票的涨幅,排序,计算中位数。 41. 如何获取前一个交易日?在每只股票的日线数据存中,我们存储的是按日期排序的vector,这样我们可以通过二分查找找到指定日期的记录,然后取前一条记录就是前一个交易日。 42. 注意:如果该日是股票上市第一天,则没有前一日数据,则跳过该股票。 43. 存加载:第一次调用时加载所有股票的日线数据会非常慢(需要读取4000多个文件),所以我们可以考虑在后台线程加载,或者按需加载(只加载需要计算的股票)。 44. 按需加载:我们并需要所有股票的数据,只需要在传入的日期范围内有交易的股票。但传入的日期可能任意,所以我们还是需要加载所有股票的数据。 45. 启动时加载:在DLL加载时,启动一个线程加载所有股票数据,但这样会导致DLL加载时间很长。因此,我们采用懒加载:当需要某只股票的数据时,才加载其日线数据。 46. 具体实现: - 全局变量:std::map<std::string, std::map<DWORD, float>> g_stockData; // 股票代码->(日期->收盘价) - 对于一只股票,如果g_stockData中没有,则读取其.day文件,并解析存储。 47. 日期格式:.day文件中的日期是DWORD(YYYYMMDD),收盘价是DWORD(实际是float*1000的整数?,是存储为整数,例如10.05元存储为10050?通达信.day文件格式:收盘价是DWORD类型,实际是整数,单位是分?对,是元*1000,即1000倍整数。 48. 收盘价转换:收盘价 = (float)(dayData.close) / 1000.0f; 49. 文件读取:读取.day文件,按照32字节一条记录读取。 50. 文件路径:例如,上海市场的600000股票:shase\day\sh600000.day 51. 市场代码:我们通过目录名区分,上海市场:shase,深圳市场:sznse。 52. 股票代码:目录下的文件名(含扩展名)就是股票代码。 53. 步骤总结(单个日期计算): DWORD dwDate = 转换传入的日期(浮点数)为整数; 初始化一个空的vector<float> changes; 遍历两个市场目录下的所有.day文件(股票): if (该股票数据未加载) { 加载该股票.day文件到g_stockData[股票代码](一个map<DWORD, float>) } 在该股票的map中查找dwDate: 如果找到,则记录当日收盘价(price0),然后查找前一个交易日(即map中比dwDate小的最大日期对应的收盘价price1) 如果找到price1,则计算涨跌幅 = (price0 - price1) / price1 * 100.0f,并加入changes 对changes排序,计算中位数,赋值给pfOUT[i] 54. 查找前一个交易日:由于map是按日期升序排列的,我们可以用lower_bound找到第一个大于等于dwDate的迭代器,然后迭代器减1就是前一个交易日。注意:减1前要判断是否在开始位置。 55. 性能问题:即使使用内存存,每个日期都需要遍历4000多只股票,并且每只股票进行两次map查找(当日和昨日),那么每个日期需要约8000次map查找(O(log n)),假设每只股票有2000个交易日,则每次查找约11次比较。总计算量:4000*11=44000次比较,对于每个日期。这可以接受(现代CPU每秒可以执行上亿次操作)。 56. 但是,如果DataLen很大(比如1000根K线),那么就需要1000*4000=4百万次查找,这可能需要几秒钟。因此,我们需要进一步优化。 57. 优化:我们可以按日期存整个市场的中位数结果,这样相同日期只需计算一次。即全局存:std::map<DWORD, float> g_medianCache; 这样,如果传入的日期已经计算过,直接使用。 58. 因此,流程变为: for (int i=0; i<DataLen; i++) { DWORD dwDate = FloatToDate(pfINa[i]); if (强制刷新 || g_medianCache中没有dwDate) { 计算该日期的中位数,存入g_medianCache[dwDate] } pfOUT[i] = g_medianCache[dwDate]; } 59. 这样,即使DataLen很大,但实际需要计算的日期可能只有少数(因为K线日期是连续的,但可能有重复?),所以效率高。 60. 存大小:随着时间推移,存会变大,我们可以设置最大存大小(例如10000个交易日),超过则清除最早的一部分。 61. 代码结构: - 全局存:g_stockData(按股票代码存日线数据)和g_medianCache(按日期存中位数) - 函数:LoadStockData(const char* market, const char* stockCode) 加载单个股票数据 - 函数:float CalculateMedian(DWORD date) 计算指定日期的中位数 62. 注意:多线程安全。通达信调用DLL是单线程的,所以需要考虑多线程。 63. 文件路径获取:通过注册表获取通达信安装路径,然后拼接"vipdoc\\shase\\day"等。 64. 获取股票列表:遍历vipdoc\shase\day和vipdoc\sznse\day目录下的所有.day文件。 65. 遍历目录:使用Windows API(FindFirstFile, FindNextFile)。 66. 加载股票数据:在第一次需要该股票时加载。 67. 计算中位数的函数CalculateMedian(DWORD date): vector<float> changes; for (遍历所有股票) { if (该股票在date有数据) { 找到该日数据,然后找到前一个交易日数据(如果存在) 计算涨跌幅,加入changes } } if (changes为空) 返回0.0f; else 排序,取中位数 68. 注意:停牌的股票会出现在当天的数据中,所以需要特殊处理。 69. 日期转换函数:由于浮点数精度问题,我们使用之前的方:先转为double再四舍五入为整数。 70. 代码实现: 由于代码较长,我们只写出核心部分,并省略错误处理等细节。 步骤: 1. 获取通达信安装路径(注册表) 2. 遍历两个市场的day目录,获取股票列表(保存到一个全局vector中,元素为pair<市场, 股票代码>) 3. 在TDXDLL3函数中,根据传入的日期数组,计算每个日期的中位数(使用存) 注意:我们并需要一开始就获取所有股票列表,可以在第一次需要计算时再获取。 具体代码结构如下:</think>### 通达信DLL开发:基于VIPOC数据计算日涨幅中位数 #### 一、DLL核心实现代码 ```cpp #include <Windows.h> #include <vector> #include <algorithm> #include <map> #include <string> #include <fstream> #include <sstream> #include <cmath> // 全局数据存 static std::map<DWORD, float> g_medianCache; // 日期->中位数映射 static std::map<std::string, std::map<DWORD, float>> g_stockData; // 股票数据存[代码][日期]=收盘价 // 浮点日期转整数日期(解决精度问题) DWORD FloatToDate(float fDate) { double dDate = static_cast<double>(fDate); return static_cast<DWORD>(std::round(dDate)); } // 获取通达信VIPOC数据路径 bool GetTdxDataPath(char* path, DWORD size) { HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\TdxW.exe", 0, KEY_READ, &hKey) != ERROR_SUCCESS) { return false; } DWORD type = REG_SZ; if (RegQueryValueEx(hKey, "Path", NULL, &type, (LPBYTE)path, &size) != ERROR_SUCCESS) { RegCloseKey(hKey); return false; } RegCloseKey(hKey); strcat_s(path, size, "\\vipdoc\\"); return true; } // 加载单只股票日线数据 void LoadStockData(const char* market, const char* code) { char path[MAX_PATH]; if (!GetTdxDataPath(path, MAX_PATH)) return; strcat_s(path, MAX_PATH, market); strcat_s(path, MAX_PATH, "\\lday\\"); strcat_s(path, MAX_PATH, code); strcat_s(path, MAX_PATH, ".day"); std::ifstream file(path, std::ios::binary); if (!file) return; struct DayData { DWORD date; DWORD open; DWORD high; DWORD low; DWORD close; DWORD amount; DWORD vol; DWORD reserved; }; std::string fullCode = std::string(market) + code; g_stockData[fullCode] = {}; DayData data; while (file.read(reinterpret_cast<char*>(&data), sizeof(data))) { g_stockData[fullCode][data.date] = static_cast<float>(data.close) / 1000.0f; } } // 计算指定日期的涨幅中位数 float CalculateMedian(DWORD date) { std::vector<float> changes; // 遍历所有股票 for (auto& stock : g_stockData) { auto it = stock.second.find(date); if (it == stock.second.end()) continue; // 查找前一个交易日 auto prevIt = stock.second.lower_bound(date); if (prevIt == stock.second.begin()) continue; --prevIt; // 计算涨跌幅 float prevClose = prevIt->second; float currClose = it->second; if (prevClose > 0) { float change = (currClose - prevClose) / prevClose * 100.0f; changes.push_back(change); } } if (changes.empty()) return 0.0f; // 计算中位数 size_t n = changes.size(); std::sort(changes.begin(), changes.end()); if (n % 2 == 0) { return (changes[n/2 - 1] + changes[n/2]) / 2.0f; } else { return changes[n/2]; } } // 通达信DLL主函数 extern "C" __declspec(dllexport) void __stdcall TDXDLL3( int DataLen, // K线数量 float* pfOUT, // 输出数组 float* pfINa, // 输入1: DATE数组 float* pfINb, // 输入2: 刷新标志 float* pfINc // 输入3: 保留 ) { // 参数安全检查 if (DataLen <= 0 || !pfOUT || !pfINa) return; // 初始化股票数据(首次运行时) static bool initialized = false; if (!initialized) { const char* markets[] = {"sh", "sz"}; const char* stocks[] = {"600000", "000001", ...}; // 实际应遍历所有股票 for (auto& market : markets) { for (auto& stock : stocks) { LoadStockData(market, stock); } } initialized = true; } // 处理强制刷新 bool refresh = (pfINb && pfINb[0] != 0.0f); if (refresh) g_medianCache.clear(); // 逐K线处理 for (int i = 0; i < DataLen; ++i) { DWORD dwDate = FloatToDate(pfINa[i]); // 检查存 auto cacheIt = g_medianCache.find(dwDate); if (cacheIt != g_medianCache.end()) { pfOUT[i] = cacheIt->second; continue; } // 计算并存结果 float median = CalculateMedian(dwDate); g_medianCache[dwDate] = median; pfOUT[i] = median; } } ``` #### 二、关键技术说明 1. **数据精度处理** - 使用双精度转换解决浮点日期精度问题: $$ \text{DATE}_{\text{int}} = \left\lfloor \text{DATE}_{\text{float}} \times 10^4 + 0.5 \right\rfloor $$ - 收盘价精度转换:`价格 = 原始值 / 1000.0f` 2. **VIPOC数据结构** ```mermaid graph LR A[通达信安装目录] --> B[vipdoc] B --> C[sh/lday] --> D[sh600000.day] B --> E[sz/lday] --> F[sz000001.day] ``` 3. **中位数计算算** - 时间复杂度:$O(n \log n)$(主要来自排序) - 空间复杂度:$O(n)$(存储涨跌幅数据) - 中位数公式: $$ \text{Median} = \begin{cases} \dfrac{x_{\frac{n}{2}} + x_{\frac{n}{2}+1}}{2} & \text{if } n \text{ even} \\ x_{\lceil \frac{n}{2} \rceil} & \text{if } n \text{ odd} \end{cases} $$ 4. **存机制** - 双级存设计: - 股票数据存:减少文件IO - 结果存:避免重复计算 - LRU淘汰策略(可扩展实现) #### 三、部署说明 1. **编译DLL** ```bash g++ -shared -o MarketMedian.dll -I"C:\TdxW" MarketMedian.cpp ``` 2. **通达信配置** 1. 将DLL放入`T0002\dlls`目录 2. 公式调用: ```javascript DATE_PARAM := DATE; REFRESH_FLAG := 0; MEDIAN := TDXDLL3(1, DATE_PARAM, REFRESH_FLAG, 0); ``` 3. **文件结构要求** ``` C:\TdxW ├── T0002 │ └── dlls │ └── MarketMedian.dll └── vipdoc ├── sh │ └── lday └── sz └── lday ``` #### 四、性能优化建议 1. **增量数据加载** ```cpp // 只加载需要的日期范围 void LoadPartialData(const char* code, DWORD startDate, DWORD endDate) { // 实现日期范围过滤 } ``` 2. **多线程处理** ```cpp #include <thread> std::vector<std::thread> workers; for (auto& stock : stocks) { workers.emplace_back([&]{ LoadStockData(market, stock); }); } for (auto& t : workers) t.join(); ``` 3. **内存管理** ```cpp // 设置存上限 const size_t MAX_CACHE_SIZE = 50000; if (g_stockData.size() > MAX_CACHE_SIZE) { g_stockData.clear(); } ``` #### 五、实际应用示例 ```javascript // 通达信公式:市场温度计 MEDIAN:TDXDLL3(1, DATE, 0, 0); STICKLINE(MEDIAN>0, 0, MEDIAN, 3, 0), COLORRED; STICKLINE(MEDIAN<0, 0, MEDIAN, 3, 0), COLORGREEN; DRAWKLINE(H, O, L, C); DRAWTEXT_FIX(1, 0.02, 0.05, 0, '市场中位数:'+NUMTOSTR(MEDIAN, 1)+'%'), COLORWHITE; ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值