├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cache ├── cache.go ├── cache_code.go ├── cache_code_test.go ├── cache_filename.go ├── cache_filepath.go ├── cache_others.go ├── cache_test.go ├── cache_trader.go ├── data.go ├── data_action.go ├── data_adapter.go ├── data_adapter_test.go ├── data_increase.go ├── data_summary.go ├── datetime.go ├── datetime_test.go └── scoreboard.go ├── command ├── app.go ├── app_test.go ├── command_backtest.go ├── command_backtesting.go ├── command_bestip.go ├── command_conf.go ├── command_flag.go ├── command_print.go ├── command_repair.go ├── command_rules.go ├── command_safes.go ├── command_service.go ├── command_tools.go ├── command_tracker.go ├── command_update.go ├── command_version.go ├── daemon_darwin.go ├── daemon_dev_darwin.go ├── daemon_linux.go └── daemon_windows.go ├── config ├── config.go ├── config_crontab.go ├── config_data.go ├── config_profile.go ├── config_rules.go ├── config_runtime.go ├── config_strategy.go ├── config_test.go ├── config_trader.go ├── miniqmt.go ├── range.go ├── range_number.go ├── range_session.go ├── range_session_test.go ├── range_test.go ├── range_time.go ├── resources │ └── quant1x.yaml └── rule_consts.go ├── datasource ├── adapter.go ├── base │ ├── tdx_fundflow.go │ ├── tdx_fundflow_test.go │ ├── tdx_kline.go │ ├── tdx_kline_basic.go │ ├── tdx_kline_basic_test.go │ ├── tdx_kline_test.go │ ├── tdx_minutes.go │ ├── tdx_minutes_test.go │ ├── tdx_realtime.go │ ├── tdx_realtime_test.go │ ├── tdx_transaction.go │ ├── tdx_transaction_test.go │ ├── tdx_xdxr.go │ ├── tdx_xdxr.md │ ├── tdx_xdxr_test.go │ ├── tdx_zxg.go │ └── tdx_zxg_test.go ├── dfcf │ ├── capital.go │ ├── capital_test.go │ ├── dfcf.go │ ├── finance.go │ ├── finance_analysis.go │ ├── finance_analysis_test.go │ ├── finance_test.go │ ├── financial_reports.go │ ├── financial_reports_test.go │ ├── fundflow.go │ ├── fundflow_test.go │ ├── longhubang.go │ ├── longhubang_test.go │ ├── margin_trading.go │ ├── margin_trading_test.go │ ├── notices.go │ ├── notices_test.go │ ├── shareholder.go │ ├── shareholder_stock.go │ ├── shareholder_stock_test.go │ └── shareholder_test.go └── tdxweb │ ├── margintrading.go │ ├── margintrading_test.go │ ├── safetyscore.go │ └── safetyscore_test.go ├── docs ├── README.md ├── quant-am.puml ├── quant-pm.puml ├── quant-realtime.puml ├── stock.puml └── strategy.puml ├── factors ├── adapter.go ├── base_data.go ├── cache1d.go ├── chips.fbs ├── chips.proto ├── data_adjust.go ├── data_adjust_test.go ├── dataset.go ├── dataset_chip.go ├── dataset_chip_test.go ├── dataset_features.go ├── dataset_kline.go ├── dataset_minutes.go ├── dataset_report.go ├── dataset_report_preview.go ├── dataset_trans.go ├── dataset_trans_count.go ├── dataset_trans_test.go ├── dataset_wide.go ├── dataset_wide_rotation.go ├── dataset_wide_test.go ├── dataset_xdxr.go ├── doc.go ├── feature.go ├── feature_aggregation.go ├── feature_aggregation_test.go ├── feature_box.go ├── feature_box_breaks.go ├── feature_box_breaks.tdx ├── feature_box_breaks_test.go ├── feature_box_dkqs.go ├── feature_box_dkqs.tdx ├── feature_box_madx.go ├── feature_box_madx.tdx ├── feature_box_qsfz.go ├── feature_box_qsfz.tdx ├── feature_box_test.go ├── feature_f10.go ├── feature_f10_base.go ├── feature_f10_capital.go ├── feature_f10_notice.go ├── feature_f10_reports.go ├── feature_f10_shareholder.go ├── feature_f10_subnew.go ├── feature_f10_test.go ├── feature_f10_utils.go ├── feature_history.go ├── feature_history_test.go ├── feature_ism.go ├── feature_ism.tdx ├── feature_ism_test.go ├── feature_misc.go ├── feature_misc_base.go ├── feature_misc_base_test.go ├── feature_misc_bdl.tdx ├── feature_misc_kline_shape.go ├── feature_misc_kline_shape_test.go ├── feature_misc_qd.tdx ├── feature_misc_rzrq.go ├── feature_misc_test.go ├── feature_no1.go ├── feature_rzrq.go ├── feature_rzrq_test.go ├── feature_test.go ├── feature_utils.go ├── kline.go ├── pb │ └── chips.pb.go └── quote_snapshot.go ├── go.mod ├── go.sum ├── labs ├── bitmap.go ├── bitmap_test.go ├── reflect.go └── reflect_test.go ├── main.go ├── market ├── doc.go ├── ignore.go ├── market.go ├── market_test.go ├── sentiments.go ├── sentiments_test.go ├── shse │ ├── README.md │ ├── sse_index.go │ ├── sse_index_test.go │ ├── sse_stock.go │ └── sse_stock_test.go ├── subnew.go ├── subnew_test.go └── szse │ ├── README.md │ ├── szse_stock.go │ └── szse_stock_test.go ├── models ├── features.go ├── features_test.go ├── snapshot_quote.go ├── snapshot_quote_test.go ├── snapshot_tick.go ├── snapshot_tick_test.go ├── statistics.go ├── strategy.go ├── strategy_result.go ├── voting.go └── voting_test.go ├── permissions └── permission.go ├── publish-compile.sh ├── publish-linux-amd64.sh ├── publish-mac-amd64.sh ├── publish-mac-arm64.sh ├── publish-windows-amd64.sh ├── publish-windows-arm64.sh ├── publish-windows.ps1 ├── publish-windows.py ├── realtime ├── doc.go ├── exponential_moving_average.go ├── moving_average.go ├── moving_average_convergence_divergence.go └── moving_average_convergence_divergence_test.go ├── rules ├── rule.go ├── rule_base.go ├── rule_f10.go ├── rule_f10_test.go ├── rule_impl.go └── rule_test.go ├── services ├── scheduler.go ├── scheduler_test.go ├── services.go ├── services_state.go ├── services_test.go ├── task_global_reset.go ├── task_network.go ├── task_network_test.go ├── task_realtime_kline.go ├── task_sell.go ├── task_sell_orders.go ├── task_sell_orders_test.go ├── task_sell_test.go ├── task_trader.go ├── task_trader_test.go ├── task_update_all.go ├── task_update_all_test.go ├── task_update_misc.go ├── task_update_misc_test.go ├── task_update_rzrq.go ├── task_update_rzrq_test.go └── task_update_snapshot.go ├── storages ├── backtest.go ├── datasets.go ├── datasets_test.go ├── features.go ├── order_state.go ├── order_state_test.go ├── stockpool.go ├── stockpool_merge.go ├── stockpool_merge_test.go ├── stockpool_test.go ├── stockpool_trade.go ├── stockpool_trade_test.go ├── storage.go └── storage_test.go ├── strategies ├── filter.go ├── no1.go └── no1.tdx ├── tools └── tail.go ├── tracker ├── backtesting.go ├── check.go ├── executor.go ├── mod_sector.go ├── mod_sentiment.go ├── mod_sentiment_test.go ├── mod_sort.go ├── mod_stock.go ├── radar.go ├── radar_test.go ├── sector.go ├── sector_filter.go ├── sector_snapshot.go ├── sector_test.go ├── tracker.go ├── tracker_sector.go └── tracker_view.go ├── trader ├── account.go ├── account_test.go ├── constants.go ├── fee.go ├── fee_test.go ├── holdingorders.go ├── holdingorders_test.go ├── orders.go ├── orders_test.go ├── positions.go ├── positions_test.go ├── safes.go ├── safes_test.go ├── trader.go ├── trader_test.go └── trader_test_model.go └── utils ├── brower.go ├── datetime.go ├── gitcmd.go ├── optimize.go ├── paging.go ├── paging_test.go ├── series.go ├── shell.go ├── template_test.go └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/ 8 | .idea/ 9 | 10 | *db/ 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | *.png 20 | *.csv 21 | *.pprof 22 | *.html 23 | *.bin -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/config" 5 | "gitee.com/quant1x/gox/logger" 6 | "gitee.com/quant1x/gox/util/homedir" 7 | "os" 8 | ) 9 | 10 | const ( 11 | // 目录权限 12 | cacheDirMode os.FileMode = 0755 13 | // 文件权限 14 | cacheFileMode os.FileMode = 0644 15 | // 文件替换模式, 会用到os.TRUNC 16 | cacheReplace = os.O_CREATE | os.O_RDWR | os.O_TRUNC 17 | // 更新 18 | cacheUpdate = os.O_CREATE | os.O_WRONLY 19 | ) 20 | 21 | var ( 22 | // 根路径 23 | cacheRootPath = "~/.quant1x" 24 | // cacheLogPath 日志路径 25 | cacheLogPath = cacheRootPath + "/logs" 26 | // var 路径 27 | cacheVariablePath = cacheRootPath + "/var" 28 | ) 29 | 30 | func init() { 31 | initCache() 32 | } 33 | 34 | func initCache() { 35 | // 加载配置文件 36 | tmpConfig, found := config.LoadConfig() 37 | // 搜索配置文件 38 | baseDir := tmpConfig.BaseDir 39 | if len(baseDir) > 0 { 40 | __path, err := homedir.Expand(baseDir) 41 | if err == nil { 42 | baseDir = __path 43 | } else { 44 | logger.Fatalf("%+v", err) 45 | } 46 | } else { 47 | baseDir = cacheRootPath 48 | } 49 | // 校验配置文件的路径 50 | __path, err := homedir.Expand(baseDir) 51 | if err != nil { 52 | logger.Fatalf("%+v", err) 53 | } 54 | cacheRootPath = __path 55 | // 创建根路径 56 | if err := os.MkdirAll(cacheRootPath, cacheDirMode); err != nil { 57 | logger.Fatalf("%+v", err) 58 | } 59 | // 创建日志路径 60 | cacheLogPath = cacheRootPath + "/logs" 61 | __logsPath, err := homedir.Expand(cacheLogPath) 62 | if err != nil { 63 | logger.Fatalf("%+v", err) 64 | } 65 | cacheLogPath = __logsPath 66 | if err := os.MkdirAll(cacheLogPath, cacheDirMode); err != nil { 67 | logger.Fatalf("%+v", err) 68 | } 69 | logger.InitLogger(cacheLogPath, logger.INFO) 70 | // 创建var路径 71 | cacheVariablePath = cacheRootPath + "/var" 72 | __varPath, err := homedir.Expand(cacheVariablePath) 73 | if err != nil { 74 | logger.Fatalf("%+v", err) 75 | } 76 | cacheVariablePath = __varPath 77 | if err := os.MkdirAll(cacheVariablePath, cacheDirMode); err != nil { 78 | logger.Fatalf("%+v", err) 79 | } 80 | // 检查配置文件并加载配置 81 | if !found { 82 | config.GlobalConfig = config.ReadConfig(GetRootPath()) 83 | } else { 84 | config.GlobalConfig = tmpConfig 85 | } 86 | 87 | // 启动性能分析 88 | config.StartPprof() 89 | initMiniQmt() 90 | } 91 | 92 | // Reset 重置日志记录器 93 | func Reset() { 94 | initCache() 95 | } 96 | 97 | // GetRootPath 获取缓存根路径 98 | func GetRootPath() string { 99 | return cacheRootPath 100 | } 101 | 102 | // GetLoggerPath 获取日志路径 103 | func GetLoggerPath() string { 104 | return cacheLogPath 105 | } 106 | 107 | // GetVariablePath 获取VAR路径 108 | func GetVariablePath() string { 109 | return cacheVariablePath 110 | } 111 | -------------------------------------------------------------------------------- /cache/cache_code.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/exchange" 6 | ) 7 | 8 | // CacheId 通过代码构建目录结构 9 | func CacheId(code string) string { 10 | _, marketName, code := exchange.DetectMarket(code) 11 | cacheId := fmt.Sprintf("%s%s", marketName, code) 12 | return cacheId 13 | } 14 | 15 | // CacheIdPath code从后保留3位, 市场缩写+从头到倒数第3的代码, 确保每个目录只有000~999个代码 16 | func CacheIdPath(code string) string { 17 | N := 3 18 | cacheId := CacheId(code) 19 | length := len(cacheId) 20 | 21 | prefix := cacheId[:length-N] 22 | return fmt.Sprintf("%s/%s", prefix, cacheId) 23 | } 24 | -------------------------------------------------------------------------------- /cache/cache_code_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestCacheIdPath(t *testing.T) { 9 | code := "600072" 10 | code = "000001" 11 | v := CacheIdPath(code) 12 | fmt.Println(v) 13 | } 14 | -------------------------------------------------------------------------------- /cache/cache_filepath.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "path/filepath" 4 | 5 | const ( 6 | cacheMetaPath = "meta" // 元数据缓存路径 7 | cacheDayPath = "day" // 日线路径 8 | cacheMinutePath = "minutes" // 分时路径 9 | cacheInfoPath = "info" // 信息路径 10 | //cacheTickPath = "tick" // tick路径 11 | cacheXdxrPath = "xdxr" // 除权除息路径 12 | cacheWidePath = "wide" // 宽表路径 13 | cacheFinancePath = "finance" // 财务信息路径 14 | cacheSnapshotPath = "snapshot" // 快照数据路径 15 | cacheHoldingPath = "holding" // 流通股东数据路径 16 | cacheFundFlowPath = "fund" // 资金流向 17 | cacheTransPath = "trans" // 成交数据 18 | cacheChipsPath = "chips" // 筹码分布 19 | ) 20 | 21 | // GetMetaPath 元数据路径 22 | func GetMetaPath() string { 23 | return GetRootPath() + "/" + cacheMetaPath 24 | } 25 | 26 | // GetDayPath 历史数据-日线缓存路径 27 | func GetDayPath() string { 28 | return GetRootPath() + "/" + cacheDayPath 29 | } 30 | 31 | // GetMinutePath 分时路径 32 | func GetMinutePath() string { 33 | return GetRootPath() + "/" + cacheMinutePath 34 | } 35 | 36 | // GetWidePath 获取特征路径 37 | func GetWidePath() string { 38 | return GetRootPath() + "/" + cacheWidePath 39 | } 40 | 41 | // GetXdxrPath 除权除息文件存储路径 42 | func GetXdxrPath() string { 43 | return GetRootPath() + "/" + cacheXdxrPath 44 | } 45 | 46 | //// GetTickPath tick数据路径 47 | //func GetTickPath() string { 48 | // return GetRootPath() + "/" + cacheTickPath 49 | //} 50 | 51 | // GetHoldingPath 十大流通股股东数据路径 52 | func GetHoldingPath() string { 53 | return GetRootPath() + "/" + cacheHoldingPath 54 | } 55 | 56 | // GetInfoPath 信息路径 57 | func GetInfoPath() string { 58 | return GetRootPath() + "/" + cacheInfoPath 59 | } 60 | 61 | // GetQuarterlyPath 季报路径 62 | // 63 | // Deprecated: 不推荐 64 | func GetQuarterlyPath() string { 65 | return GetRootPath() + "/" + cacheInfoPath + "q" 66 | } 67 | 68 | // GetSnapshotPath 快照路径 69 | func GetSnapshotPath() string { 70 | return GetRootPath() + "/" + cacheSnapshotPath 71 | } 72 | 73 | // GetFundFlowPath 资金流向目录 74 | func GetFundFlowPath() string { 75 | return GetRootPath() + "/" + cacheFundFlowPath 76 | } 77 | 78 | // GetTransPath 成交数据路径 79 | func GetTransPath() string { 80 | return filepath.Join(GetRootPath(), cacheTransPath) 81 | } 82 | 83 | // GetChipsPath 筹码分布路径 84 | func GetChipsPath() string { 85 | return filepath.Join(GetRootPath(), cacheChipsPath) 86 | } 87 | -------------------------------------------------------------------------------- /cache/cache_others.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | // GetZxgFile 自选股文件路径 4 | func GetZxgFile() string { 5 | return GetRootPath() + "/zxg.csv" 6 | } 7 | -------------------------------------------------------------------------------- /cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFilename(t *testing.T) { 9 | date := "2023-09-28" 10 | code := "sh600105" 11 | filename := QuarterlyReportFilename(code, date) 12 | fmt.Println(filename) 13 | filename = SnapshotFilename(code, date) 14 | fmt.Println(filename) 15 | } 16 | 17 | //// import "github.com/syndtr/goleveldb/leveldb" 18 | //func TestLevelDB(t *testing.T) { 19 | // db, err := leveldb.OpenFile("t1.db", nil) 20 | // if err != nil { 21 | // fmt.Println(err) 22 | // return 23 | // } 24 | // defer db.Close() 25 | // db.Put([]byte("a"), []byte("1"), nil) 26 | //} 27 | -------------------------------------------------------------------------------- /cache/cache_trader.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/config" 6 | "gitee.com/quant1x/gox/api" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | const ( 12 | keyOrdersPath = "qmt" // QMT订单缓存路径 13 | ) 14 | 15 | var ( 16 | qmtOnce sync.Once 17 | qmtOrderPath = "" // QMT订单路径 18 | ) 19 | 20 | // QMT默认路径 21 | func defaultQmtCachePath() string { 22 | path := fmt.Sprintf("%s/%s", GetRootPath(), keyOrdersPath) 23 | return path 24 | } 25 | 26 | func lazyInitQmt() { 27 | traderParameter := &config.GlobalConfig.Trader 28 | traderParameter.OrderPath = strings.TrimSpace(traderParameter.OrderPath) 29 | if len(traderParameter.OrderPath) > 0 && api.CheckFilepath(traderParameter.OrderPath, true) == nil { 30 | // 如果配置了路径且有效 31 | qmtOrderPath = traderParameter.OrderPath 32 | } else { 33 | qmtOrderPath = defaultQmtCachePath() 34 | } 35 | traderParameter.OrderPath = qmtOrderPath 36 | } 37 | 38 | func initMiniQmt() { 39 | qmtOnce.Do(lazyInitQmt) 40 | } 41 | 42 | // GetQmtCachePath QMT订单文件路径 43 | func GetQmtCachePath() string { 44 | qmtOnce.Do(lazyInitQmt) 45 | return qmtOrderPath 46 | } 47 | -------------------------------------------------------------------------------- /cache/data.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "context" 4 | 5 | const ( 6 | KBarIndex = "barIndex" 7 | KLineMin = 120 // K线最少记录数 8 | ) 9 | 10 | // Schema 缓存的概要信息 11 | type Schema interface { 12 | // Kind 数据类型 13 | Kind() Kind 14 | // Owner 提供者 15 | Owner() string 16 | // Key 数据关键词, key与cache落地强关联 17 | Key() string 18 | // Name 特性名称 19 | Name() string 20 | // Usage 控制台参数提示信息, 数据描述(data description) 21 | Usage() string 22 | } 23 | 24 | // Initialization 初始化接口 25 | type Initialization interface { 26 | // Init 初始化, 接受context, 日期作为入参 27 | Init(ctx context.Context, date string) error 28 | } 29 | 30 | // Properties 属性接口 31 | type Properties interface { 32 | // GetDate 日期 33 | GetDate() string 34 | // GetSecurityCode 证券代码 35 | GetSecurityCode() string 36 | } 37 | 38 | // Manifest 提要 39 | type Manifest interface { 40 | Schema 41 | Properties 42 | Initialization 43 | } 44 | 45 | // Validator 验证接口 46 | type Validator interface { 47 | // Check 数据校验 48 | Check(featureDate string) error 49 | } 50 | 51 | // DataFile 基础数据文件接口 52 | type DataFile interface { 53 | // Checkout 捡出指定日期的缓存数据 54 | Checkout(securityCode, date string) 55 | // Filename 缓存文件名 56 | // 接受两个参数 日期和证券代码 57 | // 文件名为空不缓存 58 | Filename(date, securityCode string) string 59 | // Check 数据校验 60 | Check(cacheDate, featureDate string) error 61 | // Print 控制台输出指定日期的数据 62 | //Print(code string, date ...string) 63 | } 64 | 65 | // Swift 快速接口 66 | // 67 | // securityCode 证券代码, 2位交易所缩写+6位数字代码, 例如sh600600, 代表上海市场的青岛啤酒 68 | // cacheDate 缓存日期 69 | // featureDate 特征数据的日期 70 | type Swift interface { 71 | } 72 | 73 | // Future 预备数据的接口 74 | type Future interface { 75 | // Update 更新数据 76 | // whole 是否完整的数据, false是加工成半成品数据, 为了配合Increase 77 | Update(securityCode, cacheDate, featureDate string, whole bool) 78 | // Repair 回补数据 79 | Repair(securityCode, cacheDate, featureDate string, whole bool) 80 | // Increase 增量计算, 用快照增量计算特征 81 | //Increase(securityCode string, snapshot quotes.Snapshot) 82 | //Assign(target *cachel5.CacheAdapter) bool // 赋值 83 | } 84 | 85 | // Operator 缓存操作接口 86 | // 87 | // 数据操作, 包含初始化和拉取两个接口 88 | type Operator interface { 89 | // Pull 拉取数据 90 | Pull(date, securityCode string) Operator 91 | } 92 | -------------------------------------------------------------------------------- /cache/data_action.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | const ( 4 | UseGoroutine = false // 更新和修复数据是否启用协程 5 | ) 6 | 7 | type OpKind int 8 | 9 | const ( 10 | OpUpdate OpKind = iota + 1 // 更新 11 | OpRepair // 修复 12 | OpIncr // 增量 13 | OpBackTest // 回测 14 | ) 15 | 16 | var OpMap = map[OpKind]string{ 17 | OpUpdate: "更新", 18 | OpRepair: "修复", 19 | OpIncr: "增量", 20 | OpBackTest: "回测", 21 | } 22 | -------------------------------------------------------------------------------- /cache/data_adapter_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "testing" 7 | ) 8 | 9 | func TestPlugins(t *testing.T) { 10 | k := Kind(0) 11 | v := k & 1 12 | fmt.Println(v) 13 | k1 := PluginMaskBaseData | 2 14 | v = k1 & PluginMaskBaseData 15 | fmt.Println(v == PluginMaskBaseData) 16 | 17 | list := []uint{3, 1, 2, 5, 4} 18 | slices.Sort(list) 19 | fmt.Println(list) 20 | } 21 | -------------------------------------------------------------------------------- /cache/data_increase.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | // Increase 增量数据计算接口 4 | // 5 | // deprecated: 不推荐 6 | type Increase[T any] interface { 7 | Add(data T) T 8 | } 9 | -------------------------------------------------------------------------------- /cache/data_summary.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | // DataSummary 数据概要 4 | type DataSummary struct { 5 | kind Kind // 类型 6 | key string // 关键字 7 | name string // 名称 8 | owner string // 拥有者 9 | usage string // 用法 10 | } 11 | 12 | func Summary(kind Kind, key, name, owner string, usage ...string) DataSummary { 13 | var description string 14 | if len(usage) > 0 { 15 | description = usage[0] 16 | } 17 | return DataSummary{ 18 | kind: kind, 19 | key: key, 20 | name: name, 21 | owner: owner, 22 | usage: description, 23 | } 24 | } 25 | 26 | func (d DataSummary) Kind() Kind { 27 | return d.kind 28 | } 29 | 30 | func (d DataSummary) Key() string { 31 | return d.key 32 | } 33 | 34 | func (d DataSummary) Name() string { 35 | return d.name 36 | } 37 | 38 | func (d DataSummary) Owner() string { 39 | return d.owner 40 | } 41 | 42 | func (d DataSummary) Usage() string { 43 | return d.usage 44 | } 45 | -------------------------------------------------------------------------------- /cache/datetime.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "gitee.com/quant1x/exchange" 5 | "time" 6 | ) 7 | 8 | const ( 9 | TDX_FORMAT_PROTOCOL_DATE = "20060102" // 通达信协议的日期字符串格式 10 | CACHE_DATE = "20060102" // 缓存日期 11 | INDEX_DATE = "2006-01-02" // 索引日期格式 12 | TDX_DATE = "20060102" // 通达信日期 13 | YearOnly = "2006" // 仅年份 14 | TimeStampMilli = "2006-01-02 15:04:05.000" 15 | TimeStampMicro = "2006-01-02 15:04:05.000000" 16 | TimeStampNano = "2006-01-02 15:04:05.000000000" 17 | ) 18 | 19 | func Today() string { 20 | now := time.Now() 21 | return now.Format(CACHE_DATE) 22 | } 23 | 24 | //// CorrectDate 矫正日期, 统一格式: 20060102 25 | //func CorrectDate(date string) string { 26 | // dt, err := api.ParseTime(date) 27 | // if err != nil { 28 | // return Today() 29 | // } 30 | // date = dt.Format(CACHE_DATE) 31 | // return date 32 | //} 33 | 34 | // CorrectDate 校正日期 35 | func CorrectDate(date string) (cacheDate, resourcesDate string) { 36 | cacheDate = exchange.FixTradeDate(date) 37 | dates := exchange.LastNDate(cacheDate, 1) 38 | if len(dates) == 0 { 39 | // TODO: 存在一定概率的坑, 比如1990-12-19执行 40 | resourcesDate = cacheDate 41 | } else { 42 | resourcesDate = dates[0] 43 | } 44 | cacheDate = exchange.NextTradeDate(resourcesDate) 45 | return 46 | } 47 | 48 | // DefaultCanReadDate 获取默认可以读缓存文件的日期 49 | func DefaultCanReadDate() string { 50 | dateOfReadingData := exchange.GetCurrentDate() 51 | return dateOfReadingData 52 | } 53 | 54 | // DefaultCanUpdateDate 获取默认可以更新缓存文件的日期 55 | func DefaultCanUpdateDate() string { 56 | currentDate := exchange.GetCurrentDate() 57 | dateOfUpdatingData := exchange.NextTradeDate(currentDate) 58 | return dateOfUpdatingData 59 | } 60 | 61 | // DefaultCanUpdateDate 获取默认可以更新缓存文件的日期 62 | func testDefaultCanUpdateDate() string { 63 | dateOfUpdatingData := exchange.GetCurrentlyDay() 64 | return dateOfUpdatingData 65 | } 66 | -------------------------------------------------------------------------------- /cache/datetime_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestCorrectDate(t *testing.T) { 9 | date := "1991-12-19" 10 | c, s := CorrectDate(date) 11 | fmt.Println(c, s) 12 | date = "2023-07-03" 13 | c, s = CorrectDate(date) 14 | fmt.Println(c, s) 15 | date = "2023-07-10" 16 | c, s = CorrectDate(date) 17 | fmt.Println(c, s) 18 | date = "2023-07-09" 19 | c, s = CorrectDate(date) 20 | fmt.Println(c, s) 21 | date = "2023-07-08" 22 | c, s = CorrectDate(date) 23 | fmt.Println(c, s) 24 | date = "2023-07-07" 25 | c, s = CorrectDate(date) 26 | fmt.Println(c, s) 27 | } 28 | -------------------------------------------------------------------------------- /cache/scoreboard.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // ScoreBoard 记分牌, 线程安全 10 | type ScoreBoard struct { 11 | m sync.Mutex 12 | AdapterMetric 13 | } 14 | 15 | // AdapterMetric 适配器性能指标 16 | type AdapterMetric struct { 17 | Name string `name:"name"` // 名称 18 | Kind Kind `name:"kind"` // 类型 19 | Count int `name:"count"` // 总数 20 | Passed int `name:"passed"` // 通过检测数 21 | Max time.Duration `name:"max"` // 最大值 22 | Min time.Duration `name:"min"` // 最小值 23 | CrossTime time.Duration `name:"cross_time"` // 总耗时 24 | Speed float64 `name:"speed"` // 速度 25 | } 26 | 27 | func (this *ScoreBoard) From(adapter DataAdapter) { 28 | this.Name = adapter.Name() 29 | this.Kind = adapter.Kind() 30 | } 31 | 32 | func (this *ScoreBoard) Add(delta int, take time.Duration, pass ...bool) { 33 | this.m.Lock() 34 | defer this.m.Unlock() 35 | this.Count = this.Count + delta 36 | this.CrossTime += take 37 | if this.Min == 0 || this.Min > take { 38 | this.Min = take 39 | } 40 | if this.Max == 0 || this.Max < take { 41 | this.Max = take 42 | } 43 | this.Speed = float64(this.Count) / this.CrossTime.Seconds() 44 | passed := false 45 | if len(pass) > 0 { 46 | passed = pass[0] 47 | } 48 | if passed { 49 | this.Passed++ 50 | } 51 | } 52 | 53 | func (this *ScoreBoard) String() string { 54 | s := fmt.Sprintf("name: %s, kind: %d, total: %d, crosstime: %s, max: %d, min: %d, speed: %f", this.Name, this.Kind, this.Count, this.CrossTime, this.Max, this.Min, this.Speed) 55 | return s 56 | } 57 | 58 | func (this *ScoreBoard) Metric() AdapterMetric { 59 | return this.AdapterMetric 60 | } 61 | -------------------------------------------------------------------------------- /command/app_test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestCommand(t *testing.T) { 10 | fmt.Println("Quant1X-" + FirstUpper("stock")) 11 | err := errors.New("invalid argument \"f10\" for \"--features\" flag: strconv.ParseBool: parsing \"f10\": invalid syntax") 12 | parseFlagError(err) 13 | } 14 | 15 | func TestUpdateApplicationName(t *testing.T) { 16 | type args struct { 17 | name string 18 | } 19 | tests := []struct { 20 | name string 21 | args args 22 | }{ 23 | { 24 | name: "test", 25 | args: args{ 26 | name: "test", 27 | }, 28 | }, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | UpdateApplicationName(tt.args.name) 33 | }) 34 | } 35 | } 36 | 37 | func TestInitCommands(t *testing.T) { 38 | InitCommands() 39 | 40 | } 41 | -------------------------------------------------------------------------------- /command/command_backtesting.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/models" 5 | "gitee.com/quant1x/engine/tracker" 6 | "gitee.com/quant1x/exchange" 7 | cmder "github.com/spf13/cobra" 8 | "strings" 9 | ) 10 | 11 | var ( 12 | strategyCode uint64 // 策略ID 13 | securityCode string // 证券代码 14 | topN int // 统计前N 15 | days int // 统计多少天 16 | date string // 回测日期 17 | ) 18 | 19 | // CmdBackTesting 回测 20 | var CmdBackTesting = &cmder.Command{ 21 | Use: "backtesting", 22 | Short: "回测", 23 | Run: func(cmd *cmder.Command, args []string) { 24 | securityCode = strings.TrimSpace(securityCode) 25 | securityCode = exchange.CorrectSecurityCode(securityCode) 26 | if len(securityCode) > 0 { 27 | tracker.CheckStrategy(strategyCode, securityCode, date) 28 | } else { 29 | tracker.BackTesting(strategyCode, days, topN) 30 | } 31 | }, 32 | } 33 | 34 | func initBackTesting() { 35 | CmdBackTesting.Flags().IntVar(&days, "count", 0, "统计多少天") 36 | CmdBackTesting.Flags().IntVar(&topN, "top", models.AllStockTopN(), "输出前排几名") 37 | CmdBackTesting.Flags().Uint64Var(&strategyCode, "strategy", 0, "策略ID") 38 | CmdBackTesting.Flags().StringVar(&securityCode, "code", "", "证券代码") 39 | CmdBackTesting.Flags().StringVar(&date, "date", "", "日期") 40 | } 41 | -------------------------------------------------------------------------------- /command/command_bestip.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "gitee.com/quant1x/gotdx/quotes" 5 | cmder "github.com/spf13/cobra" 6 | ) 7 | 8 | // CmdBestIP 检测服务器地址 9 | var CmdBestIP = &cmder.Command{ 10 | Use: "bestip", 11 | Short: "检测服务器网速", 12 | Run: func(cmd *cmder.Command, args []string) { 13 | quotes.BestIP() 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /command/command_conf.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/config" 6 | "gitee.com/quant1x/gox/api" 7 | "gitee.com/quant1x/pkg/yaml" 8 | cmder "github.com/spf13/cobra" 9 | ) 10 | 11 | // CmdConfig 显示配置信息 12 | var CmdConfig = &cmder.Command{ 13 | Use: "config", 14 | Short: "显示配置信息", 15 | Run: func(cmd *cmder.Command, args []string) { 16 | data, err := yaml.Marshal(config.GlobalConfig) 17 | if err != nil { 18 | fmt.Println(err) 19 | } else { 20 | fmt.Println(api.Bytes2String(data)) 21 | } 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /command/command_rules.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/rules" 5 | cmder "github.com/spf13/cobra" 6 | ) 7 | 8 | // CmdRules 规则 9 | var CmdRules = &cmder.Command{ 10 | Use: "rules", 11 | Short: "规则", 12 | Args: func(cmd *cmder.Command, args []string) error { 13 | return nil 14 | }, 15 | //ValidArgsFunction: func(cmd *cmder.Command, args []string, toComplete string) ([]string, cmder.ShellCompDirective) { 16 | // 17 | //}, 18 | PreRun: func(cmd *cmder.Command, args []string) { 19 | 20 | }, 21 | Run: func(cmd *cmder.Command, args []string) { 22 | rules.PrintRuleList() 23 | }, 24 | } 25 | 26 | func initRules() { 27 | //CmdRules.SetFlagErrorFunc(func(cmd *cmder.Command, err error) error { 28 | // args := os.Args[1:] 29 | // cmd_, flags, err := cmd.Parent().Find(args) 30 | // fmt.Println(cmd_, flags, err) 31 | // return nil 32 | //}) 33 | } 34 | -------------------------------------------------------------------------------- /command/command_safes.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/trader" 6 | cmder "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | safesList bool // 输出列表 11 | safesSecureType int = 0 // 名单类型 12 | safesSecurityCode string // 证券代码 13 | ) 14 | 15 | var ( 16 | // CmdSafes 安全类-黑白名单 17 | CmdSafes *cmder.Command = nil 18 | ) 19 | 20 | func initSafes() { 21 | CmdSafes = &cmder.Command{ 22 | Use: "safes", 23 | Example: Application + " safes --code=sh000001 --type=1", 24 | Short: "黑白名单", 25 | Run: func(cmd *cmder.Command, args []string) { 26 | if safesList { 27 | trader.GetBlackAndWhiteList() 28 | } else { 29 | if len(safesSecurityCode) == 0 { 30 | fmt.Println("证券代码不能为空") 31 | return 32 | } 33 | trader.AddCodeToBlackList(safesSecurityCode, trader.SecureType(safesSecureType)) 34 | } 35 | }, 36 | } 37 | CmdSafes.Flags().BoolVar(&safesList, "list", false, "显示黑白名单列表") 38 | CmdSafes.Flags().StringVar(&safesSecurityCode, "code", "", "证券代码") 39 | CmdSafes.Flags().IntVar(&safesSecureType, "type", 0, trader.UsageOfSecureType()) 40 | } 41 | -------------------------------------------------------------------------------- /command/command_tools.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/tools" 6 | "gitee.com/quant1x/num" 7 | "gitee.com/quant1x/pkg/tools/tail" 8 | cmder "github.com/spf13/cobra" 9 | "slices" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | toolsCommand = "tool" 15 | toolsDescription = "工具" 16 | ) 17 | 18 | var ( 19 | CmdTools *cmder.Command = nil // 工具集合 20 | cmdToolTail *cmder.Command = nil // tail工具 21 | ) 22 | 23 | func initTools() { 24 | CmdTools = &cmder.Command{ 25 | Use: toolsCommand, 26 | Example: Application + " " + toolsCommand + " --help", 27 | Short: toolsDescription, 28 | Run: func(cmd *cmder.Command, args []string) { 29 | 30 | }, 31 | } 32 | toolsInitTail() 33 | CmdTools.AddCommand(cmdToolTail) 34 | } 35 | 36 | func toolsInitTail() { 37 | var taiConfig tail.Config 38 | var n int 39 | cmdToolTail = &cmder.Command{ 40 | Use: "tail", 41 | Example: Application + " tool tail -f runtime.log", 42 | Short: "文件末端阅览", 43 | DisableFlagParsing: true, 44 | Run: func(cmd *cmder.Command, args []string) { 45 | for i := 0; i < len(args); i++ { 46 | args[i] = strings.TrimSpace(args[i]) 47 | } 48 | if len(args) != 2 || slices.Contains(args, "--help") || slices.Contains(args, "-h") { 49 | _ = cmd.Usage() 50 | return 51 | } 52 | if args[0] == "-f" { 53 | taiConfig.Follow = true 54 | taiConfig.Poll = true 55 | } else if args[0][:1] == "-" { 56 | n = int(num.AnyToInt64(args[0])) 57 | n = -n 58 | } 59 | name := args[1] 60 | if n > 0 { 61 | tools.TailFileWithNumber(name, taiConfig, n) 62 | } else { 63 | done := make(chan bool) 64 | tools.TailFile(name, taiConfig, done) 65 | <-done 66 | } 67 | }, 68 | //PreRunE: func(cmd *cmder.Command, args []string) error { 69 | // //fmt.Println(args) 70 | // if slices.Contains(args, "--help") || slices.Contains(args, "-h") { 71 | // cmd.Usage() 72 | // } 73 | // return nil 74 | //}, 75 | } 76 | cmdToolTail.SetUsageFunc(func(command *cmder.Command) error { 77 | fmt.Println("Usage:\n" + Application + " tool tail [-f] [-n #] [file ...]") 78 | return nil 79 | }) 80 | cmdToolTail.Flags().BoolVarP(&taiConfig.Follow, commandDefaultLongFlag, "f", false, "一直等待新数据添加到文件") 81 | } 82 | -------------------------------------------------------------------------------- /command/command_tracker.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/models" 6 | "gitee.com/quant1x/engine/permissions" 7 | "gitee.com/quant1x/engine/tracker" 8 | "gitee.com/quant1x/gox/api" 9 | "gitee.com/quant1x/gox/logger" 10 | cmder "github.com/spf13/cobra" 11 | "strings" 12 | ) 13 | 14 | const ( 15 | trackerCommand = "tracker" 16 | trackerDescription = "实时跟踪" 17 | ) 18 | 19 | var ( 20 | trackerStrategyCodes = "1" // 策略编号 21 | CmdTracker *cmder.Command = nil // 实时跟踪 22 | ) 23 | 24 | func initTracker() { 25 | CmdTracker = &cmder.Command{ 26 | Use: trackerCommand, 27 | Example: Application + " " + trackerCommand + " --no=1", 28 | //Args: cobra.MinimumNArgs(0), 29 | Args: func(cmd *cmder.Command, args []string) error { 30 | return nil 31 | }, 32 | Short: trackerDescription, 33 | Long: trackerDescription, 34 | Run: func(cmd *cmder.Command, args []string) { 35 | var strategyCodes []uint64 36 | array := strings.Split(trackerStrategyCodes, ",") 37 | for _, strategyNumber := range array { 38 | strategyNumber := strings.TrimSpace(strategyNumber) 39 | code := api.ParseUint(strategyNumber) 40 | // 1. 确定策略是否存在 41 | medel, err := models.CheckoutStrategy(code) 42 | if err != nil { 43 | fmt.Printf("策略编号%d, 不存在\n", code) 44 | logger.Errorf("策略编号%d, 不存在", code) 45 | continue 46 | } 47 | // 2. 确定策略是否有权限 48 | err = permissions.CheckPermission(medel) 49 | if err != nil { 50 | fmt.Printf("策略编号%d, 权限验证失败: %+v\n", code, err) 51 | logger.Errorf("策略编号%d, 权限验证失败: %+v", code, err) 52 | continue 53 | } 54 | strategyCodes = append(strategyCodes, code) 55 | } 56 | if len(strategyCodes) == 0 { 57 | fmt.Println("没有有效的策略编号, 实时扫描结束") 58 | logger.Info("没有有效的策略编号, 实时扫描结束") 59 | return 60 | } 61 | tracker.Tracker(strategyCodes...) 62 | }, 63 | } 64 | 65 | CmdTracker.Flags().StringVar(&trackerStrategyCodes, "no", trackerStrategyCodes, "策略编号, 多个用逗号分隔") 66 | } 67 | -------------------------------------------------------------------------------- /command/command_version.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | cmder "github.com/spf13/cobra" 6 | ) 7 | 8 | // CmdVersion 版本 9 | var CmdVersion = &cmder.Command{ 10 | Use: "version", 11 | Short: "显示版本号", 12 | Run: func(cmd *cmder.Command, args []string) { 13 | fmt.Println(MinVersion) 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /command/daemon_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin && !dev 2 | // +build darwin,!dev 3 | 4 | package command 5 | 6 | var propertyList = ` 7 | 8 | 9 | 10 | KeepAlive 11 | 12 | Label 13 | {{.Name}} 14 | ProgramArguments 15 | 16 | {{.Path}} 17 | {{range .Args}}{{.}} 18 | {{end}} 19 | 20 | RunAtLoad 21 | 22 | WorkingDirectory 23 | ${ROOT_PATH} 24 | 25 | 26 | ` 27 | -------------------------------------------------------------------------------- /command/daemon_dev_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin && dev 2 | // +build darwin,dev 3 | 4 | package command 5 | 6 | var propertyList = ` 7 | 8 | 9 | 10 | KeepAlive 11 | 12 | Label 13 | {{.Name}} 14 | ProgramArguments 15 | 16 | {{.Path}} 17 | {{range .Args}}{{.}} 18 | {{end}} 19 | 20 | RunAtLoad 21 | 22 | WorkingDirectory 23 | ${ROOT_PATH} 24 | StandardErrorPath 25 | ${LOG_PATH}/{{.Name}}.err 26 | StandardOutPath 27 | ${LOG_PATH}/{{.Name}}.log 28 | 29 | 30 | ` 31 | -------------------------------------------------------------------------------- /command/daemon_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package command 5 | 6 | var propertyList = "" 7 | -------------------------------------------------------------------------------- /command/daemon_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package command 5 | 6 | var propertyList = "" 7 | -------------------------------------------------------------------------------- /config/config_crontab.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // JobParameter 定时任务配置 4 | type JobParameter struct { 5 | //Name string `yaml:"name" default:""` // 任务名称 6 | Trigger string `yaml:"trigger" default:""` // 触发条件 7 | Enable bool `yaml:"enable" default:"true"` // 任务是否有效 8 | } 9 | 10 | // CrontabConfig 获取定时任务配置 11 | func CrontabConfig() map[string]JobParameter { 12 | return GlobalConfig.Runtime.Crontab 13 | } 14 | 15 | // GetJobParameter 获取计划执行任务 16 | func GetJobParameter(name string) *JobParameter { 17 | mapJob := CrontabConfig() 18 | if len(mapJob) == 0 { 19 | return nil 20 | } 21 | v, ok := mapJob[name] 22 | if ok { 23 | return &v 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /config/config_profile.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gox/logger" 6 | "net/http" 7 | _ "net/http/pprof" 8 | ) 9 | 10 | type PprofParameter struct { 11 | Enable bool `yaml:"enable" default:"false"` // 是否开启go tool pprof 12 | Port int `yaml:"port" default:"6060"` // pprof web端口 13 | } 14 | 15 | // PprofEnable 获取配置中pprof开关 16 | func PprofEnable() bool { 17 | return GlobalConfig.Runtime.Pprof.Enable 18 | } 19 | 20 | // StartPprof 启动性能分析工具 21 | func StartPprof() { 22 | if !PprofEnable() { 23 | return 24 | } 25 | go func() { 26 | addr := fmt.Sprintf("localhost:%d", GlobalConfig.Runtime.Pprof.Port) 27 | err := http.ListenAndServe(addr, nil) 28 | logger.Info("启动pprof性能分析工具", err) 29 | }() 30 | } 31 | -------------------------------------------------------------------------------- /config/config_runtime.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // RuntimeParameter 运行时配置参数 4 | type RuntimeParameter struct { 5 | Pprof PprofParameter `name:"性能分析" yaml:"pprof"` 6 | Debug bool `name:"业务调试开关" yaml:"debug" default:"false"` 7 | Crontab map[string]JobParameter `name:"定时任务" yaml:"crontab" default:"{}"` 8 | } 9 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "gitee.com/quant1x/gotdx/securities" 8 | ) 9 | 10 | func TestConfig(t *testing.T) { 11 | config, found := LoadConfig() 12 | fmt.Println(found) 13 | fmt.Println(config) 14 | strategyCode := uint64(82) 15 | v := GetStrategyParameterByCode(strategyCode) 16 | fmt.Println(v) 17 | } 18 | 19 | func TestBlocks(t *testing.T) { 20 | sectorCode := "sh880884" 21 | blk := securities.GetBlockInfo(sectorCode) 22 | fmt.Println(len(blk.ConstituentStocks)) 23 | } 24 | -------------------------------------------------------------------------------- /config/miniqmt.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "fmt" 4 | 5 | // QmtStrategyNameFromId 通过策略ID返回用于在QMT系统中表示的string类型的策略名称 6 | func QmtStrategyNameFromId(strategyCode uint64) string { 7 | return fmt.Sprintf("S%d", strategyCode) 8 | } 9 | -------------------------------------------------------------------------------- /config/range.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "gitee.com/quant1x/gox/exception" 5 | "gitee.com/quant1x/gox/logger" 6 | "gitee.com/quant1x/num" 7 | "regexp" 8 | "strings" 9 | _ "unsafe" 10 | ) 11 | 12 | // 正则表达式 13 | var ( 14 | // 值范围正则表达式 15 | valueRangePattern = "[~]\\s*" 16 | valueRangeRegexp = regexp.MustCompile(valueRangePattern) 17 | 18 | // 数组正则表达式 19 | arrayPattern = "[,]\\s*" 20 | arrayRegexp = regexp.MustCompile(arrayPattern) 21 | ) 22 | 23 | // 错误信息 24 | var ( 25 | errnoConfig = 0 26 | ErrRangeFormat = exception.New(errnoConfig+0, "数值范围格式错误") 27 | ) 28 | 29 | type ValueType interface { 30 | ~int | ~float64 | ~string 31 | } 32 | 33 | func ParseRange[T ValueType](text string) ValueRange[T] { 34 | text = strings.TrimSpace(text) 35 | arr := valueRangeRegexp.Split(text, -1) 36 | if len(arr) != 2 { 37 | logger.Fatalf("text=%s, %+v", text, ErrTimeFormat) 38 | } 39 | var begin, end T 40 | begin = num.GenericParse[T](strings.TrimSpace(arr[0])) 41 | end = num.GenericParse[T](strings.TrimSpace(arr[1])) 42 | if begin > end { 43 | begin, end = end, begin 44 | } 45 | r := ValueRange[T]{ 46 | begin: begin, 47 | end: end, 48 | } 49 | return r 50 | } 51 | 52 | // ValueRange 数值范围 53 | type ValueRange[T ValueType] struct { 54 | begin T // 最小值 55 | end T // 最大值 56 | } 57 | 58 | // In 检查是否包含在范围内 59 | func (r ValueRange[T]) In(v T) bool { 60 | if v < r.begin || v > r.end { 61 | return false 62 | } 63 | return true 64 | } 65 | -------------------------------------------------------------------------------- /config/range_session_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gox/api" 6 | "gitee.com/quant1x/pkg/yaml" 7 | "testing" 8 | ) 9 | 10 | func TestTimeRange(t *testing.T) { 11 | text := " 09:30:00 ~ 14:56:30 " 12 | text = " 14:56:30 - 09:30:00 " 13 | var tr TimeRange 14 | tr.Parse(text) 15 | fmt.Println(tr) 16 | text = "09:15:00~09:26:59,09:15:00~09:19:59,09:25:00~11:29:59,13:00:00~14:59:59,09:00:00~09:14:59" 17 | var ts TradingSession 18 | ts.Parse(text) 19 | fmt.Println(ts) 20 | fmt.Println(ts.IsTrading()) 21 | } 22 | 23 | type tt struct { 24 | Session TimeRange `yaml:"session"` 25 | ChangeRate float64 `yaml:"change_rate" default:"0.01"` 26 | } 27 | 28 | func TestTimeRangeWithParse(t *testing.T) { 29 | text := `time: "09:50:00~09:50:59,10:50:00~10:50:59"` 30 | text = ` 31 | session: "09:50:00~09:50:59" 32 | name: "buyiwang" 33 | ` 34 | bytes := api.String2Bytes(text) 35 | v := tt{} 36 | err := yaml.Unmarshal(bytes, &v) 37 | fmt.Println(err, v) 38 | } 39 | -------------------------------------------------------------------------------- /config/range_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | ) 8 | 9 | func TestParseRange(t *testing.T) { 10 | v := math.SmallestNonzeroFloat64 11 | fmt.Println(v) 12 | v = math.MaxFloat64 13 | fmt.Println(v) 14 | fmt.Println(-10000000000.00 < v) 15 | 16 | rn := NumberRange{} 17 | ok := rn.Validate(100) 18 | fmt.Println(ok) 19 | } 20 | 21 | func TestNumberRange(t *testing.T) { 22 | text := "" 23 | fmt.Println("==>", text) 24 | var r NumberRange 25 | err := r.Parse(text) 26 | fmt.Println(r, err) 27 | fmt.Println(-1000000 < r.Min()) 28 | text = "1" 29 | fmt.Println("==>", text) 30 | err = r.Parse(text) 31 | fmt.Println(r, err) 32 | text = "1~" 33 | fmt.Println("==>", text) 34 | err = r.Parse(text) 35 | fmt.Println(r, err) 36 | text = "~1" 37 | fmt.Println("==>", text) 38 | err = r.Parse(text) 39 | fmt.Println(r, err) 40 | text = "1~2" 41 | fmt.Println("==>", text) 42 | err = r.Parse(text) 43 | fmt.Println(r, err) 44 | text = "1~2~3" 45 | fmt.Println("==>", text) 46 | err = r.Parse(text) 47 | fmt.Println(r, err) 48 | } 49 | -------------------------------------------------------------------------------- /config/range_time.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gox/exception" 6 | "gitee.com/quant1x/pkg/yaml" 7 | "regexp" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // 值范围正则表达式 13 | var ( 14 | stringRangePattern = "[~-]\\s*" 15 | stringRangeRegexp = regexp.MustCompile(stringRangePattern) 16 | ) 17 | 18 | var ( 19 | ErrTimeFormat = exception.New(errnoConfig+1, "时间格式错误") 20 | formatOfTimestamp = time.TimeOnly 21 | ) 22 | 23 | func getTradingTimestamp() string { 24 | now := time.Now() 25 | return now.Format(formatOfTimestamp) 26 | } 27 | 28 | // TimeRange 时间范围 29 | type TimeRange struct { 30 | begin string // 开始时间 31 | end string // 结束时间 32 | } 33 | 34 | func (this TimeRange) String() string { 35 | return fmt.Sprintf("{begin: %s, end: %s}", this.begin, this.end) 36 | } 37 | 38 | func (this TimeRange) v2String() string { 39 | return fmt.Sprintf("%s~%s", this.begin, this.end) 40 | } 41 | 42 | func (this *TimeRange) Parse(text string) error { 43 | text = strings.TrimSpace(text) 44 | arr := stringRangeRegexp.Split(text, -1) 45 | if len(arr) != 2 { 46 | return ErrTimeFormat 47 | } 48 | this.begin = strings.TrimSpace(arr[0]) 49 | this.end = strings.TrimSpace(arr[1]) 50 | if this.begin > this.end { 51 | this.begin, this.end = this.end, this.begin 52 | } 53 | return nil 54 | } 55 | 56 | //// UnmarshalText 设置默认值调用 57 | //func (this *TimeRange) UnmarshalText(text []byte) error { 58 | // //TODO implement me 59 | // panic("implement me") 60 | //} 61 | 62 | // UnmarshalYAML YAML自定义解析 63 | func (this *TimeRange) UnmarshalYAML(node *yaml.Node) error { 64 | var key, value string 65 | if len(node.Content) == 0 { 66 | value = node.Value 67 | } else if len(node.Content) == 2 { 68 | key = node.Content[0].Value 69 | value = node.Content[1].Value 70 | } 71 | _ = key 72 | return this.Parse(value) 73 | } 74 | 75 | func (this *TimeRange) IsTrading(timestamp ...string) bool { 76 | var tm string 77 | if len(timestamp) > 0 { 78 | tm = strings.TrimSpace(timestamp[0]) 79 | } else { 80 | tm = getTradingTimestamp() 81 | } 82 | if tm >= this.begin && tm <= this.end { 83 | return true 84 | } 85 | return false 86 | } 87 | -------------------------------------------------------------------------------- /config/resources/quant1x.yaml: -------------------------------------------------------------------------------- 1 | basedir: ~/.quant1x # 数据路径 2 | trader: # 交易配置 3 | account_id: "888xxxxxxx" # QMT账号 4 | order_path: ~/.quant1x/qmt # 订单路径 5 | proxy_url: http://127.0.0.1:18168/qmt # miniQMT Proxy地址 6 | stamp_duty_rate_for_buy: 0.0000 # 买入印花税率 7 | stamp_duty_rate_for_sell: 0.0010 # 卖出印花税率 8 | transfer_rate: 0.0006 # 过户费 9 | commission_rate: 0.00025 # 佣金率 10 | commission_min: 5.0000 # 佣金最低 11 | position_ratio: 0.5000 # 买入总占比 12 | keep_cash: 10000.00 # 预留备用金 13 | buy_amount_max: 250000.00 # 最大可买金额 14 | buy_amount_min: 1000.00 # 最小可买金额 15 | strategies: 16 | - id: 1 # 策略ID 17 | name: 1号策略 # 策略名称 18 | auto: false # 是否自动交易 19 | flag: tick # 订单类型 20 | time: 09:39:00~14:56:30 # 交易时间段 21 | total: 6 # 可买多少个标的 22 | fee_max: 20000.00 # 可买最大金额 23 | fee_min: 1000.00 # 可买最小金额 24 | sectors: [ "880847","880785","880884","880887","880880" ] # 行业龙头880847, 近期多板880785, 最近异动880884, 户数减少880887, 近期强势 880880 25 | ignore_margin_trading: true # 是否剔除两融 26 | holding_period: 1 # 持股周期, 默认是1天, 即T+1卖出 27 | sell_strategy: 117 # 关联卖出策略 28 | rules: 29 | price: 2.00~30.00 # 股价范围 30 | open_turn_z: 1.50~200.00 # 换手z范围 31 | open_change_rate: -2.00~2.00 # 开盘涨幅 32 | - id: 117 33 | name: 一刀切卖出 34 | auto: false 35 | flag: sell 36 | time: 09:50:00~09:50:99,10:50:00~10:50:99 # 交易时间段 37 | total: 0 # 卖出策略中股票总是为0, 视为全部卖出 38 | runtime: 39 | crontab: 40 | realtime_kline: 41 | enable: false 42 | sell_117: 43 | enable: true 44 | trigger: '@every 1s' 45 | -------------------------------------------------------------------------------- /config/rule_consts.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | TenThousand = 1e4 // 万 5 | Million = 100 * TenThousand // 百万 6 | Billion = 100 * Million // 1亿 7 | ) 8 | -------------------------------------------------------------------------------- /datasource/adapter.go: -------------------------------------------------------------------------------- 1 | package datasource 2 | 3 | // Adapter 数据源适配器 4 | // 5 | // TODO: 需要根据不同的数据源抽象方法 6 | type Adapter interface { 7 | } 8 | -------------------------------------------------------------------------------- /datasource/base/tdx_fundflow_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFundFlow(t *testing.T) { 9 | code := "sh600977" 10 | code = "sh600058" 11 | code = "sz002175" 12 | code = "sz002043" 13 | df := FundFlow(code) 14 | fmt.Println(df) 15 | } 16 | -------------------------------------------------------------------------------- /datasource/base/tdx_kline_basic_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestUpdateAllBasicKLine(t *testing.T) { 9 | code := "sh000001" 10 | data := UpdateAllBasicKLine(code) 11 | fmt.Println(data[len(data)-1]) 12 | _ = data 13 | } 14 | -------------------------------------------------------------------------------- /datasource/base/tdx_kline_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/pandas" 7 | "testing" 8 | ) 9 | 10 | func TestGetAllBasicKLine(t *testing.T) { 11 | code := "sh000001" 12 | code = "sz002043" 13 | code = "sz000567" 14 | code = "sz300580" 15 | code = "sz301129" 16 | code = "sz002669" 17 | code = "600256" 18 | code = "000063" 19 | code = "688981" 20 | code = "002857" 21 | code = "603230" 22 | code = "880866" 23 | code = exchange.CorrectSecurityCode(code) 24 | klines := UpdateAllBasicKLine(code) 25 | df := pandas.LoadStructs(klines) 26 | fmt.Println(df) 27 | _ = df 28 | } 29 | -------------------------------------------------------------------------------- /datasource/base/tdx_minutes.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/gotdx" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | "gitee.com/quant1x/gox/api" 9 | "gitee.com/quant1x/gox/runtime" 10 | ) 11 | 12 | // GetMinutes 获取分时数据 13 | func GetMinutes(securityCode, date string) (list []quotes.MinuteTime) { 14 | tdxApi := gotdx.GetTdxApi() 15 | u32Date := exchange.ToUint32Date(date) 16 | hs, err := tdxApi.GetHistoryMinuteTimeData(securityCode, u32Date) 17 | if err != nil || hs.Count == 0 { 18 | return 19 | } 20 | list = append(list, hs.List...) 21 | _ = hs 22 | return 23 | } 24 | 25 | // UpdateMinutes 更新指定日期的个股分时数据 26 | func UpdateMinutes(securityCode, date string) { 27 | defer runtime.IgnorePanic("update-minutes: code=%s, date=%s", securityCode, date) 28 | list := GetMinutes(securityCode, date) 29 | if len(list) > 0 { 30 | filename := cache.MinuteFilename(securityCode, date) 31 | _ = api.SlicesToCsv(filename, list) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /datasource/base/tdx_minutes_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/pandas" 7 | "testing" 8 | ) 9 | 10 | func TestGetMinutes(t *testing.T) { 11 | code := "sh000001" 12 | code = "sh510050" 13 | code = "sh600105" 14 | code = "880948" 15 | code = "603228" 16 | date := "2023-09-28" 17 | date = "2024-07-02" 18 | code = exchange.CorrectSecurityCode(code) 19 | date = exchange.FixTradeDate(date) 20 | v := GetMinutes(code, date) 21 | df := pandas.LoadStructs(v) 22 | fmt.Println(df) 23 | } 24 | -------------------------------------------------------------------------------- /datasource/base/tdx_realtime_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import "testing" 4 | 5 | //func TestBatchRealtime(t *testing.T) { 6 | // //BatchKLineWideRealtime([]string{"sh000001", "sh000905", "sz399001", "sz399006", "sh600600", "sz002528"}) 7 | // _ = BatchKLineWideRealtime([]string{"sz000966"}) 8 | //} 9 | 10 | func TestBatchRealtimeBasicKLine(t *testing.T) { 11 | _ = BatchRealtimeBasicKLine([]string{"sh000001"}) 12 | } 13 | -------------------------------------------------------------------------------- /datasource/base/tdx_transaction_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/pandas" 7 | "testing" 8 | ) 9 | 10 | func TestGetBeginDateOfHistoricalTradingData(t *testing.T) { 11 | v := GetBeginDateOfHistoricalTradingData() 12 | fmt.Println(v) 13 | } 14 | 15 | func TestCheckoutTransactionData(t *testing.T) { 16 | code := "sh000001" 17 | code = "sh510050" 18 | code = "sh600105" 19 | code = "880948" 20 | code = "603228" 21 | date := "2023-09-28" 22 | date = "2024-07-02" 23 | code = exchange.CorrectSecurityCode(code) 24 | date = exchange.FixTradeDate(date) 25 | v := CheckoutTransactionData(code, date, false) 26 | df := pandas.LoadStructs(v) 27 | fmt.Println(df) 28 | } 29 | -------------------------------------------------------------------------------- /datasource/base/tdx_xdxr.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/gotdx" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | "gitee.com/quant1x/gox/api" 9 | "gitee.com/quant1x/gox/logger" 10 | ) 11 | 12 | // UpdateXdxrInfo 除权除息数据 13 | func UpdateXdxrInfo(securityCode string) { 14 | securityCode = exchange.CorrectSecurityCode(securityCode) 15 | tdxApi := gotdx.GetTdxApi() 16 | xdxrInfos, err := tdxApi.GetXdxrInfo(securityCode) 17 | if err != nil { 18 | logger.Errorf("获取除权除息数据失败", err) 19 | return 20 | } 21 | //slices.SortFunc(xdxrInfos, func(a, b quotes.XdxrInfo) int { 22 | // if a.Date == b.Date { 23 | // return 0 24 | // } else if a.Date > b.Date { 25 | // return 1 26 | // } else { 27 | // return -1 28 | // } 29 | //}) 30 | if len(xdxrInfos) > 0 { 31 | filename := cache.XdxrFilename(securityCode) 32 | _ = api.SlicesToCsv(filename, xdxrInfos) 33 | } 34 | } 35 | 36 | // GetCacheXdxrList 获取除权除息的数据列表 37 | func GetCacheXdxrList(securityCode string) []quotes.XdxrInfo { 38 | securityCode = exchange.CorrectSecurityCode(securityCode) 39 | filename := cache.XdxrFilename(securityCode) 40 | var list []quotes.XdxrInfo 41 | _ = api.CsvToSlices(filename, &list) 42 | return list 43 | } 44 | -------------------------------------------------------------------------------- /datasource/base/tdx_xdxr.md: -------------------------------------------------------------------------------- 1 | 知识库 2 | === 3 | 4 | # 1. [除权除息的详细计算方法](https://china.findlaw.cn/zhishi/a1701513.html) 5 | 6 | 例如:某股票股权登记日的收盘价为18.00元,10股配3股,即每股配股数为0.3,配股价为每股6.00元,则次日股价为(18.00+6.00×0.3)÷(1+0.3)=15.23(元)。 7 | 8 | 计算除权除息价: 9 | 10 | 除权除息价=(股权登记日的收盘价-每股所分红利现金额+配股价×每股配股数)÷(1+每股送红股数+每股配股数); 11 | 12 | 例如:某股票股权登记日的收盘价为20.35元,每10股派发现金红利4.00元,送1股,配2股,配股价为5.50元/股,即每股分红0.4元,送0.1股,配0.2股,则次日除权除息价为(20.35-0.4+5.50×0.2)÷(1+0.1+0.2)=16.19(元)。 -------------------------------------------------------------------------------- /datasource/base/tdx_xdxr_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestUpdateXdxrInfo(t *testing.T) { 9 | code := "sh603158" 10 | UpdateXdxrInfo(code) 11 | } 12 | 13 | func TestGetCacheXdxrList(t *testing.T) { 14 | code := "sz002043" 15 | code = "sh000001" 16 | code = "sh603158" 17 | list := GetCacheXdxrList(code) 18 | fmt.Println(list) 19 | } 20 | -------------------------------------------------------------------------------- /datasource/base/tdx_zxg.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/pandas" 7 | ) 8 | 9 | const ( 10 | BlockPath = "/T0002/blocknew" 11 | ZxgBlk = "zxg.blk" 12 | BkltBlk = "BKLT.blk" 13 | ZdBk = "ZDBK.blk" 14 | ) 15 | 16 | func GetZxgList() []string { 17 | filename := cache.GetZxgFile() 18 | df := pandas.ReadCSV(filename) 19 | if df.Nrow() == 0 { 20 | return []string{} 21 | } 22 | rows := df.Col("code") 23 | if rows.Len() == 0 { 24 | return []string{} 25 | } 26 | // 校验证券代码, 统一格式前缀加代码 27 | cs := rows.Strings() 28 | for i, v := range cs { 29 | cs[i] = exchange.CorrectSecurityCode(v) 30 | } 31 | return cs 32 | } 33 | -------------------------------------------------------------------------------- /datasource/base/tdx_zxg_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetZxgList(t *testing.T) { 9 | list := GetZxgList() 10 | fmt.Println(list) 11 | } 12 | -------------------------------------------------------------------------------- /datasource/dfcf/capital_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestCapitalChange(t *testing.T) { 10 | code := "600115" 11 | v := CapitalChange(code) 12 | fmt.Println(v) 13 | fmt.Println(time.Local) 14 | } 15 | -------------------------------------------------------------------------------- /datasource/dfcf/dfcf.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | // 原始的接口状态信息 4 | type eastMoneyStatus struct { 5 | Version string `json:"version"` // 版本 6 | Success bool `json:"success"` // 是否成功 7 | Code int `json:"code"` // 错误码 8 | Message string `json:"message"` // 错误信息 9 | } 10 | 11 | // 原始api结果结构体 12 | type eastMoneyData[T any] struct { 13 | Pages int `json:"pages"` 14 | Count int `json:"count"` 15 | Data []T `json:"data"` 16 | } 17 | 18 | // 原始的api响应结果 19 | type rawResult[T any] struct { 20 | eastMoneyStatus 21 | eastMoneyData[T] `json:"result"` 22 | } 23 | -------------------------------------------------------------------------------- /datasource/dfcf/finance_analysis_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gitee.com/quant1x/gox/api" 7 | "testing" 8 | ) 9 | 10 | func TestGetQuarterlyReports(t *testing.T) { 11 | v, n, err := GetQuarterlyReports() 12 | fmt.Println(v) 13 | data, _ := json.Marshal(v) 14 | text := api.Bytes2String(data) 15 | fmt.Println(text) 16 | fmt.Println(n) 17 | fmt.Println(err) 18 | } 19 | -------------------------------------------------------------------------------- /datasource/dfcf/finance_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gitee.com/quant1x/gox/api" 7 | "testing" 8 | ) 9 | 10 | func TestFinanceReports(t *testing.T) { 11 | date := "2022-09-30" 12 | date = "2023-03-01" 13 | reports, _, _, err := FinanceReports(date) 14 | if err != nil { 15 | return 16 | } 17 | data, err := json.Marshal(reports) 18 | fmt.Println(api.Bytes2String(data)) 19 | } 20 | -------------------------------------------------------------------------------- /datasource/dfcf/financial_reports_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gitee.com/quant1x/gox/api" 7 | "testing" 8 | ) 9 | 10 | func TestQuarterlyReports(t *testing.T) { 11 | date := "20230928" 12 | 13 | list, pages, err := QuarterlyReports(date, 1) 14 | fmt.Println(list) 15 | fmt.Println(pages) 16 | fmt.Println(err) 17 | } 18 | 19 | func TestGetCacheQuarterlyReportsBySecurityCode(t *testing.T) { 20 | date := "20231027" 21 | code := "sh600178" 22 | v := GetCacheQuarterlyReportsBySecurityCode(code, date) 23 | fmt.Println(v) 24 | data, _ := json.Marshal(v) 25 | text := api.Bytes2String(data) 26 | fmt.Println(text) 27 | } 28 | -------------------------------------------------------------------------------- /datasource/dfcf/fundflow_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/pandas" 6 | "testing" 7 | ) 8 | 9 | func TestIndividualStocksFundFlow(t *testing.T) { 10 | code := "sz000701" 11 | list := IndividualStocksFundFlow(code, "2025-03-12") 12 | df := pandas.LoadStructs(list) 13 | fmt.Println(df) 14 | } 15 | -------------------------------------------------------------------------------- /datasource/dfcf/longhubang_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Test_rawBillBoard(t *testing.T) { 9 | date := "20240730" 10 | v, n, err := rawBillBoardList(date, 1, lhbBuy) 11 | fmt.Println(v, n, err) 12 | } 13 | -------------------------------------------------------------------------------- /datasource/dfcf/margin_trading_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMarginTrading(t *testing.T) { 9 | date := "20250307" 10 | v, n, err := rawMarginTradingList(date, 2) 11 | fmt.Println(date) 12 | fmt.Println(v, n, err) 13 | } 14 | -------------------------------------------------------------------------------- /datasource/dfcf/notices_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gitee.com/quant1x/exchange" 7 | "gitee.com/quant1x/gox/api" 8 | "testing" 9 | ) 10 | 11 | func TestStockNoticeReport(t *testing.T) { 12 | notices, _, err := StockNotices("603045", "20240101", "20240430", 1) 13 | if err != nil { 14 | return 15 | } 16 | data, err := json.Marshal(notices) 17 | fmt.Println(api.Bytes2String(data)) 18 | } 19 | 20 | func TestNoticeDateForAnnualReport(t *testing.T) { 21 | code := "sz301587" 22 | date := "2024-04-09" 23 | y, q := NoticeDateForReport(code, date) 24 | fmt.Println(y, q) 25 | ys := exchange.DateRange(date, y) 26 | fmt.Println(len(ys)) 27 | qs := exchange.DateRange(date, q) 28 | fmt.Println(len(qs)) 29 | } 30 | -------------------------------------------------------------------------------- /datasource/dfcf/shareholder_stock_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/exchange" 6 | "testing" 7 | ) 8 | 9 | func TestShareHolder(t *testing.T) { 10 | code := "sh600115" 11 | v := ShareHolder(code, exchange.Today(), 2) 12 | fmt.Println(v) 13 | } 14 | 15 | func TestGetCacheShareHolder(t *testing.T) { 16 | code := "sh600105" 17 | v := GetCacheShareHolder(code, exchange.Today(), 4) 18 | fmt.Println(v) 19 | } 20 | -------------------------------------------------------------------------------- /datasource/dfcf/shareholder_test.go: -------------------------------------------------------------------------------- 1 | package dfcf 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFreeHoldingDetail(t *testing.T) { 9 | data := freeHoldingDetail() 10 | fmt.Println(data) 11 | //df := pandas.LoadStructs(data) 12 | //_ = df.WriteCSV("capitals-0.csv") 13 | } 14 | 15 | func TestFreeHoldingAnalyse(t *testing.T) { 16 | data, num, err := getFreeHoldingAnalyse(1) 17 | fmt.Println(num, err) 18 | fmt.Printf("%+v\n", data) 19 | //df := pandas.LoadStructs(data) 20 | //_ = df.WriteCSV("capitals-1.csv") 21 | } 22 | -------------------------------------------------------------------------------- /datasource/tdxweb/margintrading.go: -------------------------------------------------------------------------------- 1 | package tdxweb 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gox/http" 6 | ) 7 | 8 | const ( 9 | // https://wenda.tdx.com.cn/site/wenda/stock_index.html?message=%E8%9E%8D%E8%B5%84%E8%9E%8D%E5%88%B8 10 | //urlMarginTrading = "https://wenda.tdx.com.cn/TQL?Entry=JNLPSE.getAllCode&RI=6C07" 11 | urlMarginTrading = "https://wenda.tdx.com.cn/TQL?Entry=JNLPSE.getAllCode&RI=6BFD" 12 | ) 13 | 14 | func MarginTrading() { 15 | //params := urlpkg.Values{ 16 | // "direction": {direction.String()}, 17 | // "code": {fmt.Sprintf("%s.%s", symbol, strings.ToUpper(mflag))}, 18 | // "price": {fmt.Sprintf("%f", price)}, 19 | // "volume": {fmt.Sprintf("%d", volume)}, 20 | // "strategy": {models.QmtStrategyName(model)}, 21 | // "remark": {models.QmtOrderRemark(model)}, 22 | //} 23 | //body := params.Encode() 24 | body := `[{"nlpse_id":"7318110250698020161","op_flag":1,"sec_code":"","order_field":"sec_code","dynamic_order":"","order_flag":"1","POS":"0","COUNT":"30","timestamps":0,"RANG":"AG"}]` 25 | //body = `[{"op_flag":1,"sec_code":"","order_field":"sec_code","dynamic_order":"","order_flag":"1","POS":"0","COUNT":"30","timestamps":0,"RANG":"AG"}]` 26 | body = `[{"nlpseId":"7318094960614448284","orderField":"chg","orderFlag":"0"}]` 27 | //logger.Infof("trader-order: %s", body) 28 | header := map[string]any{ 29 | http.ContextType: "application/x-www-form-urlencoded" + "; charset=UTF-8", 30 | //"Cookie": "Hm_lvt_5c4c948b141e4d66943a8430c3d600d0=1703193725; Hm_lpvt_5c4c948b141e4d66943a8430c3d600d0=1703720553; LST=10; ASPSessionID=3755195075085152819", 31 | "Cookie": "Hm_lvt_5c4c948b141e4d66943a8430c3d600d0=1703193725; Hm_lpvt_5c4c948b141e4d66943a8430c3d600d0=1703720553; LST=10; ASPSessionID=3755195113739858633", 32 | "Origin": "https://wenda.tdx.com.cn", 33 | "Referer": "https://wenda.tdx.com.cn/", 34 | } 35 | 36 | data, _, err := http.Request(urlMarginTrading, http.MethodPost, body, header) 37 | 38 | fmt.Println(data, err) 39 | } 40 | -------------------------------------------------------------------------------- /datasource/tdxweb/margintrading_test.go: -------------------------------------------------------------------------------- 1 | package tdxweb 2 | 3 | import "testing" 4 | 5 | func TestMarginTrading(t *testing.T) { 6 | MarginTrading() 7 | } 8 | -------------------------------------------------------------------------------- /datasource/tdxweb/safetyscore.go: -------------------------------------------------------------------------------- 1 | package tdxweb 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/market" 6 | "gitee.com/quant1x/exchange" 7 | "gitee.com/quant1x/gox/concurrent" 8 | "gitee.com/quant1x/gox/http" 9 | "gitee.com/quant1x/gox/logger" 10 | "gitee.com/quant1x/pkg/fastjson" 11 | ) 12 | 13 | const ( 14 | urlRiskAssessment = "http://page3.tdx.com.cn:7615/site/pcwebcall_static/bxb/json/" 15 | defaultSafetyScore = 100 16 | defaultSafetyScoreOfNotFound = 100 17 | defaultSafetyScoreOfIgnore = 0 18 | ) 19 | 20 | var ( 21 | __mapSafetyScore = concurrent.NewTreeMap[string, int]() 22 | ) 23 | 24 | // GetSafetyScore 获取个股安全分 25 | // 26 | // http://page3.tdx.com.cn:7615/site/pcwebcall_static/bxb/bxb.html?code=600178&color=0 27 | func GetSafetyScore(securityCode string) (score int) { 28 | if !exchange.AssertStockBySecurityCode(securityCode) { 29 | return defaultSafetyScore 30 | } 31 | if market.IsNeedIgnore(securityCode) { 32 | return defaultSafetyScoreOfIgnore 33 | } 34 | score = defaultSafetyScore 35 | _, _, code := exchange.DetectMarket(securityCode) 36 | if len(code) == 6 { 37 | url := fmt.Sprintf("%s%s.json", urlRiskAssessment, code) 38 | data, err := http.Get(url) 39 | if err != nil || err == http.NotFound { 40 | score = defaultSafetyScoreOfNotFound 41 | } else { 42 | obj, err := fastjson.ParseBytes(data) 43 | if err != nil { 44 | logger.Errorf("%+v\n", err) 45 | tmpScore, ok := __mapSafetyScore.Get(securityCode) 46 | if ok { 47 | score = tmpScore 48 | } else { 49 | score = defaultSafetyScore 50 | } 51 | } else { 52 | result := obj.GetArray("data") 53 | if result != nil && len(result) > 0 { 54 | tmpScore := 100 55 | for _, v := range result { 56 | rows := v.GetArray("rows") 57 | for _, row := range rows { 58 | trig := row.GetInt("trig") 59 | if trig == 1 { 60 | tmpScore = tmpScore - row.GetInt("fs") 61 | } 62 | } 63 | } 64 | score = tmpScore 65 | __mapSafetyScore.Put(securityCode, score) 66 | } 67 | } 68 | } 69 | } 70 | return score 71 | } 72 | -------------------------------------------------------------------------------- /datasource/tdxweb/safetyscore_test.go: -------------------------------------------------------------------------------- 1 | package tdxweb 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetSafetyScore(t *testing.T) { 9 | code := "sh510050" 10 | code = "sh000001" 11 | code = "sh600178" 12 | v := GetSafetyScore(code) 13 | fmt.Println(v) 14 | } 15 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 数据文档 2 | === 3 | 4 | ***数据项, 按顺序自上而下存在依赖关系*** 5 | 6 | | 级别 | 数据项 | 功能 | 更新时间 | 数据具有时效性 | 7 | |:---|:-------|:--------------------------|:------------------|:--------| 8 | | 0 | 交易日历 | 截止今年底最后一个交易日的全部交易日期列表 | 09:00:00 | [X] | 9 | | 0 | 证券列表 | 当日有效交易的全部证券代码列表 | 09:00:00 | [X] | 10 | | 0 | 板块列表 | 当日有效交易的全部板块数据列表 | 09:00:00 | [X] | 11 | | 0 | 历史成交记录 | 每日成交记录 | 09:15:00 | [X] | 12 | | 0 | 除权除息 | 除权除息全部记录 | 09:15:00 | [X] | 13 | | 0 | K线数据 | 日K线 | 09:15:00 | [X] | 14 | | 0 | 5档行情快照 | 即时行情 | 09:15:00 | [√] | 15 | | 0 | F10 | 基本面, 上市日期、股本结构等 | 17:00:00 | [√] | 16 | | 1 | 开盘指数情绪 | sh000xxx, sz399xxx | 09:25:00~09:29:59 | [√] | 17 | | 1 | 开盘板块情绪 | 通达信板块: sh880xxx, sh881xxx | 09:27:00~09:29:59 | [√] | 18 | | 1 | 收盘指数情绪 | sh000xxx, sz399xxx | 15:01:00 | [X] | 19 | | 1 | 收盘板块情绪 | 通达信板块: sh880xxx, sh881xxx | 15:01:00 | [X] | 20 | | 2 | K线形态 | 检测K线的各种形态 | 15:30:00 | [X] | 21 | | 2 | 资金流向 | 内外盘, 资金流向统计 | 15:30:00 | [X] | 22 | | 2 | 十大流通股东 | 季报-流通盘 | 22:00:00 | [X] | 23 | | 2 | 财务报告 | 季报-财报 | 22:00:00 | [X] | 24 | | 2 | 上市公司公告 | 公告 | 22:00:00 | [X] | 25 | 26 | -------------------------------------------------------------------------------- /docs/quant-am.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | title A股 - 早盘(09:15~09:30) 3 | concise "miniQMT" as QMT 4 | concise "量化策略工具" as Quant 5 | concise "数据引擎" as Stock 6 | scale 60 as 60 pixels 7 | 8 | @09:13:00 9 | Stock is 数据初始化 10 | 11 | @09:15:00 12 | Stock is "集合竞价阶段" 13 | Quant is Waiting #LightCyan;line:Aqua 14 | QMT is Waiting 15 | 16 | @09:20:00 17 | Stock is "集合竞价开盘阶段, 计算个股竞价" 18 | 19 | @09:25:00 20 | Stock is 计算板块竞价 21 | 22 | @09:27:31 23 | Stock is 缓存 24 | Quant is 计算情绪 #Gold 25 | 26 | @09:29:00 27 | Stock is ok 28 | 29 | @09:29:00 30 | Quant is 缓存 31 | Quant ->QMT: 输出订单 32 | 33 | @09:30:00 34 | QMT is 执行交易指令 #Red 35 | Quant is ok 36 | 37 | @09:31:00 38 | highlight 09:15:00 to 09:27:30 #Yellow;line:DimGrey : 数据采集阶段 39 | highlight 09:27:31 to 09:28:55 #Gold;line:DimGrey : 执行策略 40 | 41 | @enduml 42 | -------------------------------------------------------------------------------- /docs/quant-pm.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | title A股 - 尾盘(14:57~15:02) 3 | concise "miniQMT" as QMT 4 | concise "量化策略工具" as Quant 5 | concise "数据引擎" as Stock 6 | scale 30 as 60 pixels 7 | 8 | @14:56:00 9 | Stock is 初始化 10 | 11 | @14:57:00 12 | Stock is "集合竞价收盘阶段" 13 | Quant is Waiting #LightCyan;line:Aqua 14 | QMT is Waiting 15 | 16 | @14:58:00 17 | Stock is 缓存 18 | Quant is 计算情绪 #Gold 19 | @14:58:30 20 | Stock is 采集连续竞价数据 21 | Quant is 缓存 22 | Quant ->QMT: 输出订单 23 | 24 | @14:58:30 25 | QMT is 执行交易指令 #Red 26 | Quant is ok 27 | 28 | @14:59:30 29 | QMT is 同步订单回报 30 | 31 | @15:01:00 32 | Stock is 计算板块竞价 33 | Quant is 计算情绪 #Gold 34 | 35 | @15:02:00 36 | Stock is 缓存 37 | Quant is 同步 38 | 39 | @15:02:10 40 | Quant -> QMT: 同步持仓 41 | 42 | highlight 14:57:00 to 15:02:00 #Yellow;line:DimGrey : 数据采集阶段 43 | highlight 14:58:00 to 14:59:30 #Gold;line:DimGrey : 执行策略 44 | 45 | @15:02:30 46 | QMT is 结束 47 | Quant is 结束 48 | Stock is 结束 49 | 50 | @enduml 51 | -------------------------------------------------------------------------------- /docs/quant-realtime.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | start 4 | :加载实时策略; 5 | :板块排序; 6 | if (选出板块前N1?) then (yes) 7 | :前N1板块内个股排序:因子权重,依赖Tick数据准确性; 8 | if (板块内个股数等于0,最大N2只?) then (no) 9 | stop 10 | endif 11 | 12 | if (命中标的数大于0?) then (yes) 13 | :推荐不超过N1*N2只个股; 14 | else (no) 15 | :全量的个股列表; 16 | endif 17 | :执行策略x号策略,输出结果集; 18 | :执行交易执行; 19 | endif 20 | stop 21 | @enduml 22 | -------------------------------------------------------------------------------- /docs/stock.puml: -------------------------------------------------------------------------------- 1 | !theme cerulean 2 | '更多皮肤 https://moretop.gitee.io/plantuml/themes 3 | skinparam backgroundColor #FCFCFC 4 | 5 | @startuml 6 | participant Stock as stock 7 | queue TCPPool as pool 8 | control IPPool as ips 9 | database TDX as tdx 10 | stock -> stock: 业务处理 11 | stock -> pool: 获取一个connection 12 | pool -> pool: ping 13 | pool -> pool: ping成功, 从queue返回一个connect 14 | pool -->stock: 取得一个可用的connection 15 | pool -> pool: ping失败,创建新的链接 16 | pool -> ips: 获取一个ip地址 17 | ips --> ips: 顺序获取下一个可用ip,控制只有一个IP正在保持连接 18 | ips --> pool: 返回一个ip地址 19 | pool -> pool: 创建新的链接 20 | pool -->stock: 取得一个可用的connection 21 | stock ->tdx: 业务请求 22 | tdx-->stock: 业务返回 23 | stock ->pool: 返还connection 24 | pool->pool: 放入已打开的链接队列,进行心跳 25 | @enduml -------------------------------------------------------------------------------- /docs/strategy.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | start 4 | :加载板块列表; 5 | if (板块总数大于0) then (true) 6 | :板块排序:金额和涨幅; 7 | if (选出板块前5?) then (yes) 8 | :前5板块内个股排序:涨幅和竞价换手,依赖Tick数据准确性; 9 | if (板块内个股数等于0,最大3只?) then (no) 10 | stop 11 | endif 12 | 13 | if (是否0策略?) then (yes) 14 | :推荐不超过15只个股; 15 | else (no) 16 | :全量的个股列表; 17 | endif 18 | :执行策略x号策略,输出结果集; 19 | endif 20 | else (false) 21 | endif 22 | 23 | if (曲线回归命中数等于0?) then (yes) 24 | :返回; 25 | else 26 | if (量能检测命中?) then (yes) 27 | :置信区间检测; 28 | else (no) 29 | endif 30 | endif 31 | :输出结果集; 32 | stop 33 | 34 | @enduml 35 | -------------------------------------------------------------------------------- /factors/adapter.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/cache" 6 | "gitee.com/quant1x/gox/util/treemap" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | const ( 12 | // 闪存路径 13 | cache1dPrefix = "flash" 14 | // 默认证券代码为上证指数 15 | defaultSecurityCode = "sh000001" 16 | ) 17 | 18 | // 返回文件路径, 指定关键字和日期 19 | func getCache1DFilepath(key, date string) string { 20 | cachePath, key, found := strings.Cut(key, "/") 21 | if !found { 22 | key = cachePath 23 | cachePath = cache1dPrefix 24 | } 25 | cachePath = cache.GetRootPath() + "/" + cachePath 26 | year := date[:4] 27 | filename := fmt.Sprintf("%s/%s/%s.%s", cachePath, year, key, date) 28 | return filename 29 | } 30 | 31 | // FeatureRotationAdapter 特征缓存日旋转适配器 32 | // 33 | // 一天一个特征组合缓存文件 34 | type FeatureRotationAdapter interface { 35 | // Kind 获取缓存类型 36 | Kind() cache.Kind 37 | // Name 名称 38 | Name() string 39 | // Element 获取指定日期的特征 40 | Element(securityCode string, date ...string) Feature 41 | // Checkout 加载指定日期的缓存 42 | Checkout(date ...string) 43 | // Merge 合并数据 44 | Merge(p *treemap.Map) 45 | // Factory 工厂 46 | Factory(date, securityCode string) Feature 47 | } 48 | 49 | var ( 50 | __mutexFeatureRotationAdapters sync.Mutex 51 | __mapFeatureRotationAdapters = map[string]FeatureRotationAdapter{} 52 | ) 53 | 54 | func RegisterFeatureRotationAdapter(key string, adapter FeatureRotationAdapter) { 55 | __mutexFeatureRotationAdapters.Lock() 56 | defer __mutexFeatureRotationAdapters.Unlock() 57 | __mapFeatureRotationAdapters[key] = adapter 58 | } 59 | 60 | // SwitchDate 统一切换数据的缓存日期 61 | func SwitchDate(date string) { 62 | __mutexFeatureRotationAdapters.Lock() 63 | defer __mutexFeatureRotationAdapters.Unlock() 64 | for _, v := range __mapFeatureRotationAdapters { 65 | v.Checkout(date) 66 | } 67 | } 68 | 69 | func Get(key string) FeatureRotationAdapter { 70 | __mutexFeatureRotationAdapters.Lock() 71 | defer __mutexFeatureRotationAdapters.Unlock() 72 | return __mapFeatureRotationAdapters[key] 73 | } 74 | -------------------------------------------------------------------------------- /factors/base_data.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/gotdx/quotes" 5 | ) 6 | 7 | // BaseData 数据层, 数据集接口 8 | // 9 | // 数据集是基础数据, 应当遵循结构简单, 尽量减小缓存的文件数量, 加载迅速 10 | // 检索的规则是按日期和代码进行查询 11 | type BaseData interface { 12 | // Clone 克隆一个BaseData, 是所有写操作的基础 13 | Clone(featureDate, code string) BaseData 14 | // Update 更新数据 15 | Update(featureDate, code string) error 16 | // Repair 回补数据 17 | Repair(featureDate, code string) error 18 | // Increase 增量计算, 用快照增量计算特征 19 | Increase(snapshot quotes.Snapshot) error 20 | } 21 | -------------------------------------------------------------------------------- /factors/chips.fbs: -------------------------------------------------------------------------------- 1 | // metrics.fbs 2 | namespace Metrics; 3 | 4 | table DataPoint { 5 | timestamp: long; 6 | visits: int; 7 | users: int; 8 | // 可扩展其他字段 9 | } 10 | 11 | table Chip { 12 | price: float64; 13 | vol: float64; 14 | } 15 | 16 | table DateBlock { 17 | date: string (key); // 日期作为键 18 | points: [DataPoint]; // 数据点列表 19 | } 20 | 21 | root_type DateBlock; -------------------------------------------------------------------------------- /factors/chips.proto: -------------------------------------------------------------------------------- 1 | //edition = "2024"; 2 | syntax = "proto3"; 3 | 4 | package pb; 5 | option go_package = "./pb"; 6 | 7 | message Chips { 8 | string date = 1; 9 | map dist = 2; // key单位为分的价格, value是成交量 10 | } 11 | 12 | message ChipDistribution { 13 | map data = 1; // key是日期, value是chips的map 14 | } 15 | -------------------------------------------------------------------------------- /factors/data_adjust.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/datasource/base" 5 | "gitee.com/quant1x/exchange" 6 | ) 7 | 8 | // AdjustmentExecutor 复权执行接口 9 | type AdjustmentExecutor interface { 10 | Apply(factor func(p float64) float64) 11 | GetDate() string 12 | } 13 | 14 | // ApplyAdjustment 计算前复权 假定缓存中的记录都是截至当日的前一个交易日已经前复权 15 | // 16 | // 参数: 17 | // - securityCode 证券代码 18 | // - data 实现AdjustmentExecutor接口的数据集 19 | // - startDate 表示已经除权的日期 20 | // 21 | // 返回: 无 22 | func ApplyAdjustment[E any](securityCode string, data []E, startDate string) { 23 | rows := len(data) 24 | if rows == 0 { 25 | return 26 | } 27 | startDate = exchange.FixTradeDate(startDate) 28 | // 复权之前, 假定当前缓存之中的数据都是复权过的数据 29 | // 那么就应该只拉取缓存最后1条记录之后的除权除息记录进行复权 30 | // 前复权adjust 31 | dividends := base.GetCacheXdxrList(securityCode) 32 | for i := 0; i < len(dividends); i++ { 33 | xdxr := dividends[i] 34 | if xdxr.Category != 1 { 35 | // 忽略非除权信息 36 | continue 37 | } 38 | xdxrDate := exchange.FixTradeDate(xdxr.Date) 39 | if xdxrDate <= startDate { 40 | // 忽略除权数据在新数据之前的除权记录 41 | continue 42 | } 43 | factor := xdxr.Adjust() 44 | for j := 0; j < rows; j++ { 45 | kl, ok := any(&data[j]).(AdjustmentExecutor) 46 | if !ok { 47 | continue 48 | } 49 | barCurrentDate := kl.GetDate() 50 | if barCurrentDate > xdxrDate { 51 | break 52 | } 53 | if barCurrentDate < xdxrDate { 54 | //kl.Open = factor(kl.Open) 55 | //kl.Close = factor(kl.Close) 56 | //kl.High = factor(kl.High) 57 | //kl.Low = factor(kl.Low) 58 | //// 成交量复权 59 | //// 1. 计算均价线 60 | //maPrice := kl.Amount / kl.Volume 61 | //// 2. 均价线复权 62 | //// 通达信中可能存在没有量复权的情况, 需要在系统设置中的"设置1"勾选分析图中成交量复权 63 | //maPrice = factor(maPrice) 64 | //// 3. 以成交金额为基准, 用复权均价线计算成交量 65 | //kl.Volume = kl.Amount / maPrice 66 | kl.Apply(factor) 67 | } 68 | if barCurrentDate == xdxrDate { 69 | break 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /factors/data_adjust_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/datasource/base" 6 | "gitee.com/quant1x/exchange" 7 | "testing" 8 | ) 9 | 10 | // 2. 完善接口实现 11 | type testCase struct { 12 | date string 13 | price float64 14 | } 15 | 16 | func (tc *testCase) Apply(factor func(float64) float64) { 17 | tc.price = factor(tc.price) // 实际实现调整逻辑 18 | } 19 | 20 | func (tc *testCase) GetDate() string { 21 | return tc.date 22 | } 23 | 24 | func apply0[E any](securityCode string, data []E, startDate string) { 25 | for i := 0; i < len(data); i++ { 26 | f, ok := any(&data[i]).(AdjustmentExecutor) 27 | if !ok { 28 | continue 29 | } 30 | if f.GetDate() > startDate { 31 | f.Apply(func(p float64) float64 { 32 | return p * 0.5 // 示例调整因子 33 | }) 34 | } 35 | } 36 | _ = securityCode 37 | _ = data 38 | _ = startDate 39 | } 40 | 41 | func TestApplyAdjustment0(t *testing.T) { 42 | code := "sh000001" 43 | date := "2025-03-10" 44 | securityCode := code 45 | //securityCode := exchange.CorrectSecurityCode(code) 46 | //date = exchange.FixTradeDate(date) 47 | //klines := base.CheckoutKLines(securityCode, date) 48 | //fmt.Println(klines) 49 | testCases := []testCase{ 50 | { 51 | date: "2025-03-11", 52 | price: 1.0, 53 | }, 54 | { 55 | date: "2025-03-12", 56 | price: 2.0, 57 | }, 58 | } 59 | fmt.Println(testCases) 60 | // 执行测试 61 | apply0(securityCode, testCases, date) 62 | fmt.Println("------------------------------------------------------------") 63 | fmt.Println(testCases) 64 | } 65 | 66 | func TestApplyAdjustment(t *testing.T) { 67 | code := "600580" 68 | date := "2025-03-10" 69 | securityCode := exchange.CorrectSecurityCode(code) 70 | date = exchange.FixTradeDate(date) 71 | klines := base.CheckoutKLines(securityCode, date) 72 | fmt.Println(klines) 73 | // 执行测试 74 | ApplyAdjustment(securityCode, klines, date) 75 | fmt.Println("------------------------------------------------------------") 76 | fmt.Println(klines) 77 | } 78 | -------------------------------------------------------------------------------- /factors/dataset_chip_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/factors/pb" 6 | "gitee.com/quant1x/pkg/yaml" 7 | "google.golang.org/protobuf/proto" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func Test_updateChipDistribution(t *testing.T) { 13 | code := "000701" 14 | date := "2025-03-11" 15 | _ = updateChipDistribution(date, code) 16 | filename := "t1.bin" 17 | dataBytes, err := os.ReadFile(filename) 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | cd := pb.ChipDistribution{} 23 | err = proto.Unmarshal(dataBytes, &cd) 24 | if err != nil { 25 | fmt.Println(err) 26 | return 27 | } 28 | fmt.Println(cd.String()) 29 | } 30 | 31 | func Test_v1updateChipDistribution(t *testing.T) { 32 | code := "000701" 33 | date := "2025-03-11" 34 | _ = updateChipDistribution(date, code) 35 | 36 | filename := "t1.yaml" 37 | dataBytes, err := os.ReadFile(filename) 38 | if err != nil { 39 | fmt.Println(err) 40 | return 41 | } 42 | tmp := map[float64]float64{} 43 | err = yaml.Unmarshal(dataBytes, tmp) 44 | if err != nil { 45 | fmt.Println(err) 46 | return 47 | } 48 | fmt.Println(tmp) 49 | } 50 | -------------------------------------------------------------------------------- /factors/dataset_kline.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "context" 5 | "gitee.com/quant1x/engine/cache" 6 | "gitee.com/quant1x/engine/datasource/base" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | ) 9 | 10 | type DataKLine struct { 11 | cache.DataSummary 12 | Date string 13 | Code string 14 | } 15 | 16 | func init() { 17 | summary := __mapDataSets[BaseKLine] 18 | _ = cache.Register(&DataKLine{DataSummary: summary}) 19 | } 20 | 21 | func (k *DataKLine) Clone(date, code string) DataSet { 22 | summary := __mapDataSets[BaseKLine] 23 | var dest = DataKLine{ 24 | DataSummary: summary, 25 | Date: date, 26 | Code: code, 27 | } 28 | return &dest 29 | } 30 | 31 | func (k *DataKLine) GetDate() string { 32 | return k.Date 33 | } 34 | 35 | func (k *DataKLine) GetSecurityCode() string { 36 | return k.Code 37 | } 38 | 39 | func (k *DataKLine) Init(ctx context.Context, date string) error { 40 | //_ = barIndex 41 | _ = ctx 42 | _ = date 43 | return nil 44 | } 45 | 46 | func (k *DataKLine) Checkout(securityCode, date string) { 47 | //TODO implement me 48 | _ = securityCode 49 | _ = date 50 | panic("implement me") 51 | } 52 | 53 | func (k *DataKLine) Check(cacheDate, featureDate string) error { 54 | //TODO implement me 55 | _ = cacheDate 56 | _ = featureDate 57 | panic("implement me") 58 | } 59 | 60 | func (k *DataKLine) Filename(date, code string) string { 61 | //TODO implement me 62 | _ = code 63 | _ = date 64 | panic("implement me") 65 | } 66 | 67 | func (k *DataKLine) Print(code string, date ...string) { 68 | //TODO implement me 69 | _ = code 70 | _ = date 71 | panic("implement me") 72 | } 73 | 74 | func (k *DataKLine) Update(date string) error { 75 | base.UpdateAllBasicKLine(k.GetSecurityCode()) 76 | _ = date 77 | return nil 78 | } 79 | 80 | func (k *DataKLine) Repair(date string) error { 81 | base.UpdateAllBasicKLine(k.GetSecurityCode()) 82 | _ = date 83 | return nil 84 | } 85 | 86 | func (k *DataKLine) Increase(snapshot quotes.Snapshot) error { 87 | //TODO K线增量更新数据的条件是缓存的数据最晚的日期是上一个交易日, 否则会缺失缓存数据中最后1条数据和当日之间的数据, 所以只能按照K线的更新方法, 不适合用快照更新 88 | // 第一步: 取出最后一条数据的记录 89 | // 第二步: 检查时间的有效性 90 | // 第三步: 用快照组织K线结构 91 | // 第四步: 如果不符合快照更新, 则忽略 92 | _ = snapshot 93 | panic("implement me") 94 | } 95 | -------------------------------------------------------------------------------- /factors/dataset_minutes.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "context" 5 | "gitee.com/quant1x/engine/cache" 6 | "gitee.com/quant1x/engine/datasource/base" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | ) 9 | 10 | // DataMinutes 分时数据 11 | type DataMinutes struct { 12 | Manifest 13 | } 14 | 15 | func init() { 16 | summary := __mapDataSets[BaseMinutes] 17 | _ = cache.Register(&DataMinutes{Manifest: Manifest{DataSummary: summary}}) 18 | } 19 | 20 | func (this *DataMinutes) Clone(date string, code string) DataSet { 21 | summary := __mapDataSets[BaseMinutes] 22 | var dest = DataMinutes{ 23 | Manifest: Manifest{ 24 | DataSummary: summary, 25 | Date: date, 26 | Code: code, 27 | }, 28 | } 29 | return &dest 30 | } 31 | 32 | func (this *DataMinutes) Init(ctx context.Context, date string) error { 33 | _ = ctx 34 | _ = date 35 | return nil 36 | } 37 | 38 | func (this *DataMinutes) Update(date string) error { 39 | base.UpdateMinutes(this.GetSecurityCode(), date) 40 | return nil 41 | } 42 | 43 | func (this *DataMinutes) Repair(date string) error { 44 | this.Update(date) 45 | return nil 46 | } 47 | 48 | func (this *DataMinutes) Increase(snapshot quotes.Snapshot) error { 49 | _ = snapshot 50 | //TODO implement me 51 | panic("implement me") 52 | 53 | } 54 | 55 | func (this *DataMinutes) Print(code string, date ...string) { 56 | _ = code 57 | _ = date 58 | //TODO implement me 59 | panic("implement me") 60 | } 61 | -------------------------------------------------------------------------------- /factors/dataset_trans.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "context" 5 | "gitee.com/quant1x/engine/cache" 6 | "gitee.com/quant1x/engine/datasource/base" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | ) 9 | 10 | // TransactionRecord 成交记录 11 | // 12 | // 最短3秒内的合并统计数据, 与行情数据保持一致 13 | // 不可以当作tick数据来使用 14 | type TransactionRecord struct { 15 | cache.DataSummary 16 | Date string 17 | Code string 18 | } 19 | 20 | func init() { 21 | summary := __mapDataSets[BaseTransaction] 22 | _ = cache.Register(&TransactionRecord{DataSummary: summary}) 23 | } 24 | 25 | func (r *TransactionRecord) Clone(date string, code string) DataSet { 26 | summary := __mapDataSets[BaseTransaction] 27 | var dest = TransactionRecord{DataSummary: summary, Date: date, Code: code} 28 | return &dest 29 | } 30 | 31 | func (r *TransactionRecord) GetDate() string { 32 | return r.Date 33 | } 34 | 35 | func (r *TransactionRecord) GetSecurityCode() string { 36 | return r.Code 37 | } 38 | 39 | func (r *TransactionRecord) Print(code string, date ...string) { 40 | //TODO implement me 41 | panic("implement me") 42 | } 43 | 44 | func (r *TransactionRecord) Init(ctx context.Context, date string) error { 45 | _ = ctx 46 | _ = date 47 | return nil 48 | } 49 | 50 | func (r *TransactionRecord) Checkout(securityCode, date string) { 51 | //TODO implement me 52 | panic("implement me") 53 | } 54 | 55 | func (r *TransactionRecord) Check(cacheDate, featureDate string) error { 56 | //TODO implement me 57 | panic("implement me") 58 | } 59 | 60 | func (r *TransactionRecord) Filename(date, code string) string { 61 | //TODO implement me 62 | panic("implement me") 63 | } 64 | 65 | func (r *TransactionRecord) Update(date string) error { 66 | base.UpdateBeginDateOfHistoricalTradingData(date) 67 | base.GetAllHistoricalTradingData(r.GetSecurityCode()) 68 | return nil 69 | } 70 | 71 | func (r *TransactionRecord) Repair(date string) error { 72 | //base.GetAllHistoricalTradingData(r.code) 73 | base.GetHistoricalTradingDataByDate(r.GetSecurityCode(), date) 74 | return nil 75 | } 76 | 77 | func (r *TransactionRecord) Increase(snapshot quotes.Snapshot) error { 78 | _ = snapshot 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /factors/dataset_trans_count.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/exchange" 5 | "gitee.com/quant1x/gotdx/quotes" 6 | ) 7 | 8 | // CountInflow 统计指定日期的内外盘 9 | func CountInflow(list []quotes.TickTransaction, securityCode string, date string) (summary TurnoverDataSummary) { 10 | if len(list) == 0 { 11 | return 12 | } 13 | securityCode = exchange.CorrectSecurityCode(securityCode) 14 | lastPrice := float64(0) 15 | for _, v := range list { 16 | tm := v.Time 17 | direction := int32(v.BuyOrSell) 18 | price := v.Price 19 | if lastPrice == 0 { 20 | lastPrice = price 21 | } 22 | vol := int64(v.Vol) 23 | if direction != quotes.TDX_TICK_BUY && direction != quotes.TDX_TICK_SELL { 24 | switch { 25 | case price > lastPrice: 26 | direction = quotes.TDX_TICK_BUY 27 | case price < lastPrice: 28 | direction = quotes.TDX_TICK_SELL 29 | } 30 | } 31 | // 统计内外盘数据 32 | if direction == quotes.TDX_TICK_BUY { 33 | // 买入 34 | summary.OuterVolume += vol 35 | summary.OuterAmount += float64(vol) * price 36 | } else if direction == quotes.TDX_TICK_SELL { 37 | // 卖出 38 | summary.InnerVolume += vol 39 | summary.InnerAmount += float64(vol) * price 40 | } else { 41 | // 可能存在中性盘2, 最近又发现有类型是3, 暂时还是按照中性盘来处理 42 | vn := vol 43 | buyOffset := vn / 2 44 | sellOffset := vn - buyOffset 45 | // 买入 46 | summary.OuterVolume += buyOffset 47 | summary.OuterAmount += float64(buyOffset) * price 48 | // 卖出 49 | summary.InnerVolume += sellOffset 50 | summary.InnerAmount += float64(sellOffset) * price 51 | } 52 | // 计算开盘竞价数据 53 | if tm >= exchange.HistoricalTransactionDataFirstTime && tm < exchange.HistoricalTransactionDataStartTime { 54 | summary.OpenVolume += vol 55 | } 56 | // 计算收盘竞价数据 57 | if tm > exchange.HistoricalTransactionDataFinalBiddingTime && tm <= exchange.HistoricalTransactionDataLastTime { 58 | summary.CloseVolume += vol 59 | } 60 | lastPrice = price 61 | } 62 | f10 := GetL5F10(securityCode, date) 63 | if f10 != nil { 64 | summary.OpenTurnZ = f10.TurnZ(summary.OpenVolume) 65 | summary.CloseTurnZ = f10.TurnZ(summary.CloseVolume) 66 | } 67 | 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /factors/dataset_trans_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/datasource/base" 6 | "testing" 7 | ) 8 | 9 | func TestTransactionOld(t *testing.T) { 10 | code := "sh881200" 11 | date := "2025-02-21" 12 | list := base.GetHistoricalTradingData(code, date) 13 | v := CountInflow(list, code, date) 14 | fmt.Printf("%+v\n", v) 15 | } 16 | 17 | func TestTransaction(t *testing.T) { 18 | code := "sh881200" 19 | date := "2025-02-21" 20 | list := base.CheckoutTransactionData(code, date, true) 21 | v := CountInflow(list, code, date) 22 | fmt.Printf("%+v\n", v) 23 | } 24 | -------------------------------------------------------------------------------- /factors/dataset_xdxr.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "context" 5 | "gitee.com/quant1x/engine/cache" 6 | "gitee.com/quant1x/engine/datasource/base" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | ) 9 | 10 | // DataXdxr 除权除息 11 | type DataXdxr struct { 12 | cache.DataSummary 13 | Date string 14 | Code string 15 | } 16 | 17 | func init() { 18 | summary := __mapDataSets[BaseXdxr] 19 | _ = cache.Register(&DataXdxr{DataSummary: summary}) 20 | } 21 | 22 | func (x *DataXdxr) Clone(date string, code string) DataSet { 23 | summary := __mapDataSets[BaseXdxr] 24 | var dest = DataXdxr{DataSummary: summary, Date: date, Code: code} 25 | return &dest 26 | } 27 | 28 | func (x *DataXdxr) GetDate() string { 29 | return x.Date 30 | } 31 | 32 | func (x *DataXdxr) GetSecurityCode() string { 33 | return x.Code 34 | } 35 | 36 | func (x *DataXdxr) Init(ctx context.Context, date string) error { 37 | _ = ctx 38 | _ = date 39 | return nil 40 | } 41 | 42 | func (x *DataXdxr) Print(code string, date ...string) { 43 | //TODO implement me 44 | panic("implement me") 45 | } 46 | 47 | func (x *DataXdxr) Filename(date, code string) string { 48 | //TODO implement me 49 | _ = code 50 | _ = date 51 | panic("implement me") 52 | } 53 | 54 | func (x *DataXdxr) Update(date string) error { 55 | base.UpdateXdxrInfo(x.GetSecurityCode()) 56 | _ = date 57 | return nil 58 | } 59 | 60 | func (x *DataXdxr) Repair(date string) error { 61 | base.UpdateXdxrInfo(x.GetSecurityCode()) 62 | _ = date 63 | return nil 64 | } 65 | 66 | func (x *DataXdxr) Increase(snapshot quotes.Snapshot) error { 67 | // 除权除息没有增量计算的逻辑 68 | _ = snapshot 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /factors/doc.go: -------------------------------------------------------------------------------- 1 | // Package factors 特征/因子相关的功能 2 | // 3 | // 包括: 基础数据, 基本面, 常用历史特征以及1号策略数据 4 | package factors 5 | -------------------------------------------------------------------------------- /factors/feature.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "errors" 5 | "gitee.com/quant1x/engine/cache" 6 | ) 7 | 8 | const ( 9 | InvalidPeriod = -1 // 无效的周期 10 | InvalidWeight = float64(-99.99) // 无效的权重值 11 | ) 12 | 13 | // Trait 基础的特性 14 | // 15 | // 这也是一个特征, 为啥起这个名字, 自己可以脑补 哈哈~ 16 | type Trait interface { 17 | // FromHistory 从历史数据加载 18 | FromHistory(history History) Feature 19 | // Increase 增量计算 20 | // 用快照增量计算特征 21 | Increase(snapshot QuoteSnapshot) Feature 22 | // ValidateSample 验证样本 23 | ValidateSample() error 24 | } 25 | 26 | // Feature 特征 27 | type Feature interface { 28 | cache.Manifest 29 | cache.Future 30 | // Factory 工厂 31 | Factory(date string, code string) Feature 32 | Trait 33 | } 34 | 35 | // Weight 权重数据类型为64, 实际容纳63个 36 | type Weight = uint64 37 | 38 | const ( 39 | baseFeature cache.Kind = cache.PluginMaskFeature // 特征类型基础编码 40 | ) 41 | 42 | // 登记所有的特征数据 43 | const ( 44 | FeatureF10 = baseFeature + 1 // 特征数据-基本面 45 | FeatureHistory = baseFeature + 2 // 特征数据-历史 46 | FeatureNo1 = baseFeature + 3 // 特征数据-1号策略 47 | FeatureMisc = baseFeature + 4 // 特征数据-Misc 48 | FeatureBreaksThroughBox = baseFeature + 5 // 特征数据-box 49 | FeatureKLineShap = baseFeature + 6 // 特征数据-K线形态等 50 | FeatureInvestmentSentimentMaster = baseFeature + 7 // 狩猎者-情绪周期 51 | FeatureSecuritiesMarginTrading = baseFeature + 8 // 融资融券 52 | ) 53 | 54 | var ( 55 | __mapFeatures = map[cache.Kind]cache.DataSummary{ 56 | FeatureF10: cache.Summary(FeatureF10, cacheL5KeyF10, "基本面", cache.DefaultDataProvider), 57 | FeatureHistory: cache.Summary(FeatureHistory, cacheL5KeyHistory, "历史数据", cache.DefaultDataProvider), 58 | FeatureMisc: cache.Summary(FeatureMisc, cacheL5KeyMisc, "交易数据集合", cache.DefaultDataProvider), 59 | FeatureBreaksThroughBox: cache.Summary(FeatureBreaksThroughBox, cacheL5KeyBox, "有效突破平台", cache.DefaultDataProvider), 60 | FeatureInvestmentSentimentMaster: cache.Summary(FeatureInvestmentSentimentMaster, cacheL5KeyInvestmentSentimentMaster, "情绪大师", cache.DefaultDataProvider), 61 | FeatureSecuritiesMarginTrading: cache.Summary(FeatureSecuritiesMarginTrading, cacheL5KeySecuritiesMarginTrading, "融资融券", cache.DefaultDataProvider), 62 | } 63 | ) 64 | 65 | var ( 66 | ErrInvalidFeatureSample = errors.New("无效的特征数据样本") 67 | ) 68 | -------------------------------------------------------------------------------- /factors/feature_aggregation_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gotdx/securities" 6 | "testing" 7 | ) 8 | 9 | func TestGetL5History(t *testing.T) { 10 | code := "sz000713" 11 | v := GetL5History(code) 12 | fmt.Println(v) 13 | } 14 | 15 | func TestMisc(t *testing.T) { 16 | code := "sh000001" 17 | v := GetL5Misc(code) 18 | fmt.Println(v) 19 | } 20 | 21 | func TestFilterL5Misc(t *testing.T) { 22 | rows := FilterL5Misc(func(v *Misc) bool { 23 | c1 := v.BullPower > v.BearPower 24 | //c2 := v.BullPower > 0 && v.BearPower != 0 25 | c2 := v.PowerTrendPeriod == 1 26 | return c1 && c2 27 | }, "20240205") 28 | for _, v := range rows { 29 | fmt.Println(v.Code, securities.GetStockName(v.Code)) 30 | } 31 | fmt.Println("total:", len(rows)) 32 | } 33 | -------------------------------------------------------------------------------- /factors/feature_box_breaks.tdx: -------------------------------------------------------------------------------- 1 | {T05: 有效突破平台, V1.1.3 2023-08-17} 2 | {倍量1, 以5日均量线的2倍计算} 3 | ICON_B_RATIO:=0.999; 4 | ICON_S_RATIO:=1.029; 5 | VRATIO:=2.00; 6 | BL1:=VOL/REF(VOL,1); 7 | BOXH:=MAX(OPEN,CLOSE); 8 | BOXL:=MIN(OPEN,CLOSE); 9 | 10 | BLN1:=BARSLAST(BL1>=VRATIO AND CLOSE>=OPEN),NODRAW; 11 | 倍量周期:BLN1,NODRAW; 12 | BLN:=IFF(BLN1>=0,BLN1,BLN1),NODRAW; 13 | BLH:=IFF(BLN=0,BOXH,REF(BOXH,BLN)),DOTLINE; 14 | BLL:=IFF(BLN=0,BOXL,REF(BOXL,BLN)),DOTLINE; 15 | {为HHV修复BLN1的值,需要+1} 16 | BLYL:=IFF(BLN=0,HIGH,HHV(HIGH,BLN+1)); 17 | 18 | 倍量H:IFF(BLN=0,REF(BLH,1),BLH),DOTLINE; 19 | 倍量L:IFF(BLN=0,REF(BLL,1),BLL),DOTLINE; 20 | 倍量压力:BLYL,DOTLINE; 21 | 22 | MA3:MA(CLOSE,3),COLORCYAN; 23 | MA5:MA(CLOSE,5),COLOR0CAEE6; 24 | MA10:MA(CLOSE,10); 25 | MA20:MA(CLOSE,20); 26 | 27 | {绘制买入信号} 28 | B:CROSS(CLOSE,倍量H),COLORRED,NODRAW; 29 | DRAWICON(B,LOW*ICON_B_RATIO,1); 30 | 31 | {绘制卖出信号} 32 | S:CROSS(MA3,CLOSE),NODRAW; 33 | DRAWICON(S,HIGH*ICON_S_RATIO,2); 34 | 35 | BLV1:=HHV(VOL,BLN); 36 | BLHV:IFF(BLN=0,VOL,BLV1),NODRAW; 37 | 38 | SL:=VOL/BLHV; 39 | SLN:BARSLAST(SL<=0.5),NODRAW; 40 | 41 | 均价:AMOUNT/VOL/100,COLORWHITE,DOTLINE,NODRAW; 42 | D:=MA5>REF(MA5,1) AND CLOSE>=MA5; 43 | K:=MA5<=REF(MA5,1) OR CLOSE0,FD,-1*FK),NODRAW; -------------------------------------------------------------------------------- /factors/feature_box_breaks_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | 8 | "gitee.com/quant1x/gox/api" 9 | ) 10 | 11 | func TestNewBreaksThrough(t *testing.T) { 12 | code := "sh600600" 13 | code = "sz002043" 14 | code = "sz000638" 15 | code = "600105" 16 | code = "sh603193" 17 | date := "20231012" 18 | v := NewKLineBox(code, date) 19 | data, _ := json.Marshal(v) 20 | text := api.Bytes2String(data) 21 | fmt.Println(text) 22 | } 23 | -------------------------------------------------------------------------------- /factors/feature_box_dkqs.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/utils" 5 | "gitee.com/quant1x/pandas" 6 | . "gitee.com/quant1x/pandas/formula" 7 | ) 8 | 9 | // DuoKongQuShi 多空趋势 10 | type DuoKongQuShi struct { 11 | Col float64 // 多空能量 12 | K0 float64 13 | K float64 14 | D float64 15 | B bool 16 | S bool 17 | } 18 | 19 | // 多空趋势 20 | func computeDuoKongQuShi(OPEN, CLOSE, HIGH, LOW pandas.Series) *DuoKongQuShi { 21 | //{多空趋势, V1.1.2, 2023-09-13} 22 | //{量能柱} 23 | //SCALE:=100; 24 | SCALE := 100 25 | //N:=3; 26 | N := 3 27 | //NN:=MIN(BARSCOUNT(CLOSE),N); 28 | //NN := MIN(BARSCOUNT(CLOSE), N) 29 | NN := N 30 | //CNN:=REF(CLOSE,NN); 31 | CNN := REF(CLOSE, NN) 32 | //CDIFF:=CLOSE-CNN,COLORSTICK; 33 | CDIFF := CLOSE.Sub(CNN) 34 | //FF:=CDIFF/CNN; 35 | FF := CDIFF.Div(CNN) 36 | //MADK:SCALE*FF,COLORSTICK; 37 | MADK := FF.Mul(SCALE) 38 | // 39 | //{多空趋势} 40 | //MAXH:=MAX(HIGH,REF(CLOSE,1)); 41 | //MINL:=MIN(LOW,REF(CLOSE,1)); 42 | MINL := MIN(LOW, REF(CLOSE, 1)) 43 | // 44 | //DIFFK:=IFF(OPEN>=CLOSE,OPEN-CLOSE,OPEN-LOW); 45 | DIFFK := IFF(OPEN.Gte(CLOSE), OPEN.Sub(CLOSE), OPEN.Sub(LOW)) 46 | //TZ1:=OPEN-REF(CLOSE,1)-DIFFK; 47 | TZ1 := OPEN.Sub(REF(CLOSE, 1)).Sub(DIFFK) 48 | //TZ2:=TZ1/OPEN; 49 | TZ2 := TZ1.Div(OPEN) 50 | //K0:SCALE*TZ2; 51 | K0 := TZ2.Mul(SCALE) 52 | //K:ABS(K0),DOTLINE; 53 | K := ABS(K0) 54 | //DIFFD:=IFF(OPEN>=CLOSE,HIGH-OPEN,HIGH-CLOSE); 55 | DIFFD := IFF(OPEN.Gte(CLOSE), HIGH.Sub(OPEN), HIGH.Sub(CLOSE)) 56 | //TD1:=CLOSE-MINL+DIFFD; 57 | TD1 := CLOSE.Sub(MINL).Add(DIFFD) 58 | //TD2:=TD1/CLOSE; 59 | TD2 := TD1.Div(CLOSE) 60 | //D:SCALE*TD2; 61 | D := TD2.Mul(SCALE) 62 | //B:CROSS(D,K); 63 | B := CROSS(D, K) 64 | //S:CROSS(K,D); 65 | S := CROSS(K, D) 66 | //DRAWICON(B,20,1); 67 | //DRAWICON(S,20,2); 68 | dkqs := DuoKongQuShi{ 69 | Col: utils.Float64IndexOf(MADK, -1), 70 | K0: utils.Float64IndexOf(K0, -1), 71 | K: utils.Float64IndexOf(K, -1), 72 | D: utils.Float64IndexOf(D, -1), 73 | B: utils.BoolIndexOf(B, -1), 74 | S: utils.BoolIndexOf(S, -1), 75 | } 76 | return &dkqs 77 | } 78 | -------------------------------------------------------------------------------- /factors/feature_box_dkqs.tdx: -------------------------------------------------------------------------------- 1 | {多空趋势, V1.1.2, 2023-09-13} 2 | {量能柱} 3 | SCALE:=100; 4 | N:=3; 5 | NN:=MIN(BARSCOUNT(CLOSE),N); 6 | CNN:=REF(CLOSE,NN); 7 | CDIFF:=CLOSE-CNN,COLORSTICK; 8 | FF:=CDIFF/CNN; 9 | MADK:SCALE*FF,COLORSTICK; 10 | 11 | {多空趋势} 12 | MAXH:=MAX(HIGH,REF(CLOSE,1)); 13 | MINL:=MIN(LOW,REF(CLOSE,1)); 14 | 15 | DIFFK:=IFF(OPEN>=CLOSE,OPEN-CLOSE,OPEN-LOW); 16 | TZ1:=OPEN-REF(CLOSE,1)-DIFFK; 17 | TZ2:=TZ1/OPEN; 18 | K0:SCALE*TZ2; 19 | K:ABS(K0),DOTLINE; 20 | DIFFD:=IFF(OPEN>=CLOSE,HIGH-OPEN,HIGH-CLOSE); 21 | TD1:=CLOSE-MINL+DIFFD; 22 | TD2:=TD1/CLOSE; 23 | D:SCALE*TD2; 24 | B:CROSS(D,K); 25 | S:CROSS(K,D); 26 | DRAWICON(B,20,1); 27 | DRAWICON(S,20,2); -------------------------------------------------------------------------------- /factors/feature_box_madx.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/utils" 5 | "gitee.com/quant1x/pandas" 6 | . "gitee.com/quant1x/pandas/formula" 7 | ) 8 | 9 | type JuXianDongXiang struct { 10 | Dm0 float64 // 3日线差 11 | Dm1 float64 // 5日线差 12 | Dm2 float64 // 10日线差 13 | Diverging float64 // 均线发散度 14 | B bool // 买入信号 15 | BN int // 连续B信号周期数 16 | } 17 | 18 | // 多空趋势 19 | func computeJuXianDongXiang(OPEN, CLOSE, HIGH, LOW pandas.Series) *JuXianDongXiang { 20 | //{均线动向, V1.0.3, 2023-09-18} 21 | //P0:=3; 22 | P0 := 3 23 | //P1:=5; 24 | P1 := 5 25 | //P2:=10; 26 | P2 := 10 27 | //P3:=20; 28 | P3 := 20 29 | //MX:=CLOSE; 30 | MX := CLOSE 31 | //MA0:=MA(MX,P0); 32 | MA0 := MA(MX, P0) 33 | //MA1:=MA(MX,P1); 34 | MA1 := MA(MX, P1) 35 | //MA2:=MA(MX,P2); 36 | MA2 := MA(MX, P2) 37 | //MA3:=MA(MX,P3); 38 | MA3 := MA(MX, P3) 39 | //DM0:MA0-MA3; 40 | DM0 := MA0.Sub(MA3) 41 | //DM1:MA1-MA3; 42 | DM1 := MA1.Sub(MA3) 43 | //DM2:MA2-MA3; 44 | DM2 := MA2.Sub(MA3) 45 | //X0:=DM0-REF(DM0,1); 46 | X0 := DM0.Sub(REF(DM0, 1)) 47 | //X1:=DM1-REF(DM1,1); 48 | X1 := DM1.Sub(REF(DM1, 1)) 49 | //X2:=DM2-REF(DM2,1); 50 | X2 := DM2.Sub(REF(DM2, 1)) 51 | //DIVERGING:X0+X1+X2; 52 | DIVERGING := X0.Add(X1).Add(X2) 53 | //B:X0>0 AND X1>0 AND X2>0,NODRAW; 54 | B := X0.Gt(0).And(X1.Gt(0)).And(X2.Gt(0)) 55 | //DRAWICON(B,0.01,1); 56 | // BN:BARSLASTCOUNT(B),COLORRED,NODRAW; 57 | BN := BARSLASTCOUNT(B) 58 | madx := JuXianDongXiang{ 59 | Dm0: utils.Float64IndexOf(DM0, -1), 60 | Dm1: utils.Float64IndexOf(DM1, -1), 61 | Dm2: utils.Float64IndexOf(DM2, -1), 62 | Diverging: utils.Float64IndexOf(DIVERGING, -1), 63 | B: utils.BoolIndexOf(B, -1), 64 | BN: utils.IntegerIndexOf(BN, -1), 65 | } 66 | return &madx 67 | } 68 | -------------------------------------------------------------------------------- /factors/feature_box_madx.tdx: -------------------------------------------------------------------------------- 1 | {均线动向, V1.0.4, 2024-06-24} 2 | P0:=3; 3 | P1:=5; 4 | P2:=10; 5 | P3:=20; 6 | MX:=CLOSE; 7 | MA0:=MA(MX,P0); 8 | MA1:=MA(MX,P1); 9 | MA2:=MA(MX,P2); 10 | MA3:=MA(MX,P3); 11 | DM0:MA0-MA3; 12 | DM1:MA1-MA3; 13 | DM2:MA2-MA3; 14 | X0:=DM0-REF(DM0,1); 15 | X1:=DM1-REF(DM1,1); 16 | X2:=DM2-REF(DM2,1); 17 | DIVERGING:X0+X1+X2; 18 | B:X0>0 AND X1>0 AND X2>0,NODRAW; 19 | DRAWICON(B,0.01,1); 20 | BN:BARSLASTCOUNT(B),COLORRED,NODRAW; -------------------------------------------------------------------------------- /factors/feature_box_qsfz.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/utils" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/num" 7 | "gitee.com/quant1x/pandas" 8 | . "gitee.com/quant1x/pandas/formula" 9 | ) 10 | 11 | // QuShiFanZhuan 趋势反转 12 | type QuShiFanZhuan struct { 13 | QSFZ bool // 反转信号 14 | CP float64 // 股价涨幅 15 | CV float64 // 成交量涨幅 16 | VP float64 // 价量比 17 | VP3 float64 // 3日价量比 18 | VP5 float64 // 5日价量比 19 | } 20 | 21 | // 趋势反转 22 | func computeQuShiFanZhuan(date string, OPEN, CLOSE, HIGH, LOW, VOL pandas.Series) *QuShiFanZhuan { 23 | CURRBARSCOUNT := utils.IndexReverse(OPEN) 24 | // {趋势反转, V1.0.7, 2023-09-15} 25 | // MV5:=MA(VOL,5); 26 | MV5 := MA(VOL, 5) 27 | // LB0:VOL/REF(MV5,1),NODRAW; 28 | R1MV5 := REF(MV5, 1) 29 | LB0 := VOL.Div(R1MV5) 30 | // FIX:=IFF(CURRBARSCOUNT=1,FROMOPEN/TOTALFZNUM,1); 31 | minutes := exchange.Minutes(date) 32 | FIX := IFF(CURRBARSCOUNT.Eq(1), float64(minutes)/float64(exchange.CN_DEFAULT_TOTALFZNUM), 1.00) 33 | // LB:LB0/FIX,NODRAW; 34 | LB := LB0.Div(FIX) 35 | // NVOL:LB*REF(MV5,1),NODRAW; 36 | NVOL := R1MV5.Mul(LB) 37 | // CVOL:VOL,NODRAW; 38 | // XVOL:=NVOL; 39 | XVOL := NVOL 40 | // CVX:VOL/REF(VOL,1),NODRAW; 41 | // QSCV:XVOL/REF(VOL,1),NODRAW; 42 | //cv := VOL.Div(REF(VOL, 1)) 43 | cv := XVOL.Div(REF(VOL, 1)) 44 | // QSCP:(CLOSE/REF(CLOSE,1)-1)*100; 45 | cp := CLOSE.Div(REF(CLOSE, 1)).Sub(1.00).Mul(100) 46 | //cp := CLOSE.Div(REF(CLOSE, 1)) 47 | //cp = cp.Sub(1) 48 | //fmt.Println(cp) 49 | // QSVP:QSCP/QSCV; 50 | vp := cp.Div(cv) 51 | // QSVP3:MA(QSVP,3); 52 | vp3 := MA(vp, 3) 53 | // QSVP5:MA(QSVP,5); 54 | vp5 := MA(vp, 5) 55 | // VP20:=MA(QSVP,20); 56 | // B:CROSS(QSVP,QSVP3),NODRAW; 57 | // DRAWICON(B,CLOSE,1); 58 | vpBuy := CROSS(vp, vp3) 59 | fz := num.AnyToBool(vpBuy.IndexOf(-1)) 60 | qsfz := QuShiFanZhuan{ 61 | QSFZ: fz, 62 | CV: utils.Float64IndexOf(cv, -1), 63 | CP: utils.Float64IndexOf(cp, -1), 64 | VP: utils.Float64IndexOf(vp, -1), 65 | VP3: utils.Float64IndexOf(vp3, -1), 66 | VP5: utils.Float64IndexOf(vp5, -1), 67 | } 68 | return &qsfz 69 | } 70 | -------------------------------------------------------------------------------- /factors/feature_box_qsfz.tdx: -------------------------------------------------------------------------------- 1 | {趋势反转, V1.0.9, 2023-09-16} 2 | MV5:=MA(VOL,5); 3 | LB0:VOL/REF(MV5,1),NODRAW; 4 | FIX:=IFF(CURRBARSCOUNT=1,FROMOPEN/TOTALFZNUM,1); 5 | LB:LB0/FIX,NODRAW; 6 | NVOL:LB*REF(MV5,1),NODRAW; 7 | CVOL:VOL,NODRAW; 8 | XVOL:NVOL,NODRAW; 9 | CVX:VOL/REF(VOL,1),NODRAW; 10 | CV:XVOL/REF(VOL,1),NODRAW; 11 | CP:(CLOSE/REF(CLOSE,1)-1)*100; 12 | VP:CP/CV; 13 | VP3:MA(VP,3); 14 | VP5:MA(VP,5); 15 | VP20:=MA(VP,20); 16 | B:CROSS(VP,VP3),NODRAW,COLORRED; 17 | DRAWICON(B,VP,1); -------------------------------------------------------------------------------- /factors/feature_box_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gitee.com/quant1x/engine/cache" 7 | "gitee.com/quant1x/exchange" 8 | "gitee.com/quant1x/gox/api" 9 | "testing" 10 | ) 11 | 12 | func TestBox_basic(t *testing.T) { 13 | code := "002766" 14 | date := "2024-06-24" 15 | cacheDate, featureDate := cache.CorrectDate(date) 16 | code = exchange.CorrectSecurityCode(code) 17 | box := NewBox(cacheDate, code) 18 | box.Update(code, cacheDate, featureDate, true) 19 | data, _ := json.Marshal(box) 20 | text := api.Bytes2String(data) 21 | fmt.Println(text) 22 | } 23 | -------------------------------------------------------------------------------- /factors/feature_f10_base.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/datasource/base" 5 | "gitee.com/quant1x/gotdx/quotes" 6 | "gitee.com/quant1x/gotdx/securities" 7 | "gitee.com/quant1x/gox/api" 8 | "gitee.com/quant1x/gox/concurrent" 9 | ) 10 | 11 | func checkoutCapital(list []quotes.XdxrInfo, date string) *quotes.XdxrInfo { 12 | for _, v := range list { 13 | if v.IsCapitalChange() && date >= v.Date { 14 | return &v 15 | } 16 | } 17 | return nil 18 | } 19 | 20 | type f10SecurityInfo struct { 21 | TotalCapital float64 22 | Capital float64 23 | VolUnit int 24 | DecimalPoint int 25 | Name_ string 26 | IpoDate string 27 | SubNew bool 28 | UpdateDate string 29 | } 30 | 31 | var ( 32 | //__mapListingDate = map[string]string{} 33 | __mapListingDate = concurrent.NewHashMap[string, string]() 34 | ) 35 | 36 | func checkoutSecurityBasicInfo(securityCode, featureDate string) f10SecurityInfo { 37 | list := base.GetCacheXdxrList(securityCode) 38 | api.SliceSort(list, func(a, b quotes.XdxrInfo) bool { 39 | return a.Date > b.Date 40 | }) 41 | // 计算流通盘 42 | cover := checkoutCapital(list, featureDate) 43 | var f10 f10SecurityInfo 44 | if cover != nil { 45 | f10.TotalCapital = cover.HouZongGuBen * 10000 46 | f10.Capital = cover.HouLiuTong * 10000 47 | } else { 48 | f10.Capital, f10.TotalCapital, f10.IpoDate, f10.UpdateDate = getFinanceInfo(securityCode, featureDate) 49 | } 50 | if len(f10.IpoDate) == 0 { 51 | ipoDate, ok := __mapListingDate.Get(securityCode) 52 | if !ok { 53 | ipoDate = getIpoDate(securityCode, featureDate) 54 | } 55 | f10.IpoDate = ipoDate 56 | if len(f10.IpoDate) > 0 { 57 | __mapListingDate.Set(securityCode, f10.IpoDate) 58 | } 59 | } 60 | if len(f10.UpdateDate) == 0 || f10.UpdateDate > featureDate { 61 | f10.UpdateDate = featureDate 62 | } 63 | 64 | if len(f10.IpoDate) > 0 { 65 | f10.SubNew = IsSubNewStockByIpoDate(securityCode, f10.IpoDate, featureDate) 66 | } 67 | 68 | securityInfo, found := securities.CheckoutSecurityInfo(securityCode) 69 | if found { 70 | f10.VolUnit = int(securityInfo.VolUnit) 71 | f10.DecimalPoint = int(securityInfo.DecimalPoint) 72 | f10.Name_ = securityInfo.Name 73 | } else { 74 | f10.VolUnit = 100 75 | f10.DecimalPoint = 2 76 | f10.Name_ = securities.GetStockName(securityCode) 77 | } 78 | 79 | return f10 80 | } 81 | -------------------------------------------------------------------------------- /factors/feature_f10_capital.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/datasource/dfcf" 5 | ) 6 | 7 | // ComputeFreeCapital 计算自由流通股本 8 | func ComputeFreeCapital(holderList []dfcf.CirculatingShareholder, capital float64) (top10Capital, freeCapital, capitalChanged, increaseRatio, reductionRatio float64) { 9 | increase := 0 10 | reduce := 0 11 | for k, holder := range holderList { 12 | top10Capital += float64(holder.HoldNum) 13 | capitalChanged += float64(holder.HoldNumChange) 14 | if holder.HoldNumChange >= 0 { 15 | increase += holder.HoldNumChange 16 | } else { 17 | reduce += holder.HoldNumChange 18 | } 19 | if k >= 10 { 20 | continue 21 | } 22 | if holder.FreeHoldNumRatio >= 1.00 && holder.IsHoldOrg == "1" { 23 | capital -= float64(holder.HoldNum) 24 | } 25 | } 26 | increaseRatio = 100.0000 * (float64(increase) / top10Capital) 27 | reductionRatio = 100.0000 * (float64(reduce) / top10Capital) 28 | return top10Capital, capital, capitalChanged, increaseRatio, reductionRatio 29 | } 30 | -------------------------------------------------------------------------------- /factors/feature_f10_notice.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/datasource/dfcf" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/gox/api" 7 | "gitee.com/quant1x/gox/logger" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type companyNotice struct { 13 | Increase int 14 | Reduce int 15 | Risk int 16 | RiskKeywords string 17 | } 18 | 19 | // 上市公司公告 20 | func getOneNotice(securityCode, currentDate string) (notice companyNotice) { 21 | if !exchange.AssertStockBySecurityCode(securityCode) { 22 | return 23 | } 24 | now, _ := api.ParseTime(currentDate) 25 | now = now.AddDate(0, -1, 0) 26 | beginDate := now.Format(time.DateOnly) 27 | endDate := currentDate 28 | //list, pages, err := dfcf.StockNotices(securityCode, beginDate, endDate, 1) 29 | //if pages < 1 { 30 | // return 31 | //} 32 | pagesCount := 1 33 | var tmpNotice *dfcf.NoticeDetail = nil 34 | for pageNo := 1; pageNo < pagesCount+1; pageNo++ { 35 | list, pages, err := dfcf.StockNotices(securityCode, beginDate, endDate, pageNo) 36 | if err != nil || pages < 1 { 37 | logger.Errorf("notice: code=%s, %s=>%s, %s", securityCode, beginDate, endDate, err) 38 | break 39 | } 40 | if pagesCount < pages { 41 | pagesCount = pages 42 | } 43 | 44 | count := len(list) 45 | if count == 0 { 46 | break 47 | } 48 | for _, v := range list { 49 | if tmpNotice != nil { 50 | tmpNotice.Name = v.Name 51 | if tmpNotice.NoticeDate < v.NoticeDate { 52 | tmpNotice.DisplayTime = v.DisplayTime 53 | tmpNotice.NoticeDate = v.NoticeDate 54 | } 55 | // 使用最近的标题 56 | tmpNotice.Title = v.Title 57 | keywords := tmpNotice.Keywords 58 | if len(v.Keywords) > 0 { 59 | if len(keywords) == 0 { 60 | keywords += v.Keywords 61 | } else { 62 | keywords += "," + v.Keywords 63 | } 64 | } 65 | tmpArr := strings.Split(keywords, ",") 66 | //api.Unique(api.StringSlice{P: &tmpArr}) 67 | tmpArr = api.Unique(tmpArr) 68 | tmpNotice.Keywords = strings.Join(tmpArr, ",") 69 | tmpNotice.Increase += v.Increase 70 | tmpNotice.Reduce += v.Reduce 71 | tmpNotice.HolderChange += v.HolderChange 72 | tmpNotice.Risk += v.Risk 73 | } else { 74 | tmpNotice = &v 75 | } 76 | } 77 | if count < dfcf.EastmoneyNoticesPageSize { 78 | break 79 | } 80 | } 81 | if tmpNotice != nil { 82 | notice.Increase = tmpNotice.Increase 83 | notice.Reduce = tmpNotice.Reduce 84 | notice.Risk = tmpNotice.Risk 85 | notice.RiskKeywords = tmpNotice.Keywords 86 | } 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /factors/feature_f10_reports.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/engine/datasource/dfcf" 6 | "gitee.com/quant1x/exchange" 7 | "gitee.com/quant1x/gox/api" 8 | "gitee.com/quant1x/gox/logger" 9 | ) 10 | 11 | var ( 12 | __mapQuarterlyReports = map[string]dfcf.QuarterlyReport{} 13 | ) 14 | 15 | func loadQuarterlyReports(date string) { 16 | var allReports []dfcf.QuarterlyReport 17 | _, qEnd := api.GetQuarterDayByDate(date) 18 | filename := cache.ReportsFilename(qEnd) 19 | err := api.CsvToSlices(filename, &allReports) 20 | if err != nil { 21 | logger.Errorf("cache %s failed, error: %+v", filename, err) 22 | } 23 | if len(allReports) > 0 { 24 | for _, v := range allReports { 25 | __mapQuarterlyReports[v.SecurityCode] = v 26 | } 27 | } 28 | } 29 | 30 | func getQuarterlyYearQuarter(date string) string { 31 | q, _, _ := api.GetQuarterByDate(date, 1) 32 | return q 33 | } 34 | 35 | // 季报概要 36 | type quarterlyReportSummary struct { 37 | QDate string 38 | BPS float64 39 | BasicEPS float64 40 | TotalOperateIncome float64 41 | DeductBasicEPS float64 42 | } 43 | 44 | func (q *quarterlyReportSummary) Assign(v dfcf.QuarterlyReport) { 45 | q.BPS = v.BPS 46 | q.BasicEPS = v.BasicEPS 47 | q.TotalOperateIncome = v.TotalOperateIncome 48 | q.DeductBasicEPS = v.DeductBasicEPS 49 | q.QDate = v.QDATE 50 | } 51 | 52 | func getQuarterlyReportSummary(securityCode, date string) quarterlyReportSummary { 53 | var summary quarterlyReportSummary 54 | if exchange.AssertIndexBySecurityCode(securityCode) { 55 | return summary 56 | } 57 | v, ok := __mapQuarterlyReports[securityCode] 58 | if ok { 59 | summary.Assign(v) 60 | return summary 61 | } 62 | q := dfcf.GetCacheQuarterlyReportsBySecurityCode(securityCode, date) 63 | if q != nil { 64 | summary.Assign(*q) 65 | } 66 | return summary 67 | } 68 | -------------------------------------------------------------------------------- /factors/feature_f10_shareholder.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/datasource/base" 5 | "gitee.com/quant1x/engine/datasource/dfcf" 6 | "gitee.com/quant1x/exchange" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | "gitee.com/quant1x/gox/api" 9 | ) 10 | 11 | type top10ShareHolder struct { 12 | Code string 13 | FreeCapital float64 14 | Top10Capital float64 15 | Top10Change float64 16 | ChangeCapital float64 17 | IncreaseRatio float64 18 | ReductionRatio float64 19 | } 20 | 21 | func checkoutShareHolder(securityCode, featureDate string) *top10ShareHolder { 22 | xdxrs := base.GetCacheXdxrList(securityCode) 23 | api.SliceSort(xdxrs, func(a, b quotes.XdxrInfo) bool { 24 | return a.Date > b.Date 25 | }) 26 | xdxrInfo := checkoutCapital(xdxrs, featureDate) 27 | if xdxrInfo != nil && exchange.AssertStockBySecurityCode(securityCode) { 28 | list := dfcf.GetCacheShareHolder(securityCode, featureDate) 29 | capital := xdxrInfo.HouLiuTong * 10000 30 | totalCapital := xdxrInfo.HouZongGuBen * 10000 31 | top10Capital, freeCapital, capitalChanged, increaseRatio, reductionRatio := ComputeFreeCapital(list, capital) 32 | if freeCapital < 0 { 33 | top10Capital, freeCapital, capitalChanged, increaseRatio, reductionRatio = ComputeFreeCapital(list, totalCapital) 34 | } 35 | frontList := dfcf.GetCacheShareHolder(securityCode, featureDate, 2) 36 | frontTop10Capital, _, _, _, _ := ComputeFreeCapital(frontList, totalCapital) 37 | shareHolder := top10ShareHolder{ 38 | Code: securityCode, 39 | FreeCapital: freeCapital, 40 | Top10Capital: top10Capital, 41 | Top10Change: top10Capital - frontTop10Capital, 42 | ChangeCapital: capitalChanged, 43 | IncreaseRatio: increaseRatio, 44 | ReductionRatio: reductionRatio, 45 | } 46 | return &shareHolder 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /factors/feature_f10_subnew.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/exchange" 5 | "gitee.com/quant1x/gotdx/quotes" 6 | "gitee.com/quant1x/gox/api" 7 | ) 8 | 9 | const ( 10 | SubNewStockYears = 1 // 次新股几年内 11 | ) 12 | 13 | // 检查在date之前是否存在除权除息 14 | func checkXdxr(list []quotes.XdxrInfo, date string) *quotes.XdxrInfo { 15 | for _, v := range list { 16 | if v.Category == 1 && date >= v.Date { 17 | return &v 18 | } 19 | } 20 | return nil 21 | } 22 | 23 | //// IsSubNewStock 检查是否次新股 24 | //func IsSubNewStock(securityCode, date string) bool { 25 | // date = trading.FixTradeDate(date) 26 | // securityCode = proto.CorrectSecurityCode(securityCode) 27 | // f10 := flash.GetL5F10(securityCode, date) 28 | // if f10 == nil { 29 | // return false 30 | // } 31 | // return IsSubNewStockByIpoDate(securityCode, f10.IpoDate, date) 32 | //} 33 | 34 | // IsSubNewStockByIpoDate 检查是否次新股 35 | func IsSubNewStockByIpoDate(securityCode, ipoDate, date string) bool { 36 | ipoDate = exchange.FixTradeDate(ipoDate) 37 | date = exchange.FixTradeDate(date) 38 | listingDate, err := api.ParseTime(ipoDate) 39 | if err != nil { 40 | return false 41 | } 42 | tm := listingDate.AddDate(SubNewStockYears, 0, 0) 43 | after := tm.Format(exchange.TradingDayDateFormat) 44 | if date >= after { 45 | return false 46 | } 47 | //xdxrs := base.GetCacheXdxrList(securityCode) 48 | //if len(xdxrs) == 0 { 49 | // return false 50 | //} 51 | //api.SliceSort(xdxrs, func(a, b quotes.XdxrInfo) bool { 52 | // return a.Date > b.Date 53 | //}) 54 | //xdxrInfo := checkXdxr(xdxrs, after) 55 | //if xdxrInfo == nil { 56 | // return true 57 | //} 58 | 59 | return true 60 | } 61 | -------------------------------------------------------------------------------- /factors/feature_f10_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "gitee.com/quant1x/engine/cache" 8 | "gitee.com/quant1x/gox/api" 9 | "testing" 10 | ) 11 | 12 | func TestF10(t *testing.T) { 13 | date := "2024-04-10" 14 | q := getQuarterlyYearQuarter(date) 15 | fmt.Println(q) 16 | cacheDate, featureDate := cache.CorrectDate(date) 17 | //cacheDate := "2023-09-28" 18 | //featureDate := date 19 | code := "sh600105" 20 | code = "sh000001" 21 | code = "sh600859" 22 | code = "sz002685" 23 | code = "sh603158" 24 | code = "sh600178" 25 | code = "sh880941" 26 | code = "sh600016" 27 | f10 := NewF10(cacheDate, code) 28 | //barIndex := 1 29 | ctx := context.Background() 30 | f10.Init(ctx, featureDate) 31 | f10.Repair(code, cacheDate, featureDate, true) 32 | data, _ := json.Marshal(f10) 33 | text := api.Bytes2String(data) 34 | fmt.Println(text) 35 | } 36 | -------------------------------------------------------------------------------- /factors/feature_f10_utils.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/datasource/base" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/gotdx" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | "gitee.com/quant1x/gox/logger" 9 | "gitee.com/quant1x/num" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | // 获取财务数据 15 | func getFinanceInfo(securityCode, featureDate string) (capital, totalCapital float64, ipoDate, updateDate string) { 16 | basicDate := uint32(num.AnyToInt64(exchange.MARKET_CN_FIRST_DATE)) 17 | for i := 0; i < quotes.DefaultRetryTimes; i++ { 18 | securityCode := exchange.CorrectSecurityCode(securityCode) 19 | tdxApi := gotdx.GetTdxApi() 20 | info, err := tdxApi.GetFinanceInfo(securityCode) 21 | if err != nil { 22 | logger.Error(err) 23 | gotdx.ReOpen() 24 | } 25 | if info != nil { 26 | if info.LiuTongGuBen > 0 && info.ZongGuBen > 0 { 27 | capital = info.LiuTongGuBen 28 | totalCapital = info.ZongGuBen 29 | } 30 | if info.IPODate >= basicDate { 31 | ipoDate = strconv.FormatInt(int64(info.IPODate), 10) 32 | ipoDate = exchange.FixTradeDate(ipoDate) 33 | } else { 34 | ipoDate = getIpoDate(securityCode, featureDate) 35 | } 36 | if info.UpdatedDate >= basicDate { 37 | updateDate = strconv.FormatInt(int64(info.UpdatedDate), 10) 38 | updateDate = exchange.FixTradeDate(updateDate) 39 | } 40 | break 41 | } else if i+1 < quotes.DefaultRetryTimes { 42 | time.Sleep(time.Millisecond * 10) 43 | } 44 | } 45 | return 46 | } 47 | 48 | func getIpoDate(securityCode, featureDate string) (ipoDate string) { 49 | // IPO日期不存在, 从日K线第一条记录获取 50 | kls := base.CheckoutKLines(securityCode, featureDate) 51 | if len(kls) > 0 { 52 | ipoDate = kls[0].Date 53 | } 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /factors/feature_history_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gitee.com/quant1x/engine/cache" 7 | "gitee.com/quant1x/exchange" 8 | "gitee.com/quant1x/gox/api" 9 | "testing" 10 | ) 11 | 12 | func TestHistory(t *testing.T) { 13 | code := "300956" 14 | code = "300093" 15 | code = "301389" 16 | code = "000751" 17 | code = "301129" 18 | code = "002962" 19 | code = "600600" 20 | date := "2025-05-30" 21 | cacheDate, featureDate := cache.CorrectDate(date) 22 | code = exchange.CorrectSecurityCode(code) 23 | history := NewHistory(cacheDate, code) 24 | history.Update(code, cacheDate, featureDate, true) 25 | data, _ := json.Marshal(history) 26 | text := api.Bytes2String(data) 27 | fmt.Println(text) 28 | } 29 | -------------------------------------------------------------------------------- /factors/feature_ism.tdx: -------------------------------------------------------------------------------- 1 | { 2 | 策略: 情绪大师 3 | 编码: S8 4 | 版本: V1.1.0 5 | 日期: 2023-12-30 6 | 7 | 变量前缀说明: 8 | P-周期 9 | O-观察对象 10 | C-条件 11 | X-临时变量 12 | D-差值 13 | } 14 | PN:30,COLORWHITE,NODRAW; 15 | R1CLOSE:=REF(CLOSE,1); 16 | {剔除ST} 17 | CST:=NOT(NAMELIKE('S') OR NAMELIKE('*S')) AND VOL>1; 18 | {涨跌幅} 19 | ZDF:=IFF(INBLOCK('创业板'), 0.2, IFF(INBLOCK('科创板'),0.2, IFF(INBLOCK('ST板块'), 0.05, IFF(INBLOCK('北证A股'),0.3,0.1)))); 20 | {涨停价} 21 | ZTJ:=ZTPRICE(R1CLOSE,ZDF); 22 | {是否涨停} 23 | CZT:=CLOSE=ZTJ; 24 | {周期内涨停板数} 25 | BN:COUNT(CZT,PN),COLORYELLOW,NODRAW; 26 | {PN周期内首次涨停} 27 | FTZ:=BARSSINCEN(CZT,PN); 28 | {首板距离现在的天数} 29 | TN:FTZ+1,COLORWHITE,NODRAW; 30 | 天:TN,COLORYELLOW,NODRAW; 31 | 板:BN,COLORRED,NODRAW; 32 | CUP:=CLOSE>R1CLOSE; 33 | CPING:=CLOSE=R1CLOSE; 34 | CDOWN:=CLOSE 0 { 89 | return nil 90 | } 91 | return ErrInvalidFeatureSample 92 | } 93 | -------------------------------------------------------------------------------- /factors/feature_rzrq_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gitee.com/quant1x/engine/cache" 7 | "gitee.com/quant1x/exchange" 8 | "gitee.com/quant1x/gox/api" 9 | "testing" 10 | ) 11 | 12 | func TestFeatureSecuritiesMarginTrading(t *testing.T) { 13 | code := "600580" 14 | code = exchange.CorrectSecurityCode(code) 15 | date := "2025-03-07" 16 | cacheDate, featureDate := cache.CorrectDate(date) 17 | feature := NewSecuritiesMarginTrading(date, code) 18 | feature.Init(nil, featureDate) 19 | feature.Update(code, cacheDate, featureDate, false) 20 | data, _ := json.Marshal(feature) 21 | text := api.Bytes2String(data) 22 | fmt.Println(text) 23 | } 24 | -------------------------------------------------------------------------------- /factors/feature_test.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | "unsafe" 9 | ) 10 | 11 | //func TestNewDataBuilder(t *testing.T) { 12 | // date := "2023-07-04" 13 | // v := NewDataBuilder("test", date, nil) 14 | // fmt.Println(v) 15 | //} 16 | 17 | // typelinks2 for 1.7 ~ 18 | // 19 | //go:linkname typelinks2 reflect.typelinks 20 | func typelinks2() (sections []unsafe.Pointer, offset [][]int32) 21 | 22 | //go:linkname resolveTypeOff reflect.resolveTypeOff 23 | func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer 24 | 25 | var typeMaps map[string]reflect.Type 26 | var packages map[string]map[string]reflect.Type 27 | 28 | type emptyInterface struct { 29 | typ unsafe.Pointer 30 | word unsafe.Pointer 31 | } 32 | 33 | func loadGoTypes() { 34 | fia := reflect.TypeOf((*Feature)(nil)).Elem() 35 | var obj interface{} = reflect.TypeOf(0) 36 | sections, offset := typelinks2() 37 | for i, offs := range offset { 38 | rodata := sections[i] 39 | for _, off := range offs { 40 | (*emptyInterface)(unsafe.Pointer(&obj)).word = resolveTypeOff(unsafe.Pointer(rodata), off) 41 | typ := obj.(reflect.Type) 42 | if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct { 43 | loadedType := typ.Elem() 44 | pkgTypes := packages[loadedType.PkgPath()] 45 | if pkgTypes == nil { 46 | pkgTypes = map[string]reflect.Type{} 47 | packages[loadedType.PkgPath()] = pkgTypes 48 | } 49 | typeName := loadedType.String() 50 | //fmt.Println(typeName, "==>", loadedType.PkgPath()) 51 | typeMaps[typeName] = loadedType 52 | pkgTypes[loadedType.Name()] = loadedType 53 | //if loadedType.Implements(Feature) { 54 | // fmt.Println(typeName, "==>", loadedType.PkgPath()) 55 | //} 56 | pkgPath := loadedType.PkgPath() 57 | if strings.HasPrefix(pkgPath, "gitee.com/quant1x/engine") { 58 | //fmt.Println(typeName, "==>", loadedType.PkgPath()) 59 | if reflect.PtrTo(loadedType).Implements(fia) { 60 | fmt.Println("found", pkgPath, "==>", loadedType.Name()) 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | func TestInterface(t *testing.T) { 69 | typeMaps = make(map[string]reflect.Type) 70 | packages = make(map[string]map[string]reflect.Type) 71 | loadGoTypes() 72 | } 73 | -------------------------------------------------------------------------------- /factors/feature_utils.go: -------------------------------------------------------------------------------- 1 | package factors 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/gotdx/securities" 6 | "time" 7 | ) 8 | 9 | // GetTimestamp 时间戳 10 | // 11 | // 格式: YYYY-MM-DD hh:mm:ss.SSS 12 | func GetTimestamp() string { 13 | now := time.Now() 14 | return now.Format(cache.TimeStampMilli) 15 | } 16 | 17 | // PriceDigits 获取证券标的价格保留小数点后几位 18 | // 19 | // 默认范围2, 即小数点后2位 20 | func PriceDigits(securityCode string) int { 21 | securityInfo, ok := securities.CheckoutSecurityInfo(securityCode) 22 | if !ok { 23 | return 2 24 | } 25 | return int(securityInfo.DecimalPoint) 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gitee.com/quant1x/engine 2 | 3 | go 1.24 4 | 5 | require ( 6 | gitee.com/quant1x/exchange v0.6.3 7 | gitee.com/quant1x/gotdx v1.23.5 8 | gitee.com/quant1x/gox v1.22.11 9 | gitee.com/quant1x/num v0.4.6 10 | gitee.com/quant1x/pandas v1.5.1 11 | gitee.com/quant1x/pkg v0.5.1 12 | gitee.com/quant1x/ta-lib v0.8.5 13 | github.com/bits-and-blooms/bitset v1.22.0 14 | github.com/fatih/color v1.18.0 15 | github.com/go-echarts/go-echarts/v2 v2.5.2 16 | github.com/klauspost/cpuid/v2 v2.2.10 17 | github.com/sevlyar/go-daemon v0.1.6 18 | github.com/spf13/cobra v1.9.1 19 | golang.org/x/sys v0.31.0 20 | google.golang.org/protobuf v1.36.5 21 | ) 22 | 23 | require ( 24 | github.com/dlclark/regexp2 v1.11.5 // indirect 25 | github.com/fsnotify/fsnotify v1.8.0 // indirect 26 | github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect 27 | github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect 28 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 29 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect 30 | github.com/mattn/go-colorable v0.1.14 // indirect 31 | github.com/mattn/go-isatty v0.0.20 // indirect 32 | github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/spf13/pflag v1.0.6 // indirect 35 | golang.org/x/text v0.23.0 // indirect 36 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /labs/bitmap_test.go: -------------------------------------------------------------------------------- 1 | package labs 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestBitmap(t *testing.T) { 9 | array := [...]uint64{0, 6, 3, 7, 2, 8, 1, 4} 10 | 11 | var maxNum uint64 = 9 12 | bm := NewBitmap(maxNum) 13 | 14 | for _, v := range array { 15 | bm.Set(v) 16 | } 17 | bm.Set(5) 18 | bm.Set(17) 19 | fmt.Println(bm.IsFully()) 20 | fmt.Println(bm.IsEmpty()) 21 | fmt.Println("bitmap 中存在的数字:") 22 | fmt.Println(bm.GetData()) 23 | fmt.Println("bitmap 中的二进制串") 24 | fmt.Println(bm.String()) 25 | fmt.Println("bitmap 中的数字个数:", bm.Count()) 26 | fmt.Println("bitmap size:", bm.Size()) 27 | fmt.Println("Test(0):", bm.Test(0)) 28 | bm.Reset(5) 29 | fmt.Println(bm.String()) 30 | fmt.Println("Test(5):", bm.Test(5)) 31 | fmt.Println(bm.GetData()) 32 | } 33 | -------------------------------------------------------------------------------- /labs/reflect.go: -------------------------------------------------------------------------------- 1 | package labs 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/factors" 6 | "reflect" 7 | "strings" 8 | "sync" 9 | "unsafe" 10 | ) 11 | 12 | //go:linkname typelinks2 reflect.typelinks 13 | func typelinks2() (sections []unsafe.Pointer, offset [][]int32) 14 | 15 | //go:linkname resolveTypeOff reflect.resolveTypeOff 16 | func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer 17 | 18 | var ( 19 | typeOnce sync.Once 20 | types = map[string]reflect.Type{} 21 | packages = map[string]map[string]reflect.Type{} 22 | ) 23 | 24 | type emptyInterface struct { 25 | typ unsafe.Pointer 26 | word unsafe.Pointer 27 | } 28 | 29 | func loadGoTypes() { 30 | fia := reflect.TypeOf((*factors.Feature)(nil)).Elem() 31 | var obj interface{} = reflect.TypeOf(0) 32 | sections, offset := typelinks2() 33 | for i, offs := range offset { 34 | rodata := sections[i] 35 | for _, off := range offs { 36 | (*emptyInterface)(unsafe.Pointer(&obj)).word = resolveTypeOff(unsafe.Pointer(rodata), off) 37 | typ := obj.(reflect.Type) 38 | if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct { 39 | loadedType := typ.Elem() 40 | pkgTypes := packages[loadedType.PkgPath()] 41 | if pkgTypes == nil { 42 | pkgTypes = map[string]reflect.Type{} 43 | packages[loadedType.PkgPath()] = pkgTypes 44 | } 45 | //types[typeString] = loadedType 46 | pkgPath := loadedType.PkgPath() 47 | typeName := loadedType.Name() 48 | //typeString := loadedType.String() 49 | //fmt.Println(pkgPath, "=>", typeString, "=>", typeName) 50 | structName := fmt.Sprintf("%s.%s", pkgPath, typeName) 51 | types[structName] = loadedType 52 | pkgTypes[loadedType.Name()] = loadedType 53 | if strings.HasPrefix(pkgPath, "gitee.com/quant1x/engine") { 54 | //fmt.Println(structName, "==>", loadedType.PkgPath()) 55 | if reflect.PtrTo(loadedType).Implements(fia) { 56 | fmt.Println("found", pkgPath, "==>", loadedType.String(), "==>", structName) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | func lazyInit() { 65 | loadGoTypes() 66 | } 67 | 68 | func FindImplements(u reflect.Type) (list []reflect.Type) { 69 | typeOnce.Do(lazyInit) 70 | for name, t := range types { 71 | //fmt.Println(name) 72 | if reflect.PtrTo(t).Implements(u) { 73 | list = append(list, t) 74 | } 75 | _ = name 76 | } 77 | return 78 | } 79 | -------------------------------------------------------------------------------- /labs/reflect_test.go: -------------------------------------------------------------------------------- 1 | package labs 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/factors" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestReflect(t *testing.T) { 11 | //lazyInit() 12 | fia := reflect.TypeOf((*factors.Feature)(nil)).Elem() 13 | v := FindImplements(fia) 14 | fmt.Println(v) 15 | } 16 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/command" 6 | "gitee.com/quant1x/engine/config" 7 | _ "gitee.com/quant1x/engine/strategies" 8 | "gitee.com/quant1x/engine/utils" 9 | "gitee.com/quant1x/gox/logger" 10 | "gitee.com/quant1x/gox/runtime" 11 | _ "net/http/pprof" 12 | "os" 13 | "runtime/pprof" 14 | "time" 15 | ) 16 | 17 | // 系统构建时传入的值 18 | // go build -ldflags "-X 'main.MinVersion=${version}'" 19 | 20 | var ( 21 | MinVersion = utils.InvalidVersion // 应用版本号 22 | tdxVersion = utils.InvalidVersion // tdx api版本号 23 | ) 24 | 25 | var ( 26 | cpuProfile = "./cpu.pprof" 27 | memProfile = "./mem.pprof" 28 | ) 29 | 30 | func resetVersions() { 31 | if MinVersion == utils.InvalidVersion { 32 | MinVersion = utils.CurrentVersion() 33 | } 34 | if tdxVersion == utils.InvalidVersion { 35 | tdxVersion = utils.RequireVersion("gitee.com/quant1x/gotdx") 36 | } 37 | } 38 | 39 | // 更新基础数据,特征,执行策略,回测等功能入口 40 | func main() { 41 | if config.PprofEnable() { 42 | fCpu, err := os.Create(cpuProfile) 43 | if err != nil { 44 | logger.Fatal(err) 45 | } 46 | _ = pprof.StartCPUProfile(fCpu) 47 | defer pprof.StopCPUProfile() 48 | } 49 | mainStart := time.Now() 50 | resetVersions() 51 | defer func() { 52 | runtime.CatchPanic("") 53 | elapsedTime := time.Since(mainStart) / time.Millisecond 54 | fmt.Printf("\n总耗时: %.3fs\n", float64(elapsedTime)/1000) 55 | }() 56 | 57 | // stock模块内的更新版本号 58 | command.UpdateApplicationVersion(MinVersion) 59 | runtime.GoMaxProcs() 60 | 61 | // 命令字 62 | rootCommand := command.GlobalFlags() 63 | _ = rootCommand.Execute() 64 | if config.PprofEnable() { 65 | fMem, err := os.Create(memProfile) 66 | if err != nil { 67 | logger.Fatal(err) 68 | } 69 | _ = pprof.WriteHeapProfile(fMem) 70 | _ = fMem.Close() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /market/doc.go: -------------------------------------------------------------------------------- 1 | // Package market 二级市场相关的功能性函数 2 | package market 3 | -------------------------------------------------------------------------------- /market/ignore.go: -------------------------------------------------------------------------------- 1 | package market 2 | 3 | import ( 4 | "gitee.com/quant1x/gotdx/securities" 5 | "strings" 6 | ) 7 | 8 | // IsNeedIgnore 需要忽略的个股 9 | // 10 | // 检测需要的剔除ST、退市和摘牌的个股 11 | func IsNeedIgnore(securityCode string) bool { 12 | securityInfo, ok := securities.CheckoutSecurityInfo(securityCode) 13 | if !ok { 14 | // 没找到, 忽略 15 | return true 16 | } 17 | name := strings.ToUpper(securityInfo.Name) 18 | if strings.Contains(name, "ST") { 19 | // ST标志, 忽略 20 | return true 21 | } 22 | if strings.Contains(name, "退") { 23 | // 退市标志, 忽略 24 | return true 25 | } 26 | if strings.Contains(name, "摘牌") { 27 | // 摘牌标志, 忽略 28 | return true 29 | } 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /market/market_test.go: -------------------------------------------------------------------------------- 1 | package market 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/exchange" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestGetCodeList(t *testing.T) { 11 | codes := GetCodeList() 12 | fmt.Println(len(codes)) 13 | codes = GetStockCodeList() 14 | fmt.Println(len(codes)) 15 | } 16 | 17 | func TestPriceLimit(t *testing.T) { 18 | code := "sh603598" 19 | lastClose := 19.00 20 | up, down := PriceLimit(code, lastClose) 21 | fmt.Println(up, down) 22 | } 23 | 24 | func TestGetQmtCodeList(t *testing.T) { 25 | batchMax := 1000 26 | codes := GetStockCodeList() 27 | codes = codes[0:batchMax] 28 | var list []string 29 | for _, v := range codes { 30 | _, mflag, symbol := exchange.DetectMarket(v) 31 | securityCode := fmt.Sprintf("%s.%s", symbol, strings.ToUpper(mflag)) 32 | list = append(list, securityCode) 33 | } 34 | fmt.Println(strings.Join(list, ",")) 35 | _ = batchMax 36 | } 37 | -------------------------------------------------------------------------------- /market/sentiments.go: -------------------------------------------------------------------------------- 1 | package market 2 | 3 | import ( 4 | "gitee.com/quant1x/exchange" 5 | "gitee.com/quant1x/gotdx" 6 | "gitee.com/quant1x/gotdx/quotes" 7 | "gitee.com/quant1x/gox/logger" 8 | "gitee.com/quant1x/num" 9 | ) 10 | 11 | const ( 12 | kSentimentCriticalValue = float64(61.80) // 情绪临界值 13 | ) 14 | 15 | type SentimentType = int 16 | 17 | const ( 18 | SentimentZero SentimentType = iota // 情绪一般 19 | SentimentHigh SentimentType = 1 // 情绪高涨 20 | SentimentLow SentimentType = -1 // 情绪低迷 21 | ) 22 | 23 | // IndexSentiment 情绪指数, 50%为平稳, 低于50%为情绪差, 高于50%为情绪好 24 | func IndexSentiment(codes ...string) (sentiment float64, consistent int) { 25 | // 默认上证指数 26 | securityCode := "sh000001" 27 | if len(codes) > 0 { 28 | securityCode = exchange.CorrectSecurityCode(codes[0]) 29 | } 30 | // 非指数不判断情绪 31 | if !exchange.AssertIndexBySecurityCode(securityCode) { 32 | return 33 | } 34 | if len(securityCode) != 8 { 35 | return 36 | } 37 | tdxApi := gotdx.GetTdxApi() 38 | hq, err := tdxApi.GetSnapshot([]string{securityCode}) 39 | if err != nil && len(hq) != len(codes) { 40 | logger.Errorf("获取即时行情数据失败", err) 41 | return 42 | } 43 | 44 | return SnapshotSentiment(hq[0]) 45 | } 46 | 47 | // SecuritySentiment 计算证券情绪 48 | func SecuritySentiment[E ~int | ~int64 | ~float32 | ~float64](up, down E) (sentiment float64, consistent int) { 49 | sentiment = 100 * float64(up) / float64(up+down) 50 | if num.Float64IsNaN(sentiment) { 51 | sentiment = 0 52 | return 53 | } 54 | consistent = SentimentZero 55 | if sentiment >= kSentimentCriticalValue { 56 | consistent = SentimentHigh 57 | } else if sentiment < 100-kSentimentCriticalValue { 58 | consistent = SentimentLow 59 | } 60 | return 61 | } 62 | 63 | // SnapshotSentiment 情绪指数, 50%为平稳, 低于50%为情绪差, 高于50%为情绪好 64 | func SnapshotSentiment(snapshot quotes.Snapshot) (sentiment float64, consistent int) { 65 | if exchange.AssertIndexByMarketAndCode(snapshot.Market, snapshot.Code) { 66 | // 指数和板块按照上涨和下跌家数计算 67 | return SecuritySentiment(snapshot.IndexUp, snapshot.IndexDown) 68 | } 69 | // 个股按照内外盘计算 70 | return SecuritySentiment(snapshot.BVol, snapshot.SVol) 71 | } 72 | -------------------------------------------------------------------------------- /market/sentiments_test.go: -------------------------------------------------------------------------------- 1 | package market 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSentiment(t *testing.T) { 9 | s, c := IndexSentiment("sh880521") 10 | fmt.Println(s, c) 11 | } 12 | -------------------------------------------------------------------------------- /market/shse/README.md: -------------------------------------------------------------------------------- 1 | 上海证券交易所 2 | === 3 | SHANGHAI STOCK EXCHANGE 4 | 5 | http://www.sse.com.cn/market/sseindex/indexlist/ -------------------------------------------------------------------------------- /market/shse/sse_index.go: -------------------------------------------------------------------------------- 1 | package shse 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gox/http" 6 | "math" 7 | "math/rand" 8 | urlpkg "net/url" 9 | "time" 10 | ) 11 | 12 | const ( 13 | // http://www.sse.com.cn/market/sseindex/indexlist/ 14 | urlSSEIndex = "https://query.sse.com.cn/commonSoaQuery.do" 15 | //urlSSEIndex = "http://query.sse.com.cn/commonQuery.do" 16 | ) 17 | 18 | // IndexList 上海指数列表 19 | func IndexList() { 20 | //isPagination=false&sqlId=DB_SZZSLB_ZSLB&_=1682986576599 21 | now := time.Now() 22 | timestamp := now.UnixMilli() 23 | cbNum := int(math.Floor(rand.Float64() * (100000000 + 1))) 24 | // 37039113 25 | params := urlpkg.Values{ 26 | "jsonCallBack": {fmt.Sprintf("jsonpCallback%d", cbNum)}, 27 | "isPagination": {"false"}, 28 | "sqlId": {"DB_SZZSLB_ZSLB"}, 29 | "_": {fmt.Sprintf("%d", timestamp)}, 30 | } 31 | header := map[string]any{ 32 | "Referer": "http://www.sse.com.cn/", 33 | //"Cookie": "ba17301551dcbaf9_gdp_user_key=; gdp_user_id=gioenc-1aadbe52,d720,54c2,9271,29b7b6dda362; ba17301551dcbaf9_gdp_session_id_2960a971-ddff-48be-827f-6eb99e891735=true; ba17301551dcbaf9_gdp_session_id_4602911b-d360-4f09-a438-3d40bca228d7=true; ba17301551dcbaf9_gdp_session_id_86910507-dd22-4b31-9749-e3a3b18eae25=true; ba17301551dcbaf9_gdp_session_id_1bb67914-f729-4e41-b75c-d09a6b0d7873=true; JSESSIONID=7052255EE4B2357019E75B7B09D6D571; ba17301551dcbaf9_gdp_session_id=2a1f157c-1605-45e8-a68b-e4196d04b2af; ba17301551dcbaf9_gdp_session_id_2a1f157c-1605-45e8-a68b-e4196d04b2af=true; ba17301551dcbaf9_gdp_sequence_ids={\"globalKey\":42,\"VISIT\":6,\"PAGE\":14,\"VIEW_CHANGE\":2,\"CUSTOM\":3,\"VIEW_CLICK\":21}", 34 | } 35 | url := urlSSEIndex + "?" 36 | //url += fmt.Sprintf("jsonCallBack=jsonpCallback%d&", cbNum) 37 | //url += "isPagination=false&" 38 | //url += "sqlId=DB_SZZSLB_ZSLB&" 39 | url += params.Encode() 40 | data, tm, err := http.Request(url, "get", header) 41 | fmt.Println(string(data), tm, err) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /market/shse/sse_index_test.go: -------------------------------------------------------------------------------- 1 | package shse 2 | 3 | import "testing" 4 | 5 | func TestIndexList(t *testing.T) { 6 | IndexList() 7 | //Indexes() 8 | } 9 | -------------------------------------------------------------------------------- /market/shse/sse_stock_test.go: -------------------------------------------------------------------------------- 1 | package shse 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetList(t *testing.T) { 9 | v, _ := GetSecurityList() 10 | fmt.Println(v) 11 | } 12 | -------------------------------------------------------------------------------- /market/subnew.go: -------------------------------------------------------------------------------- 1 | package market 2 | 3 | import ( 4 | "gitee.com/quant1x/exchange" 5 | "gitee.com/quant1x/gotdx/securities" 6 | "sync" 7 | ) 8 | 9 | const ( 10 | // 次新股板块 11 | kBlockSubNewStock = "880529" 12 | ) 13 | 14 | var ( 15 | onceSubNew sync.Once 16 | mapSubnewStock = map[string]bool{} 17 | ) 18 | 19 | func loadSubNewStock() { 20 | blockInfo := securities.GetBlockInfo(kBlockSubNewStock) 21 | if blockInfo == nil { 22 | return 23 | } 24 | for _, v := range blockInfo.ConstituentStocks { 25 | securityCode := exchange.CorrectSecurityCode(v) 26 | mapSubnewStock[securityCode] = true 27 | } 28 | } 29 | 30 | // IsSubNewStock 是否次新股 31 | func IsSubNewStock(code string) bool { 32 | onceSubNew.Do(loadSubNewStock) 33 | securityCode := exchange.CorrectSecurityCode(code) 34 | if v, ok := mapSubnewStock[securityCode]; ok { 35 | return v 36 | } 37 | return false 38 | } 39 | -------------------------------------------------------------------------------- /market/subnew_test.go: -------------------------------------------------------------------------------- 1 | package market 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestIsSubNewStock(t *testing.T) { 9 | code := "603052" 10 | v := IsSubNewStock(code) 11 | fmt.Println(v) 12 | } 13 | -------------------------------------------------------------------------------- /market/szse/README.md: -------------------------------------------------------------------------------- 1 | 深圳证券交易所 2 | === 3 | http://www.szse.cn/market/trend/index.html -------------------------------------------------------------------------------- /market/szse/szse_stock.go: -------------------------------------------------------------------------------- 1 | package szse 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/utils" 6 | "gitee.com/quant1x/gox/http" 7 | urlpkg "net/url" 8 | ) 9 | 10 | const ( 11 | kUrlMarketSzseCodeList = "http://www.szse.cn/api/report/ShowReport/data" 12 | ) 13 | 14 | func GetStockList() { 15 | timestamp := utils.Timestamp() 16 | params := urlpkg.Values{ 17 | "SHOWTYPE": {"JSON"}, 18 | "CATALOGID": {"1815_stock_snapshot"}, 19 | "TABKEY": {"tab1"}, 20 | "txtBeginDate": {"2023-06-16"}, 21 | "txtEndDate": {"2023-06-16"}, 22 | "archiveDate": {"2021-06-01"}, 23 | "random": {fmt.Sprintf("0.%d", timestamp)}, 24 | "PAGENO": {"2"}, 25 | "PAGESIZE": {"100"}, 26 | "tab1PAGESIZE": {"100"}, 27 | } 28 | header := map[string]any{ 29 | "Referer": "http://www.szse.cn/market/trend/index.html", 30 | //"Cookie": "ba17301551dcbaf9_gdp_user_key=; gdp_user_id=gioenc-1aadbe52,d720,54c2,9271,29b7b6dda362; ba17301551dcbaf9_gdp_session_id_2960a971-ddff-48be-827f-6eb99e891735=true; ba17301551dcbaf9_gdp_session_id_4602911b-d360-4f09-a438-3d40bca228d7=true; ba17301551dcbaf9_gdp_session_id_86910507-dd22-4b31-9749-e3a3b18eae25=true; ba17301551dcbaf9_gdp_session_id_1bb67914-f729-4e41-b75c-d09a6b0d7873=true; JSESSIONID=7052255EE4B2357019E75B7B09D6D571; ba17301551dcbaf9_gdp_session_id=2a1f157c-1605-45e8-a68b-e4196d04b2af; ba17301551dcbaf9_gdp_session_id_2a1f157c-1605-45e8-a68b-e4196d04b2af=true; ba17301551dcbaf9_gdp_sequence_ids={\"globalKey\":42,\"VISIT\":6,\"PAGE\":14,\"VIEW_CHANGE\":2,\"CUSTOM\":3,\"VIEW_CLICK\":21}", 31 | } 32 | url := kUrlMarketSzseCodeList + "?" + params.Encode() 33 | data, _ := http.Get(url, header) 34 | fmt.Println(string(data)) 35 | //if err != nil { 36 | // return nil, err 37 | //} 38 | ////fmt.Println(string(data), tm, err) 39 | ////fmt.Println(string(data)) 40 | //var raw rawShangHaiSecurities 41 | //err = json.Unmarshal(data, &raw) 42 | //if err != nil { 43 | // return 44 | //} 45 | //for _, vs := range raw.List { 46 | // arr := []string{} 47 | // for _, v := range vs { 48 | // arr = append(arr, stat.AnyToString(v)) 49 | // } 50 | // var info sseSecurityEntity 51 | // _ = api.Convert(arr, &info) 52 | // list = append(list, info) 53 | //} 54 | } 55 | -------------------------------------------------------------------------------- /market/szse/szse_stock_test.go: -------------------------------------------------------------------------------- 1 | package szse 2 | 3 | import "testing" 4 | 5 | func TestGetStockList(t *testing.T) { 6 | GetStockList() 7 | } 8 | -------------------------------------------------------------------------------- /models/features_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/cache" 6 | "gitee.com/quant1x/engine/factors" 7 | "gitee.com/quant1x/exchange" 8 | "gitee.com/quant1x/gox/api" 9 | "testing" 10 | ) 11 | 12 | func TestFeatureToSnapshot(t *testing.T) { 13 | code := "300410" 14 | securityCode := exchange.CorrectSecurityCode(code) 15 | filename := cache.WideFilename(securityCode) 16 | features := []factors.SecurityFeature{} 17 | err := api.CsvToSlices(filename, &features) 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | length := len(features) 23 | feature := features[length-1] 24 | v := FeatureToSnapshot(feature, securityCode) 25 | fmt.Println(v) 26 | } 27 | -------------------------------------------------------------------------------- /models/snapshot_quote.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/factors" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/gotdx" 7 | "gitee.com/quant1x/gotdx/quotes" 8 | "gitee.com/quant1x/gox/api" 9 | "gitee.com/quant1x/gox/logger" 10 | "gitee.com/quant1x/num" 11 | ) 12 | 13 | func QuoteSnapshotFromProtocol(v quotes.Snapshot) factors.QuoteSnapshot { 14 | snapshot := factors.QuoteSnapshot{} 15 | _ = api.Copy(&snapshot, &v) 16 | securityCode := exchange.GetSecurityCode(v.Market, v.Code) 17 | snapshot.OpeningChangeRate = num.NetChangeRate(snapshot.LastClose, snapshot.Open) 18 | snapshot.ChangeRate = num.NetChangeRate(snapshot.LastClose, snapshot.Price) 19 | snapshot.PremiumRate = num.NetChangeRate(snapshot.Open, snapshot.Price) 20 | snapshot.OpenBiddingDirection, snapshot.OpenVolumeDirection = v.CheckDirection() 21 | // 涨跌力度 22 | snapshot.ChangePower = float64(snapshot.OpenVolume) / snapshot.OpeningChangeRate 23 | snapshot.AverageBiddingVolume = v.AverageBiddingVolume() 24 | 25 | // 补全F10相关 26 | f10 := factors.GetL5F10(securityCode) 27 | if f10 != nil { 28 | snapshot.Name = f10.SecurityName 29 | snapshot.Capital = f10.Capital 30 | snapshot.FreeCapital = f10.FreeCapital 31 | snapshot.OpenTurnZ = f10.TurnZ(snapshot.OpenVolume) 32 | } 33 | // 补全扩展相关 34 | history := factors.GetL5History(securityCode) 35 | if history != nil && history.MV5 > 0 { 36 | lastMinuteVolume := history.GetMV5() 37 | snapshot.OpenQuantityRatio = float64(snapshot.OpenVolume) / lastMinuteVolume 38 | minuteVolume := float64(snapshot.Vol) / float64(exchange.Minutes(snapshot.Date)) 39 | snapshot.QuantityRatio = minuteVolume / lastMinuteVolume 40 | } 41 | return snapshot 42 | } 43 | 44 | // BatchSnapShot 批量获取即时行情数据快照 45 | func BatchSnapShot(codes []string) []factors.QuoteSnapshot { 46 | tdxApi := gotdx.GetTdxApi() 47 | list := []factors.QuoteSnapshot{} 48 | var err error 49 | var hq []quotes.Snapshot 50 | retryTimes := 0 51 | for retryTimes < quotes.DefaultRetryTimes { 52 | hq, err = tdxApi.GetSnapshot(codes) 53 | if err == nil && hq != nil { 54 | break 55 | } 56 | retryTimes++ 57 | } 58 | 59 | if err != nil { 60 | logger.Errorf("获取即时行情数据失败", err) 61 | return list 62 | } 63 | 64 | for _, v := range hq { 65 | if v.State != quotes.TDX_SECURITY_TRADE_STATE_NORMAL { 66 | // 非正常交易的记录忽略掉 67 | continue 68 | } 69 | snapshot := QuoteSnapshotFromProtocol(v) 70 | list = append(list, snapshot) 71 | } 72 | return list 73 | } 74 | 75 | // GetTick 获取一只股票的tick数据 76 | // 77 | // 该函数用于测试 78 | func GetTick(securityCode string) *factors.QuoteSnapshot { 79 | list := BatchSnapShot([]string{securityCode}) 80 | if len(list) > 0 { 81 | v := list[0] 82 | return &v 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /models/snapshot_quote_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestBatchSnapShot(t *testing.T) { 9 | data := BatchSnapShot([]string{"sz003042"}) 10 | fmt.Printf("%+v\n", data) 11 | } 12 | -------------------------------------------------------------------------------- /models/snapshot_tick_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "testing" 4 | 5 | func TestSyncAllSnapshots(t *testing.T) { 6 | barIndex := 1 7 | SyncAllSnapshots(&barIndex) 8 | } 9 | -------------------------------------------------------------------------------- /models/statistics.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Statistics 0号策略数据, 订单结构 4 | type Statistics struct { 5 | Date string `name:"日期" dataframe:"date"` 6 | Code string `name:"证券代码" dataframe:"code"` 7 | Name string `name:"证券名称" dataframe:"name"` 8 | TurnZ float64 `name:"开盘换手Z%" dataframe:"turnz"` 9 | QuantityRatio float64 `name:"开盘量比" dataframe:"quantity_ratio"` 10 | Tendency string `name:"趋势" dataframe:"tendency"` 11 | LastClose float64 `name:"昨收" dataframe:"last_close"` 12 | Open float64 `name:"开盘价" dataframe:"open"` 13 | OpenRaise float64 `name:"开盘涨幅%" dataframe:"open_raise"` 14 | Price float64 `name:"现价" dataframe:"price"` 15 | UpRate float64 `name:"涨跌幅%" dataframe:"up_rate"` 16 | OpenPremiumRate float64 `name:"浮动溢价率%" dataframe:"open_premium_rate"` 17 | NextPremiumRate float64 `name:"隔日溢价率%" dataframe:"next_premium_rate"` 18 | BlockName string `name:"板块名称" dataframe:"block_name"` 19 | BlockRate float64 `name:"板块涨幅%" dataframe:"block_rate"` 20 | BlockTop int `name:"板块排名" dataframe:"block_top"` 21 | BlockRank int `name:"个股排名" dataframe:"block_rank"` 22 | OpenVolume int `name:"开盘量" dataframe:"open_volume"` 23 | AveragePrice float64 `name:"均价线" dataframe:"average_price"` 24 | Active int `name:"活跃度" dataframe:"active"` 25 | Speed float64 `dataframe:"speed"` 26 | ChangePower float64 `dataframe:"change_power"` 27 | AverageBiddingVolume int `name:"委托均量" dataframe:"average_bidding_volume"` 28 | Beta float64 `name:"Beta" dataframe:"beta"` // beta值 29 | Alpha float64 `name:"Alpha" dataframe:"alpha"` // alpha值 30 | UpdateTime string `name:"时间戳" dataframe:"update_time"` 31 | } 32 | -------------------------------------------------------------------------------- /models/strategy_result.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // ResultInfo 策略结果 4 | type ResultInfo struct { 5 | Code string `name:"证券代码" dataframe:"code"` 6 | Name string `name:"证券名称" dataframe:"name"` 7 | Date string `name:"信号日期" dataframe:"date"` 8 | TurnZ float64 `name:"开盘换手Z" dataframe:"turn_z"` 9 | Rate float64 `name:"涨跌幅%" dataframe:"rate"` 10 | Buy float64 `name:"委托价格" dataframe:"buy"` 11 | Sell float64 `name:"目标价格" dataframe:"sell"` 12 | StrategyCode uint64 `name:"策略编码" dataframe:"strategy_code"` 13 | StrategyName string `name:"策略名称" dataframe:"strategy_name"` 14 | BlockType string `name:"板块类型" dataframe:"block_type"` 15 | BlockCode string `name:"板块代码" dataframe:"block_code"` 16 | BlockName string `name:"板块名称" dataframe:"block_name"` 17 | BlockRate float64 `name:"板块涨幅%" dataframe:"block_rate"` 18 | BlockTop int `name:"板块排名" dataframe:"block_top"` 19 | BlockRank int `name:"个股排名" dataframe:"block_rank"` 20 | BlockZhangTing string `name:"板块涨停数" dataframe:"block_zhangting"` 21 | BlockDescribe string `name:"涨/跌/平" dataframe:"block_describe"` 22 | BlockTopCode string `name:"领涨股代码" dataframe:"block_top_code"` 23 | BlockTopName string `name:"领涨股名称" dataframe:"block_top_name"` 24 | BlockTopRate float64 `name:"领涨股涨幅%" dataframe:"block_top_rate"` 25 | Tendency string `name:"短线趋势" dataframe:"tendency"` 26 | } 27 | 28 | // Predict 预测趋势 29 | func (this *ResultInfo) Predict() { 30 | //N := 3 31 | //df := factors.BasicKLine(this.Code) 32 | //if df.Nrow() < N+1 { 33 | // return 34 | //} 35 | //limit := api.RangeFinite(-N) 36 | //OPEN := df.Col("open").Select(limit) 37 | //CLOSE := df.Col("close").Select(limit) 38 | //HIGH := df.Col("high").Select(limit) 39 | //LOW := df.Col("low").Select(limit) 40 | //lastClose := num.AnyToFloat64(CLOSE.IndexOf(-1)) 41 | //po := linear.CurveRegression(OPEN).IndexOf(-1).(num.DType) 42 | //pc := linear.CurveRegression(CLOSE).IndexOf(-1).(num.DType) 43 | //ph := linear.CurveRegression(HIGH).IndexOf(-1).(num.DType) 44 | //pl := linear.CurveRegression(LOW).IndexOf(-1).(num.DType) 45 | //if po > lastClose { 46 | // this.Tendency = "高开" 47 | //} else if po == lastClose { 48 | // this.Tendency = "平开" 49 | //} else { 50 | // this.Tendency = "低开" 51 | //} 52 | //if pl > ph { 53 | // this.Tendency += ",冲高回落" 54 | //} else if pl > pc { 55 | // this.Tendency += ",探底回升" 56 | //} else if pc < pl { 57 | // this.Tendency += ",趋势向下" 58 | //} else { 59 | // this.Tendency += ",短线向好" 60 | //} 61 | // 62 | //fs := []float64{float64(po), float64(pc), float64(ph), float64(pl)} 63 | //sort.Float64s(fs) 64 | // 65 | //_ = lastClose 66 | } 67 | -------------------------------------------------------------------------------- /models/voting.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/config" 5 | ) 6 | 7 | const ( 8 | // CACHE_STRATEGY_PATH 策略文件存储路径 9 | CACHE_STRATEGY_PATH = "strategy" 10 | ) 11 | 12 | var ( 13 | CountDays int // 统计多少天, 由控制台传入数值 14 | CountTopN int // 统计前N, 由控制台传入数值 15 | ) 16 | 17 | // AllStockTopN 最大输出多少只个股 18 | func AllStockTopN() int { 19 | return config.TraderConfig().TopN 20 | } 21 | -------------------------------------------------------------------------------- /models/voting_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | -------------------------------------------------------------------------------- /permissions/permission.go: -------------------------------------------------------------------------------- 1 | package permissions 2 | 3 | import ( 4 | "errors" 5 | "gitee.com/quant1x/engine/models" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | ErrAlreadyExists = errors.New("the validator already exists") // 权限验证已经存在 11 | ) 12 | 13 | type Validator func(id uint64) error 14 | 15 | var ( 16 | mutexPermission sync.Mutex 17 | validatePermission Validator = nil 18 | ) 19 | 20 | // RegisterValidatePermission 注册权限验证模块 21 | func RegisterValidatePermission(f Validator) error { 22 | mutexPermission.Lock() 23 | defer mutexPermission.Unlock() 24 | if validatePermission != nil { 25 | return ErrAlreadyExists 26 | } 27 | validatePermission = f 28 | return nil 29 | } 30 | 31 | // CheckPermission 权限验证 32 | func CheckPermission(model models.Strategy) error { 33 | if validatePermission == nil { 34 | // 没有权限验证, 直接返回成功 35 | return nil 36 | } 37 | return validatePermission(model.Code()) 38 | } 39 | -------------------------------------------------------------------------------- /publish-compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 获取当前路径, 用于返回 3 | #p0=`pwd` 4 | # 获取脚本所在路径, 防止后续操作在非项目路径 5 | p2=$(cd $(dirname $0);pwd) 6 | 7 | #COLOR_NORMAL="\033[0m" 8 | #COLOR_GREEN="\033[1;32m" 9 | #COLOR_YELLOW="\033[1;33m" 10 | #COLOR_RED="\033[1;33m" 11 | #COLOR_GREY="\033[1;30m" 12 | #echo "${COLOR_GREEN} 绿色 ${COLOR_NORMAL}" 13 | #echo "${COLOR_RED} 红色 ${COLOR_NORMAL}" 14 | 15 | #golang 16 | echo "----------------< go env >----------------" 17 | GOVERSION=$(go env GOVERSION) 18 | GOOS=$(go env GOOS) 19 | GOARCH=$(go env GOARCH) 20 | echo " GOOS: ${GOOS}" 21 | echo " GOARCH: ${GOARCH}" 22 | echo "version: ${GOVERSION:2}" 23 | 24 | export GO111MODULE=auto 25 | export GOPRIVATE=gitee.com 26 | export GOPROXY=https://goproxy.cn,direct 27 | 28 | echo "----------------< project >----------------" 29 | module=$(awk 'NR==1 {print}' go.mod) 30 | module=`echo $module | awk '{split($0,a," ");print a[2]}'` 31 | echo " go mod: ${module}" 32 | tag=$(git describe --tags `git rev-list --tags --max-count=1`) 33 | version=${tag:1} 34 | echo "version: ${version}" 35 | last_commit=`git rev-parse HEAD` 36 | author=`git log ${tag} --pretty=format:"%an"|sed -n 1p` 37 | echo " author: ${author}" 38 | 39 | function compile() { 40 | echo "----------------< compile >----------------" 41 | BIN=$p2/bin 42 | app=$1 43 | EXT=$2 44 | echo " GOOS: ${GOOS}" 45 | echo " GOARCH: ${GOARCH}" 46 | echo "正在编译应用:${app} => ${BIN}/${app}${EXT}..." 47 | env GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-s -w -X 'main.MinVersion=${version}'" -o ${BIN}/${app}${EXT} ${module} 48 | echo "正在编译应用:${app} => ${BIN}/${app}${EXT}...OK" 49 | } 50 | -------------------------------------------------------------------------------- /publish-linux-amd64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 获取当前路径, 用于返回 3 | p0=`pwd` 4 | # 获取脚本所在路径, 防止后续操作在非项目路径 5 | p1=$(cd $(dirname $0);pwd) 6 | cd $p1 7 | 8 | source ./publish-compile.sh 9 | 10 | # linux amd64 11 | GOOS=linux 12 | GOARCH=amd64 13 | app=stock 14 | ext= 15 | 16 | compile $app $ext 17 | 18 | cd $p0 -------------------------------------------------------------------------------- /publish-mac-amd64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 获取当前路径, 用于返回 3 | p0=`pwd` 4 | # 获取脚本所在路径, 防止后续操作在非项目路径 5 | p1=$(cd $(dirname $0);pwd) 6 | cd $p1 7 | 8 | source ./publish-compile.sh 9 | 10 | # darwin amd64 11 | GOOS=darwin 12 | GOARCH=amd64 13 | app=stock 14 | ext= 15 | 16 | compile $app $ext 17 | 18 | cd $p0 -------------------------------------------------------------------------------- /publish-mac-arm64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 获取当前路径, 用于返回 3 | p0=`pwd` 4 | # 获取脚本所在路径, 防止后续操作在非项目路径 5 | p1=$(cd $(dirname $0);pwd) 6 | cd $p1 7 | 8 | source ./publish-compile.sh 9 | 10 | # darwin arm64 11 | GOOS=darwin 12 | GOARCH=arm64 13 | app=stock 14 | ext= 15 | 16 | compile $app $ext 17 | 18 | cd $p0 -------------------------------------------------------------------------------- /publish-windows-amd64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 获取当前路径, 用于返回 3 | p0=`pwd` 4 | # 获取脚本所在路径, 防止后续操作在非项目路径 5 | p1=$(cd $(dirname $0);pwd) 6 | cd $p1 7 | 8 | source ./publish-compile.sh 9 | 10 | # windows amd64 11 | GOOS=windows 12 | GOARCH=amd64 13 | app=stock 14 | ext=.exe 15 | 16 | compile $app $ext 17 | 18 | cd $p0 -------------------------------------------------------------------------------- /publish-windows-arm64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 获取当前路径, 用于返回 3 | p0=`pwd` 4 | # 获取脚本所在路径, 防止后续操作在非项目路径 5 | p1=$(cd $(dirname $0);pwd) 6 | cd $p1 7 | 8 | source ./publish-compile.sh 9 | 10 | # windows arm64 11 | GOOS=windows 12 | GOARCH=arm64 13 | app=stock 14 | ext=.exe 15 | 16 | compile $app $ext 17 | 18 | cd $p0 -------------------------------------------------------------------------------- /publish-windows.ps1: -------------------------------------------------------------------------------- 1 | # engine 编译脚本 2 | # author: wangfeng 3 | # since: 2023-09-12 4 | 5 | $commit_id = (git rev-list --tags --max-count = 1) | Out-String 6 | $command = "git describe --tags ${commit_id}" 7 | $repo = (Invoke-Expression $command) | Out-String 8 | $version = $repo.Substring(1).Trim() 9 | Write-Output "quant version: ${version}" 10 | $repo = (go list -m gitee.com/quant1x/gotdx) | Out-String 11 | $gotdx_version = ($repo -split " ")[1] 12 | $gotdx_version = $gotdx_version.Substring(1).Trim() 13 | Write-Output "gotdx version: $gotdx_version" 14 | 15 | $BIN = "./bin" 16 | $APP = "stock" 17 | $EXT = ".exe" 18 | $repo = "gitee.com/quant1x/engine" 19 | $GOOS = "windows" 20 | $GOARCH = $env:PROCESSOR_ARCHITECTURE 21 | $GOARCH = $GOARCH.Trim().ToLower() 22 | $command = "go env -w GOOS=${GOOS} GOARCH=${GOARCH}; go build -ldflags `"-s -w -X 'main.MinVersion=$version' -X 'main.tdxVersion=$gotdx_version'`" -o ${BIN}/${APP}${EXT} ${repo}" 23 | Write-Output $command 24 | Invoke-Expression $command 25 | -------------------------------------------------------------------------------- /publish-windows.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # windows下的编译脚本 3 | # author: wangfeng 4 | # since: 2023-09-12 5 | 6 | import os 7 | import subprocess 8 | from git import Repo 9 | import platform 10 | 11 | def get_latest_tag(diff: int=0) -> str: 12 | """ 13 | 获取仓库最新的一个tag, 支持传入调整的数值, 默认只修改修订版本号 14 | """ 15 | repo = Repo(r'./') 16 | tags = [] 17 | for __tag in repo.tags: 18 | tag = str(__tag) 19 | tag = tag[1:] 20 | tags.append(tag) 21 | tags.sort(key=lambda x:tuple(int(v) for v in x.split('.'))) 22 | latest = tags[-1] 23 | if diff != 0: 24 | last_vs = tags[-1].split('.') 25 | last_vs[-1] = str(int(last_vs[-1])+diff) 26 | latest = '.'.join(last_vs) 27 | return latest 28 | 29 | 30 | # 只能获取执行结果 31 | def subprocess_check_output(stmt, shell:bool=False): 32 | result = subprocess.check_output(stmt, shell).decode('utf-8') 33 | # 执行失败不需要特殊处理,命令执行失败会直接报错 34 | return result # 返回执行结果,但是结果返回的是一个str字符串(不论有多少行),并且返回的结果需要转换编码 35 | 36 | def fix_version(v: str) -> str: 37 | if v.startswith(('v','V')): 38 | v = v[1:] 39 | return v 40 | 41 | def get_mod_version(module: str) -> str: 42 | # 获取依赖库stock的版本号 43 | cmd_result = subprocess_check_output(f'go list -m {module}').strip('\n') 44 | vs = cmd_result.split(' ') 45 | tag_latest = fix_version(vs[1]) 46 | version = tag_latest 47 | return version 48 | 49 | if __name__ == '__main__': 50 | print(os.path.abspath('.')) 51 | # 获取应用的版本号 52 | version = get_latest_tag() 53 | # 获取依赖库的版本号 54 | gotdx_version = get_mod_version('gitee.com/quant1x/gotdx') 55 | print('gotdx version: ', gotdx_version) 56 | repo = 'gitee.com/quant1x/engine' 57 | BIN='./bin' 58 | current_path = os.path.dirname(os.path.abspath(__file__)) 59 | #print(current_path) 60 | BIN=current_path + '/' + BIN 61 | APP = 'engine' 62 | EXT = '.exe' 63 | GOOS = platform.system().lower() 64 | machine = platform.machine().lower() 65 | GOARCH = 'amd64' if machine=='amd64' else 'arm64' 66 | print(f"正在编译应用:{APP} => {BIN}/{APP}{EXT}...") 67 | cmd= fr'''go env -w GOOS={GOOS} GOARCH={GOARCH} && go build -ldflags "-s -w -X 'main.MinVersion={version}' -X 'main.tdxVersion={gotdx_version}'" -o {BIN}/{APP}{EXT} {repo}''' 68 | print(cmd) 69 | subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) 70 | #subprocess.call(cmd, stdout=subprocess.PIPE, shell=True) 71 | print(f"正在编译应用:{APP} => {BIN}/{APP}{EXT}...OK") 72 | 73 | -------------------------------------------------------------------------------- /realtime/doc.go: -------------------------------------------------------------------------------- 1 | // Package realtime 实时数据相关的功能集合 2 | package realtime 3 | -------------------------------------------------------------------------------- /realtime/exponential_moving_average.go: -------------------------------------------------------------------------------- 1 | package realtime 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/factors" 5 | "gitee.com/quant1x/pandas/formula" 6 | ) 7 | 8 | // AlphaOfExponentialMovingAverage 计算EMA的alpha值 9 | func AlphaOfExponentialMovingAverage(period int) float64 { 10 | return formula.AlphaOfEMA(period) 11 | } 12 | 13 | // IncrementalExponentialMovingAverage 增量计算 指数移动平均线 14 | func IncrementalExponentialMovingAverage(now, last, alpha float64) float64 { 15 | return formula.EmaIncr(now, last, alpha) 16 | } 17 | 18 | // DynamicExponentialMovingAverage 动态EMA 19 | // 20 | // 返回当前值以及最高值和最低值 21 | func DynamicExponentialMovingAverage(snapshot factors.QuoteSnapshot, last, alpha float64) (ema, emaHigh, emaLow float64) { 22 | ema = IncrementalExponentialMovingAverage(snapshot.Price, last, alpha) 23 | emaHigh = IncrementalExponentialMovingAverage(snapshot.High, last, alpha) 24 | emaLow = IncrementalExponentialMovingAverage(snapshot.Low, last, alpha) 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /realtime/moving_average.go: -------------------------------------------------------------------------------- 1 | package realtime 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/factors" 5 | "gitee.com/quant1x/num" 6 | "gitee.com/quant1x/pandas" 7 | "gitee.com/quant1x/pandas/formula" 8 | ) 9 | 10 | // MovingAverage 计算均线范围 11 | func MovingAverage(CLOSE, HIGH, LOW pandas.Series, PN int) (ma, half, maMax, maMin pandas.Series) { 12 | half = formula.MA(CLOSE, PN-1) 13 | r1Half := formula.REF(half, 1) 14 | maMax = r1Half.Mul(PN - 1).Add(HIGH).Div(PN) 15 | maMin = r1Half.Mul(PN - 1).Add(LOW).Div(PN) 16 | ma = r1Half.Mul(PN - 1).Add(CLOSE).Div(PN) 17 | return ma, half, maMax, maMin 18 | } 19 | 20 | // IncrementalMovingAverage 增量计算移动平均线 21 | // 22 | // period 周期数 23 | // previousHalfValue 前period-1的平均值 24 | // price 现价 25 | func IncrementalMovingAverage(previousHalfValue float64, period int, price float64) float64 { 26 | n := float64(period) 27 | sum := previousHalfValue*(n-1) + price 28 | ma := num.Decimal(sum / n) 29 | return ma 30 | } 31 | 32 | // DynamicMovingAverage 增量计算移动平均线的范围 33 | // 34 | // period 周期数 35 | // previousHalfValue 前period-1的平均值 36 | // price 现价 37 | func DynamicMovingAverage(previousHalfValue float64, period int, snapshot factors.QuoteSnapshot) (ma, maHigh, maLow float64) { 38 | ma = IncrementalMovingAverage(previousHalfValue, period, snapshot.Price) 39 | maHigh = IncrementalMovingAverage(previousHalfValue, period, snapshot.High) 40 | maLow = IncrementalMovingAverage(previousHalfValue, period, snapshot.Low) 41 | return ma, maHigh, maLow 42 | } 43 | -------------------------------------------------------------------------------- /rules/rule_f10_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/config" 6 | "gitee.com/quant1x/engine/models" 7 | "testing" 8 | ) 9 | 10 | func Test_baseFilter(t *testing.T) { 11 | code := "601868" 12 | code = "sh600622" 13 | code = "sh601188" 14 | code = "sz002682" 15 | snapshot := models.GetTick(code) 16 | strategyParameter := config.GetStrategyParameterByCode(0) 17 | passed, failKind, err := Filter(strategyParameter.Rules, *snapshot) 18 | fmt.Println(passed, failKind, err) 19 | } 20 | -------------------------------------------------------------------------------- /rules/rule_impl.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/config" 5 | "gitee.com/quant1x/engine/factors" 6 | ) 7 | 8 | type RuleImpl struct { 9 | kind Kind 10 | name string 11 | exec func(rules config.RuleParameter, snapshot factors.QuoteSnapshot) error 12 | } 13 | 14 | func (r RuleImpl) Kind() Kind { 15 | return r.kind 16 | } 17 | 18 | func (r RuleImpl) Name() string { 19 | return r.name 20 | } 21 | 22 | func (r RuleImpl) Exec(rules config.RuleParameter, snapshot factors.QuoteSnapshot) error { 23 | return r.exec(rules, snapshot) 24 | } 25 | 26 | func (r RuleImpl) RuleMethod() func(rules config.RuleParameter, snapshot factors.QuoteSnapshot) error { 27 | return r.exec 28 | } 29 | -------------------------------------------------------------------------------- /rules/rule_test.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bits-and-blooms/bitset" 6 | "testing" 7 | ) 8 | 9 | func TestRule(t *testing.T) { 10 | fmt.Printf("Hello from BitSet!\n") 11 | var b bitset.BitSet 12 | //// play some Go Fish 13 | //for i := 0; i < 100; i++ { 14 | // card1 := uint(rand.Intn(52)) 15 | // card2 := uint(rand.Intn(52)) 16 | // b.Set(card1) 17 | // if b.Test(card2) { 18 | // fmt.Println("Go Fish!") 19 | // } 20 | // b.Clear(card1) 21 | //} 22 | 23 | // Chaining 24 | b.Set(10).Set(11).Set(1000) 25 | 26 | for i, e := b.NextSet(0); e; i, e = b.NextSet(i + 1) { 27 | fmt.Println("The following bit is set:", i) 28 | } 29 | if b.Intersection(bitset.New(100).Set(10)).Count() == 1 { 30 | fmt.Println("Intersection works.") 31 | } else { 32 | fmt.Println("Intersection doesn't work???") 33 | } 34 | text := b.String() 35 | fmt.Println(text) 36 | } 37 | -------------------------------------------------------------------------------- /services/scheduler.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "gitee.com/quant1x/engine/config" 7 | "gitee.com/quant1x/gox/coroutine" 8 | "gitee.com/quant1x/gox/cron" 9 | "gitee.com/quant1x/gox/logger" 10 | "gitee.com/quant1x/gox/runtime" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | // Task 定时任务 16 | // 17 | // 默认每10秒检测1次 18 | // 排名不分先后 19 | type Task struct { 20 | name string // 任务名称 21 | spec string // 触发条件 22 | Service func() // 任务函数 23 | } 24 | 25 | var ( 26 | ErrAlreadyExists = errors.New("the job already exists") // 任务已经存在 27 | ErrForbidden = errors.New("the job was forbidden") // 任务被禁止 28 | ) 29 | 30 | var ( 31 | jobMutex sync.Mutex 32 | mapJobs = map[string]Task{} 33 | crontab = cron.New() 34 | ) 35 | 36 | // Register 注册定时任务 37 | func Register(name, spec string, callback func()) error { 38 | jobMutex.Lock() 39 | defer jobMutex.Unlock() 40 | _, ok := mapJobs[name] 41 | if ok { 42 | return ErrAlreadyExists 43 | } 44 | if len(spec) == 0 { 45 | spec = CronDefaultInterval 46 | } 47 | enable := true 48 | jobParam := config.GetJobParameter(name) 49 | if jobParam != nil { 50 | enable = jobParam.Enable 51 | trigger := strings.TrimSpace(jobParam.Trigger) 52 | if len(trigger) > 0 { 53 | spec = trigger 54 | } 55 | } 56 | if !enable { 57 | // 配置禁止任务, 不能返回错误 58 | return nil 59 | } 60 | job := Task{name: name, spec: spec, Service: callback} 61 | mapJobs[job.name] = job 62 | return nil 63 | } 64 | 65 | // DaemonService 守护进程服务入口 66 | func DaemonService() { 67 | jobMutex.Lock() 68 | // 启动服务 69 | logger.Infof("启动定时任务列表") 70 | crontab.Start() 71 | // 输出2个空白行 72 | fmt.Printf("%s", strings.Repeat("\n", 2)) 73 | for _, v := range mapJobs { 74 | message := fmt.Sprintf("Service: %s, Interval: %s, ", v.name, v.spec) 75 | logger.Info(message) 76 | _, err := crontab.AddJobWithSkipIfStillRunning(v.spec, v.Service) 77 | if err != nil { 78 | logger.Infof(message+"failed, err: %s", err.Error()) 79 | } else { 80 | logger.Infof(message + "success") 81 | } 82 | } 83 | jobMutex.Unlock() 84 | // 等待结束 85 | coroutine.WaitForShutdown() 86 | // 关闭任务调度 87 | crontab.Stop() 88 | } 89 | 90 | // PrintJobList 输出定时任务列表 91 | func PrintJobList() { 92 | for _, v := range mapJobs { 93 | spec := v.spec 94 | message := fmt.Sprintf("Service: %s, Interval: %s, method: %s", v.name, spec, runtime.FuncName(v.Service)) 95 | fmt.Println(message) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /services/scheduler_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "testing" 4 | 5 | func TestJobRealtimeKLine(t *testing.T) { 6 | jobRealtimeKLine() 7 | } 8 | -------------------------------------------------------------------------------- /services/services_state.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/cache" 6 | "gitee.com/quant1x/exchange" 7 | "gitee.com/quant1x/gox/api" 8 | "os" 9 | "path/filepath" 10 | "time" 11 | ) 12 | 13 | const ( 14 | // 状态文件时间格式 15 | timeLayoutOfState = "150405" 16 | ) 17 | 18 | func stateFilename(date, timestamp string) string { 19 | date = exchange.FixTradeDate(date) 20 | t, _ := time.ParseInLocation(exchange.CN_SERVERTIME_FORMAT, timestamp, time.Local) 21 | timestamp = t.Format(timeLayoutOfState) 22 | tm := date + "T" + timestamp 23 | filename := fmt.Sprintf("%s/update.%s", cache.GetVariablePath(), tm) 24 | return filename 25 | } 26 | 27 | // 状态文件不存在则可更新, 反之不可更新 28 | func checkUpdateState(date, timestamp string) bool { 29 | filename := stateFilename(date, timestamp) 30 | return !api.FileExist(filename) 31 | } 32 | 33 | // 确定完成当期更新状态 34 | func doneUpdate(date, timestamp string) { 35 | filename := stateFilename(date, timestamp) 36 | file, err := os.Create(filename) 37 | if err != nil { 38 | return 39 | } 40 | api.CloseQuietly(file) 41 | } 42 | 43 | // 清理过期的状态文件 44 | func cleanExpiredStateFiles() error { 45 | statePath := cache.GetVariablePath() 46 | pattern := filepath.Join(statePath, "update.*") 47 | filePaths, err := filepath.Glob(pattern) 48 | if err != nil { 49 | return err 50 | } 51 | for _, filePath := range filePaths { 52 | _ = os.Remove(filePath) 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /services/services_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/gotdx" 7 | "testing" 8 | ) 9 | 10 | func TestGlobalReset(t *testing.T) { 11 | _ = cleanExpiredStateFiles() 12 | gotdx.ReOpen() 13 | date := cache.DefaultCanUpdateDate() 14 | factors.SwitchDate(date) 15 | } 16 | -------------------------------------------------------------------------------- /services/task_global_reset.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/gotdx" 7 | "gitee.com/quant1x/gox/logger" 8 | "gitee.com/quant1x/gox/runtime" 9 | ) 10 | 11 | // 任务 - 交易日数据缓存重置 12 | func jobGlobalReset() { 13 | defer runtime.IgnorePanic("") 14 | logger.Info("系统初始化...") 15 | logger.Info("清理过期的更新状态文件...") 16 | _ = cleanExpiredStateFiles() 17 | logger.Info("清理过期的更新状态文件...OK") 18 | gotdx.ReOpen() 19 | logger.Info("重置系统缓存...") 20 | factors.SwitchDate(cache.DefaultCanReadDate()) 21 | logger.Info("重置系统缓存...OK") 22 | 23 | logger.Info("系统初始化...OK") 24 | } 25 | -------------------------------------------------------------------------------- /services/task_network.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/gotdx/quotes" 5 | "gitee.com/quant1x/gox/logger" 6 | ) 7 | 8 | // 网络配置重置 9 | func jobResetNetwork() { 10 | logger.Infof("刷新服务器列表...") 11 | quotes.BestIP() 12 | logger.Infof("刷新服务器列表...OK") 13 | } 14 | -------------------------------------------------------------------------------- /services/task_network_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "testing" 4 | 5 | func Test_jobResetNetwork(t *testing.T) { 6 | jobResetNetwork() 7 | } 8 | -------------------------------------------------------------------------------- /services/task_realtime_kline.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/datasource/base" 5 | "gitee.com/quant1x/engine/market" 6 | "gitee.com/quant1x/engine/models" 7 | "gitee.com/quant1x/exchange" 8 | "gitee.com/quant1x/gox/coroutine" 9 | "gitee.com/quant1x/gox/logger" 10 | "gitee.com/quant1x/gox/progressbar" 11 | "gitee.com/quant1x/gox/runtime" 12 | ) 13 | 14 | // 任务 - 实时更新K线 15 | func jobRealtimeKLine() { 16 | funcName := "jobRealtimeKLine" 17 | updateInRealTime, status := exchange.CanUpdateInRealtime() 18 | // 14:30:00~15:01:00之间更新数据 19 | if updateInRealTime && IsTrading(status) { 20 | realtimeUpdateOfKLine() 21 | } else { 22 | if runtime.Debug() { 23 | realtimeUpdateOfKLine() 24 | } else { 25 | logger.Infof("%s, 非尾盘交易时段: %d", funcName, status) 26 | } 27 | } 28 | } 29 | 30 | // 更新K线 31 | func realtimeUpdateOfKLine() { 32 | defer runtime.IgnorePanic("") 33 | barIndex := barIndexRealtimeKLine 34 | allCodes := market.GetCodeList() 35 | wg := coroutine.NewRollingWaitGroup(5) 36 | bar := progressbar.NewBar(barIndex, "执行[实时更新K线]", len(allCodes)) 37 | for _, code := range allCodes { 38 | updateKLine := func(waitGroup *coroutine.RollingWaitGroup, securityCode string) { 39 | defer waitGroup.Done() 40 | bar.Add(1) 41 | snapshot := models.GetTickFromMemory(securityCode) 42 | if snapshot != nil { 43 | base.BasicKLineForSnapshot(*snapshot) 44 | } 45 | } 46 | wg.Add(1) 47 | go updateKLine(wg, code) 48 | } 49 | wg.Wait() 50 | } 51 | -------------------------------------------------------------------------------- /services/task_sell_orders_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/trader" 6 | "gitee.com/quant1x/exchange" 7 | "testing" 8 | ) 9 | 10 | func TestTaskSell_getEarlierDate(t *testing.T) { 11 | v := getEarlierDate(1) 12 | fmt.Println(v) 13 | } 14 | 15 | func Test_checkoutCanSellStockList(t *testing.T) { 16 | positions, err := trader.QueryHolding() 17 | if err != nil { 18 | return 19 | } 20 | var holdings []string 21 | for _, position := range positions { 22 | if position.CanUseVolume < 1 { 23 | continue 24 | } 25 | stockCode := position.StockCode 26 | securityCode := exchange.CorrectSecurityCode(stockCode) 27 | holdings = append(holdings, securityCode) 28 | } 29 | v := CheckoutCanSellStockList(117, holdings) 30 | fmt.Println(v) 31 | } 32 | 33 | func Test_getHoldingDates(t *testing.T) { 34 | v := getHoldingDates(1) 35 | fmt.Println(v) 36 | } 37 | -------------------------------------------------------------------------------- /services/task_sell_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/config" 6 | "gitee.com/quant1x/engine/models" 7 | "reflect" 8 | "slices" 9 | "testing" 10 | ) 11 | 12 | func TestTaskSell_LastSession(t *testing.T) { 13 | sellStrategyCode := models.ModelOneSizeFitsAllSells 14 | // 1. 获取117号策略(卖出) 15 | sellRule := config.GetStrategyParameterByCode(sellStrategyCode) 16 | if sellRule == nil { 17 | return 18 | } 19 | timestamp := "14:55:00" 20 | v := sellRule.Session.IsTodayLastSession(timestamp) 21 | fmt.Println(v) 22 | } 23 | 24 | type MyStruct struct { 25 | Field1 string 26 | Field2 int 27 | } 28 | 29 | func TestStructPtr(t *testing.T) { 30 | // 创建一个结构体切片 31 | structSlice := []MyStruct{ 32 | {"first", 1}, 33 | {"second", 2}, 34 | {"third", 3}, 35 | } 36 | 37 | // 获取切片的反射值对象 38 | sliceValue := reflect.ValueOf(structSlice) 39 | // 遍历切片 40 | for i := 0; i < sliceValue.Len(); i++ { 41 | // 获取切片元素 42 | elem := sliceValue.Index(i) 43 | // 获取元素的地址 44 | elemAddr := elem.Addr().Interface() 45 | // 打印元素的地址 46 | fmt.Printf("Element %d address: %v\n", i, elemAddr) 47 | } 48 | } 49 | 50 | func Test_cookieCutterSell(t *testing.T) { 51 | cookieCutterSell() 52 | } 53 | 54 | func TestFinal(t *testing.T) { 55 | var list []string = nil 56 | v := slices.Contains(list, "1") 57 | fmt.Println(v) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /services/task_trader.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/trader" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/gox/api" 7 | "gitee.com/quant1x/gox/logger" 8 | "gitee.com/quant1x/gox/runtime" 9 | ) 10 | 11 | // 同步委托订单 12 | func jobSyncTraderOrders() { 13 | defer runtime.IgnorePanic("") 14 | // 非交易日直接退出 15 | if !exchange.DateIsTradingDay() { 16 | return 17 | } 18 | name := trader.GetOrderFilename() 19 | // 检查文件最后修改时间, 如果文件存在, 且时间在收盘之后, 则跳过同步 20 | stat, err := api.GetFileStat(name) 21 | if err == nil && stat != nil { 22 | modTime := stat.LastWriteTime.Format(exchange.CN_SERVERTIME_FORMAT) 23 | if modTime >= exchange.CN_CallAuctionPmEnd { 24 | return 25 | } 26 | } 27 | logger.Info("同步交易订单...") 28 | defer logger.Info("同步交易订单...OK") 29 | list, err := trader.QueryOrders() 30 | if err != nil || len(list) == 0 { 31 | logger.Info("同步交易订单...今日未操作") 32 | return 33 | } 34 | _ = api.SlicesToCsv(name, list) 35 | } 36 | -------------------------------------------------------------------------------- /services/task_trader_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "testing" 4 | 5 | func Test_syncTraderOrders(t *testing.T) { 6 | jobSyncTraderOrders() 7 | } 8 | -------------------------------------------------------------------------------- /services/task_update_all.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/engine/storages" 7 | "gitee.com/quant1x/exchange" 8 | "time" 9 | ) 10 | 11 | var ( 12 | // 非交易日每天更新一次 13 | lastUpdateTime = "22:00:00.000" 14 | // 交易日每天更新2次 15 | allDateUpdateTimes = []string{"15:10:00.000", lastUpdateTime} 16 | ) 17 | 18 | // 任务 - 更新全部数据 19 | func jobUpdateAll() { 20 | now := time.Now() 21 | tm := now.Format(exchange.CN_SERVERTIME_FORMAT) 22 | today := exchange.Today() 23 | lastDate := exchange.LastTradeDate() 24 | bUpdated := false 25 | phase := "" 26 | if today == lastDate { 27 | for _, v := range allDateUpdateTimes { 28 | if tm >= v { 29 | phase = v 30 | bUpdated = checkUpdateState(today, phase) 31 | if bUpdated { 32 | break 33 | } 34 | } 35 | } 36 | } else { 37 | if tm >= lastUpdateTime { 38 | phase = lastUpdateTime 39 | bUpdated = checkUpdateState(today, phase) 40 | } 41 | } 42 | if bUpdated && len(phase) > 0 { 43 | factors.SwitchDate(cache.DefaultCanReadDate()) 44 | updateAll() 45 | doneUpdate(today, phase) 46 | } 47 | } 48 | 49 | func updateAll() { 50 | barIndex := 1 51 | currentDate := cache.DefaultCanUpdateDate() 52 | cacheDate, featureDate := cache.CorrectDate(currentDate) 53 | updateAllBaseData(barIndex, featureDate) 54 | updateAllFeatures(barIndex+1, cacheDate, featureDate) 55 | } 56 | 57 | func updateAllBaseData(barIndex int, featureDate string) { 58 | // 1. 获取全部注册的数据集插件 59 | mask := cache.PluginMaskBaseData 60 | plugins := cache.Plugins(mask) 61 | // 2. 执行操作 62 | storages.DataSetUpdate(barIndex, featureDate, plugins, cache.OpUpdate) 63 | } 64 | 65 | func updateAllFeatures(barIndex int, cacheDate, featureDate string) { 66 | // 1. 获取全部注册的数据集插件 67 | mask := cache.PluginMaskFeature 68 | plugins := cache.Plugins(mask) 69 | storages.FeaturesUpdate(&barIndex, cacheDate, featureDate, plugins, cache.OpUpdate) 70 | } 71 | -------------------------------------------------------------------------------- /services/task_update_all_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "testing" 4 | 5 | func Test_updateAll(t *testing.T) { 6 | updateAll() 7 | } 8 | -------------------------------------------------------------------------------- /services/task_update_misc_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/models" 5 | "testing" 6 | ) 7 | 8 | func TestRealtimeUpdateExchangeAndSnapshot(t *testing.T) { 9 | models.SyncAllSnapshots(nil) 10 | realtimeUpdateMiscAndSnapshot() 11 | } 12 | -------------------------------------------------------------------------------- /services/task_update_rzrq.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/engine/market" 7 | "gitee.com/quant1x/gox/api" 8 | "gitee.com/quant1x/gox/logger" 9 | ) 10 | 11 | func jobUpdateMarginTrading() { 12 | logger.Infof("同步融资融券...") 13 | date := cache.DefaultCanReadDate() 14 | factors.MarginTradingTargetInit(date) 15 | updateMarginTradingForMisc(date) 16 | updateMarginTradingForRzrq(date) 17 | logger.Infof("同步融资融券...OK") 18 | } 19 | 20 | func updateMarginTradingForMisc(cacheDate string) { 21 | allCodes := market.GetCodeList() 22 | for _, securityCode := range allCodes { 23 | misc := factors.GetL5Misc(securityCode, cacheDate) 24 | if misc == nil { 25 | continue 26 | } 27 | rzrq, ok := factors.GetMarginTradingTarget(securityCode) 28 | if ok { 29 | misc.RZYEZB = rzrq.RZYEZB 30 | misc.UpdateTime = factors.GetTimestamp() 31 | factors.UpdateL5Misc(misc) 32 | } 33 | } 34 | factors.RefreshL5Misc() 35 | } 36 | 37 | func updateMarginTradingForRzrq(cacheDate string) { 38 | allCodes := market.GetCodeList() 39 | for _, securityCode := range allCodes { 40 | smt := factors.GetL5SecuritiesMarginTrading(securityCode, cacheDate) 41 | if smt == nil { 42 | continue 43 | } 44 | rzrq, ok := factors.GetMarginTradingTarget(securityCode) 45 | if ok { 46 | _ = api.Copy(smt, &rzrq) 47 | smt.UpdateTime = factors.GetTimestamp() 48 | smt.State |= factors.FeatureSecuritiesMarginTrading 49 | factors.UpdateL5SecuritiesMarginTrading(smt) 50 | } 51 | } 52 | factors.RefreshL5SecuritiesMarginTrading() 53 | } 54 | -------------------------------------------------------------------------------- /services/task_update_rzrq_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "testing" 4 | 5 | func Test_updateMarginTrading(t *testing.T) { 6 | jobUpdateMarginTrading() 7 | } 8 | -------------------------------------------------------------------------------- /services/task_update_snapshot.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/models" 5 | "gitee.com/quant1x/exchange" 6 | "gitee.com/quant1x/gox/logger" 7 | "gitee.com/quant1x/gox/runtime" 8 | "time" 9 | ) 10 | 11 | // 任务 - 更新快照 12 | func jobUpdateSnapshot() { 13 | tm := time.Now() 14 | updateInRealTime, status := exchange.CanUpdateInRealtime(tm) 15 | // 交易时间更新数据 16 | if updateInRealTime && (IsTrading(status) || exchange.CheckCallAuctionClose(tm)) { 17 | realtimeUpdateSnapshot() 18 | } else { 19 | if runtime.Debug() { 20 | realtimeUpdateSnapshot() 21 | } 22 | } 23 | } 24 | 25 | // 更新快照 26 | func realtimeUpdateSnapshot() { 27 | logger.Infof("同步snapshot...") 28 | models.SyncAllSnapshots(nil) 29 | logger.Infof("同步snapshot...OK") 30 | } 31 | -------------------------------------------------------------------------------- /storages/backtest.go: -------------------------------------------------------------------------------- 1 | package storages 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/engine/market" 7 | "gitee.com/quant1x/gox/logger" 8 | "gitee.com/quant1x/gox/progressbar" 9 | "gitee.com/quant1x/pkg/runewidth" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // FeaturesBackTest FeaturesUpdate 特征-数据有效性验证 15 | func FeaturesBackTest(barIndex *int, cacheDate, featureDate string, plugins []cache.DataAdapter, op cache.OpKind) []cache.AdapterMetric { 16 | moduleName := cache.OpMap[op] + "特征数据" 17 | moduleName += cacheDate 18 | var adapters []factors.FeatureRotationAdapter 19 | maxWidth := 0 20 | for _, plugin := range plugins { 21 | adapter, ok := plugin.(factors.FeatureRotationAdapter) 22 | if ok { 23 | adapters = append(adapters, adapter) 24 | width := runewidth.StringWidth(adapter.Name()) 25 | if width > maxWidth { 26 | maxWidth = width 27 | } 28 | } 29 | } 30 | logger.Infof("%s: all, begin", moduleName) 31 | 32 | var wgAdapter sync.WaitGroup 33 | cacheCount := len(adapters) 34 | barAdapter := progressbar.NewBar(*barIndex, "执行["+moduleName+"]", cacheCount) 35 | allCodes := market.GetCodeList() 36 | allCodes = allCodes[:] 37 | codeCount := len(allCodes) 38 | var metrics []cache.AdapterMetric 39 | for _, adapter := range adapters { 40 | logger.Infof("%s: %s, begin", moduleName, adapter.Name()) 41 | 42 | wgAdapter.Add(1) 43 | // 加载指定日期的特征 44 | adapter.Checkout(cacheDate) 45 | var sb cache.ScoreBoard 46 | 47 | barCode := progressbar.NewBar(*barIndex+1, "执行["+adapter.Name()+"]", codeCount) 48 | for _, code := range allCodes { 49 | now := time.Now() 50 | passed := false 51 | raw := adapter.Element(code) 52 | kind := adapter.Kind() 53 | sb.From(cache.GetDataAdapter(kind)) 54 | // 判断是否实现验证接口 55 | feature, ok := raw.(cache.Validator) 56 | if ok { 57 | err := feature.Check(featureDate) 58 | if err == nil { 59 | passed = true 60 | } 61 | } 62 | sb.Add(1, time.Since(now), passed) 63 | barCode.Add(1) 64 | //wg.Add(1) 65 | //go updateStockFeature(wg, barCode, feature, code, cacheDate, featureDate, op, mapFeature, &sb, now) 66 | } 67 | //wg.Wait() 68 | barCode.Wait() 69 | 70 | // 适配器进度条+1 71 | barAdapter.Add(1) 72 | wgAdapter.Done() 73 | metrics = append(metrics, sb.Metric()) 74 | logger.Infof("%s: %s, end", moduleName, adapter.Name()) 75 | } 76 | wgAdapter.Wait() 77 | barAdapter.Wait() 78 | logger.Infof("%s: all, end", moduleName) 79 | 80 | return metrics 81 | } 82 | -------------------------------------------------------------------------------- /storages/datasets_test.go: -------------------------------------------------------------------------------- 1 | package storages 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "testing" 6 | ) 7 | 8 | func TestBaseDataUpdate(t *testing.T) { 9 | barIndex := 1 10 | date := "2024-01-31" 11 | plugins := cache.PluginsWithName(cache.PluginMaskBaseData, "wide") 12 | DataSetUpdate(barIndex, date, plugins, cache.OpUpdate) 13 | } 14 | -------------------------------------------------------------------------------- /storages/order_state_test.go: -------------------------------------------------------------------------------- 1 | package storages 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/trader" 6 | "gitee.com/quant1x/exchange" 7 | "gitee.com/quant1x/gox/api" 8 | "testing" 9 | ) 10 | 11 | func TestOrderFlag(t *testing.T) { 12 | model := TestModel82{} 13 | date := exchange.LastTradeDate() 14 | code := "sh600178" 15 | direction := trader.BUY 16 | filename := order_state_filename(date, model, direction, code) 17 | fmt.Println(filename) 18 | err := api.Touch(filename) 19 | fmt.Println(err) 20 | ok := CheckOrderState(date, model, code, direction) 21 | fmt.Println(ok) 22 | } 23 | 24 | func TestCountOrderFlag(t *testing.T) { 25 | model := TestModel82{} 26 | date := exchange.LastTradeDate() 27 | direction := trader.BUY 28 | v := CountStrategyOrders(date, model, direction) 29 | fmt.Println(v) 30 | } 31 | 32 | func TestGetOrderDateFirstBuy(t *testing.T) { 33 | date := "2024-05-17" 34 | strategyName := "S0" 35 | direction := trader.BUY 36 | v := FetchListForFirstPurchase(date, strategyName, direction) 37 | fmt.Println(v) 38 | } 39 | -------------------------------------------------------------------------------- /storages/stockpool_merge_test.go: -------------------------------------------------------------------------------- 1 | package storages 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/config" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/engine/models" 7 | "gitee.com/quant1x/gox/concurrent" 8 | ) 9 | 10 | type TestModel struct{} 11 | 12 | func (m TestModel) Code() models.ModelKind { 13 | return 0 14 | } 15 | 16 | func (m TestModel) Name() string { 17 | return "0号策略" 18 | } 19 | 20 | func (m TestModel) OrderFlag() string { 21 | return models.OrderFlagTick 22 | } 23 | 24 | func (m TestModel) Filter(ruleParameter config.RuleParameter, snapshot factors.QuoteSnapshot) error { 25 | //TODO implement me 26 | panic("implement me") 27 | } 28 | 29 | func (m TestModel) Sort(snapshots []factors.QuoteSnapshot) models.SortedStatus { 30 | //TODO implement me 31 | panic("implement me") 32 | } 33 | 34 | func (m TestModel) Evaluate(securityCode string, result *concurrent.TreeMap[string, models.ResultInfo]) { 35 | //TODO implement me 36 | panic("implement me") 37 | } 38 | -------------------------------------------------------------------------------- /storages/stockpool_test.go: -------------------------------------------------------------------------------- 1 | package storages 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gox/api" 6 | "testing" 7 | ) 8 | 9 | func TestStockPool(t *testing.T) { 10 | filename := "./t1.csv" 11 | sp := StockPool{ 12 | Status: 2, 13 | } 14 | sp.Status.Set(StrategyPassed, true) 15 | list := []StockPool{sp} 16 | _ = api.SlicesToCsv(filename, list) 17 | 18 | list1 := []StockPool{} 19 | 20 | _ = api.CsvToSlices(filename, &list1) 21 | sp = list1[0] 22 | fmt.Println(sp.Status.IsCancel()) 23 | } 24 | -------------------------------------------------------------------------------- /storages/stockpool_trade_test.go: -------------------------------------------------------------------------------- 1 | package storages 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/exchange" 6 | "testing" 7 | ) 8 | 9 | func Test_checkOrderForBuy(t *testing.T) { 10 | list := getStockPoolFromCache() 11 | model := TestModel{} 12 | date := exchange.LastTradeDate() 13 | v := checkOrderForBuy(list, model, date) 14 | fmt.Println(v) 15 | saveStockPoolToCache(list) 16 | } 17 | 18 | func Test_strategyOrderIsFinished(t *testing.T) { 19 | v := strategyOrderIsFinished(TestModel{}) 20 | fmt.Println(v) 21 | } 22 | -------------------------------------------------------------------------------- /storages/storage.go: -------------------------------------------------------------------------------- 1 | package storages 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/cache" 5 | "gitee.com/quant1x/engine/config" 6 | "gitee.com/quant1x/engine/models" 7 | "path/filepath" 8 | ) 9 | 10 | var ( 11 | traderConfig = config.TraderConfig() 12 | ) 13 | 14 | const ( 15 | StrategiesPath = "quant" // 策略结果数据文件存储路径 16 | ) 17 | 18 | // GetResultCachePath 获取结果缓存路径 19 | func GetResultCachePath() string { 20 | path := filepath.Join(cache.GetRootPath(), StrategiesPath) 21 | return path 22 | } 23 | 24 | // OutputStatistics 输出策略结果 25 | func OutputStatistics(model models.Strategy, date string, v []models.Statistics) { 26 | tradeRule := config.GetStrategyParameterByCode(model.Code()) 27 | if tradeRule == nil || !tradeRule.Enable() || tradeRule.Total == 0 { 28 | // 配置不存在, 或者规则无效 29 | return 30 | } 31 | topN := tradeRule.Total 32 | stockPoolMerge(model, date, v, topN) 33 | } 34 | -------------------------------------------------------------------------------- /storages/storage_test.go: -------------------------------------------------------------------------------- 1 | package storages 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/config" 6 | "gitee.com/quant1x/engine/factors" 7 | "gitee.com/quant1x/engine/models" 8 | "gitee.com/quant1x/gox/concurrent" 9 | "path/filepath" 10 | "testing" 11 | ) 12 | 13 | type TestModel82 struct{} 14 | 15 | func (TestModel82) Code() models.ModelKind { 16 | return 0 17 | } 18 | 19 | func (TestModel82) Name() string { 20 | //TODO implement me 21 | panic("implement me") 22 | } 23 | 24 | func (TestModel82) OrderFlag() string { 25 | //TODO implement me 26 | panic("implement me") 27 | } 28 | 29 | func (TestModel82) Filter(ruleParameter config.RuleParameter, snapshot factors.QuoteSnapshot) error { 30 | //TODO implement me 31 | panic("implement me") 32 | } 33 | 34 | func (TestModel82) Sort(snapshots []factors.QuoteSnapshot) models.SortedStatus { 35 | //TODO implement me 36 | panic("implement me") 37 | } 38 | 39 | func (TestModel82) Evaluate(securityCode string, result *concurrent.TreeMap[string, models.ResultInfo]) { 40 | //TODO implement me 41 | panic("implement me") 42 | } 43 | 44 | func TestFilePathClean(t *testing.T) { 45 | s := "d:\\quant\\data/qmt/var/20231225/20231225-8881479758-s0-b-*.done" 46 | fmt.Println(filepath.Clean(s)) 47 | } 48 | -------------------------------------------------------------------------------- /strategies/filter.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/config" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/engine/rules" 7 | ) 8 | 9 | // GeneralFilter 过滤条件 10 | // 11 | // 执行所有在册的规则 12 | func GeneralFilter(ruleParameter config.RuleParameter, snapshot factors.QuoteSnapshot) error { 13 | passed, failed, err := rules.Filter(ruleParameter, snapshot) 14 | _ = passed 15 | _ = failed 16 | return err 17 | } 18 | -------------------------------------------------------------------------------- /strategies/no1.tdx: -------------------------------------------------------------------------------- 1 | {1号策略, V1.0.1, 2023-10-10} 2 | MA5:MA(CLOSE,5); 3 | MA10:MA(CLOSE,10); 4 | MA20:MA(CLOSE,20); 5 | C1:CROSS(MA5,MA10),NODRAW; 6 | C2:CROSS(MA10,MA20),NODRAW; 7 | R1:COUNT(C1,3),NODRAW; 8 | R2:COUNT(C2,3),NODRAW; 9 | B:R1 AND R2,NODRAW; -------------------------------------------------------------------------------- /tools/tail.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gox/util/homedir" 6 | "gitee.com/quant1x/pkg/tools/tail" 7 | "strings" 8 | ) 9 | 10 | // TailFile 跟踪文件更新 tail -f 11 | func TailFile(filename string, config tail.Config, done chan bool) { 12 | defer func() { done <- true }() 13 | filename, _ = homedir.Expand(filename) 14 | t, err := tail.TailFile(filename, config) 15 | if err != nil { 16 | fmt.Println(err) 17 | return 18 | } 19 | for line := range t.Lines { 20 | fmt.Println(line.Text) 21 | } 22 | err = t.Wait() 23 | if err != nil { 24 | fmt.Println(err) 25 | } 26 | } 27 | 28 | // TailFileWithNumber 查看最后n行数据 29 | func TailFileWithNumber(filename string, config tail.Config, n int) { 30 | filename, _ = homedir.Expand(filename) 31 | t, err := tail.TailFile(filename, config) 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | 37 | builder := strings.Builder{} 38 | for line := range t.Lines { 39 | builder.WriteString(line.Text + "\n") 40 | } 41 | arr := strings.Split(builder.String(), "\n") 42 | total := len(arr) 43 | pos := n + 1 44 | if n >= total { 45 | n = total 46 | } 47 | //var lines []string 48 | for i, v := range arr { 49 | if i < total-pos { 50 | continue 51 | } 52 | fmt.Printf(v + "\n") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tracker/executor.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/config" 6 | "gitee.com/quant1x/engine/models" 7 | "gitee.com/quant1x/engine/permissions" 8 | "gitee.com/quant1x/gox/logger" 9 | ) 10 | 11 | // ExecuteStrategy 执行策略 12 | func ExecuteStrategy(model models.Strategy, barIndex *int) { 13 | // 策略权限验证 14 | err := permissions.CheckPermission(model) 15 | if err != nil { 16 | logger.Error(err) 17 | fmt.Println(err) 18 | return 19 | } 20 | tradeRule := config.GetStrategyParameterByCode(model.Code()) 21 | if tradeRule == nil { 22 | fmt.Printf("strategy[%d]: trade rule not found\n", model.Code()) 23 | return 24 | } 25 | // 加载快照数据 26 | models.SyncAllSnapshots(barIndex) 27 | // 计算市场情绪 28 | MarketSentiment() 29 | // 扫描板块 30 | ScanAllSectors(barIndex, model) 31 | // 扫描个股 32 | AllScan(barIndex, model) 33 | } 34 | -------------------------------------------------------------------------------- /tracker/mod_sentiment.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/models" 6 | "gitee.com/quant1x/num" 7 | "github.com/fatih/color" 8 | ) 9 | 10 | // MarketSentiment 市场情绪 11 | func MarketSentiment() { 12 | //sh000001 := GetStrategySnapshot("sh000001") 13 | //sz399107 := GetStrategySnapshot("sz399107") 14 | //fmt.Println("上证上涨: ", sh000001.IndexUp) 15 | //fmt.Println("上证下跌: ", sh000001.IndexDown) 16 | //fmt.Println("深证上涨: ", sz399107.IndexUp) 17 | //fmt.Println("深证下跌: ", sz399107.IndexDown) 18 | // 19 | //fmt.Println("上涨: ", sh000001.IndexUp+sz399107.IndexUp) 20 | //fmt.Println("下跌: ", sh000001.IndexDown+sz399107.IndexDown) 21 | // 涨跌家数 22 | zdjs := "sh880005" 23 | ////sh880005 := models.GetTickFromMemory("sh880005") 24 | //tdxApi := gotdx.GetTdxApi() 25 | //defer tdxApi.Close() 26 | //stockShots, _ := tdxApi.GetSnapshot([]string{zdjs}) 27 | //sh880005 := stockShots[0] 28 | sh880005 := models.GetTickFromMemory(zdjs) 29 | //fmt.Printf("%+v\n", sh880005) 30 | up := sh880005.BidVol1 + sh880005.BidVol2 + sh880005.BidVol3 + sh880005.BidVol4 + sh880005.BidVol5 31 | down := sh880005.AskVol1 + sh880005.AskVol2 + sh880005.AskVol3 + sh880005.AskVol4 + sh880005.AskVol5 32 | //fmt.Printf("市场情绪:%.2f\n", 100*num.ChangeRate(up+down, up)) 33 | _, _ = fmt.Fprintf(color.Output, "\n市场情绪:%s\n", color.RedString("%.2f", 100*num.ChangeRate(up+down, up))) 34 | } 35 | -------------------------------------------------------------------------------- /tracker/mod_sentiment_test.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | import "testing" 4 | 5 | func TestMarketSentiment(t *testing.T) { 6 | MarketSentiment() 7 | } 8 | -------------------------------------------------------------------------------- /tracker/mod_sort.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/factors" 5 | "gitee.com/quant1x/num" 6 | ) 7 | 8 | func tickWeight(snapshot factors.QuoteSnapshot) float64 { 9 | // 1. 板块涨幅 10 | weight1 := snapshot.ChangeRate 11 | // 2. 涨速 12 | weight2 := snapshot.Rate 13 | 14 | return num.Mean([]float64{weight1, weight2}) 15 | } 16 | 17 | // SectorSortForTick 板块排序, 盘中 18 | func SectorSortForTick(a, b factors.QuoteSnapshot) bool { 19 | aWeight := tickWeight(a) 20 | bWeight := tickWeight(b) 21 | return aWeight > bWeight 22 | } 23 | 24 | // SectorSortForHead 板块排序, 早盘 25 | func SectorSortForHead(a, b factors.QuoteSnapshot) bool { 26 | if a.OpeningChangeRate > b.OpeningChangeRate { 27 | return true 28 | } 29 | if a.OpeningChangeRate == b.OpeningChangeRate && a.Amount > b.Amount { 30 | return true 31 | } 32 | if a.OpeningChangeRate == b.OpeningChangeRate && a.Amount == b.Amount && a.OpenTurnZ > b.OpenTurnZ { 33 | return true 34 | } 35 | return false 36 | } 37 | 38 | // StockSort 个股排序 39 | func StockSort(a, b factors.QuoteSnapshot) bool { 40 | if a.ChangeRate > b.ChangeRate { 41 | return true 42 | } 43 | if a.ChangeRate == b.ChangeRate && a.OpenTurnZ > b.OpenTurnZ { 44 | return true 45 | } 46 | return false 47 | } 48 | -------------------------------------------------------------------------------- /tracker/radar.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | const ( 4 | SecurityUnknown = "unknown" 5 | ) 6 | 7 | // 市场雷达 8 | -------------------------------------------------------------------------------- /tracker/radar_test.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/config" 6 | "testing" 7 | ) 8 | 9 | func TestConfig(t *testing.T) { 10 | strategyCode := 82 11 | rule := config.GetStrategyParameterByCode(uint64(strategyCode)) 12 | fmt.Println(rule) 13 | list := rule.StockList() 14 | fmt.Println(list) 15 | } 16 | -------------------------------------------------------------------------------- /tracker/sector_filter.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | import ( 4 | "slices" 5 | ) 6 | 7 | // 策略常量 8 | const ( 9 | StoreSnapshotTimeBegin = "09:25:00" // 缓存快照数据的开始时间 10 | StoreSnapshotTimeEnd = "09:29:59" // 缓存快照数据的结束时间 11 | SectorMinChangeRate = 0.10 // 板块开盘最小涨幅 12 | SectorMinVolume = 1e8 // 板块开盘最低成交金额 13 | ) 14 | 15 | var ( 16 | blockIgnoreList = []string{"sh880516"} // ST板块 17 | ) 18 | 19 | // 板块过滤规则, 早盘 20 | func sectorFilterForHead(info SectorInfo) bool { 21 | if slices.Contains(blockIgnoreList, info.Code) { 22 | return false 23 | } 24 | if info.OpenChangeRate <= SectorMinChangeRate { 25 | return false 26 | } 27 | if info.OpenAmount <= SectorMinVolume { 28 | return false 29 | } 30 | return true 31 | } 32 | 33 | // 板块过滤规则, 盘中 34 | func sectorFilterForTick(info SectorInfo) bool { 35 | if slices.Contains(blockIgnoreList, info.Code) { 36 | return false 37 | } 38 | if info.ChangeRate <= SectorMinChangeRate { 39 | return false 40 | } 41 | return true 42 | } 43 | 44 | // 盘中过滤规则 45 | func tickSectorFilter(info SectorInfo) bool { 46 | if slices.Contains(blockIgnoreList, info.Code) { 47 | return false 48 | } 49 | if info.OpenChangeRate <= SectorMinChangeRate { 50 | return false 51 | } 52 | if info.OpenAmount <= SectorMinVolume { 53 | return false 54 | } 55 | if info.ChangeRate < 0.00 { 56 | return false 57 | } 58 | return true 59 | } 60 | -------------------------------------------------------------------------------- /tracker/sector_snapshot.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/engine/models" 7 | "gitee.com/quant1x/gotdx/securities" 8 | "gitee.com/quant1x/gox/api" 9 | "gitee.com/quant1x/gox/progressbar" 10 | "gitee.com/quant1x/num" 11 | ) 12 | 13 | // 板块扫描 14 | func scanSectorSnapshots(pbarIndex *int, blockType securities.BlockType, isHead bool) (list []factors.QuoteSnapshot) { 15 | // 执行板块指数的检测 16 | blockInfos := securities.BlockList() 17 | // 获取指定类型的板块代码列表 18 | var blockCodes []string 19 | for _, v := range blockInfos { 20 | if v.Type != blockType { 21 | continue 22 | } 23 | blockCode := v.Code 24 | blockCodes = append(blockCodes, blockCode) 25 | blockTypeName, _ := securities.BlockTypeNameByTypeCode(v.Type) 26 | __mapBlockTypeName[blockCode] = blockTypeName 27 | } 28 | 29 | blockCount := len(blockCodes) 30 | fmt.Println() 31 | btn, ok := securities.BlockTypeNameByTypeCode(blockType) 32 | if !ok { 33 | btn = num.AnyToString(blockType) 34 | } 35 | bar := progressbar.NewBar(*pbarIndex, "执行[扫描"+btn+"板块指数]", blockCount) 36 | *pbarIndex++ 37 | for i := 0; i < blockCount; i++ { 38 | bar.Add(1) 39 | blockCode := blockCodes[i] 40 | snapshot := models.GetStrategySnapshot(blockCode) 41 | if snapshot == nil { 42 | continue 43 | } 44 | list = append(list, *snapshot) 45 | } 46 | if isHead { 47 | api.SliceSort(list, SectorSortForHead) 48 | } else { 49 | api.SliceSort(list, SectorSortForTick) 50 | } 51 | return list 52 | } 53 | -------------------------------------------------------------------------------- /tracker/sector_test.go: -------------------------------------------------------------------------------- 1 | package tracker 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/gotdx/securities" 6 | "gitee.com/quant1x/pandas" 7 | "testing" 8 | ) 9 | 10 | func Test_scanBlock(t *testing.T) { 11 | pbarIndex := 0 12 | data := scanSectorSnapshots(&pbarIndex, securities.BK_HANGYE, false) 13 | df := pandas.LoadStructs(data) 14 | fmt.Println(df) 15 | } 16 | -------------------------------------------------------------------------------- /trader/account_test.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/config" 6 | "testing" 7 | ) 8 | 9 | func TestCalculateAvailableFund(t *testing.T) { 10 | id := 2 11 | tradeRule := config.GetStrategyParameterByCode(uint64(id)) 12 | if tradeRule == nil { 13 | return 14 | } 15 | fmt.Println(tradeRule) 16 | fund := CalculateAvailableFund(tradeRule) 17 | fmt.Println(fund) 18 | } 19 | -------------------------------------------------------------------------------- /trader/fee_test.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFundAllocate(t *testing.T) { 9 | traderParameter.ResetPositionRatio() 10 | fmt.Println(traderParameter) 11 | } 12 | 13 | func TestEvaluateFeeForBuy(t *testing.T) { 14 | code := "sh600178" 15 | price := 8.17 16 | 17 | v := EvaluateFeeForBuy(code, traderParameter.BuyAmountMax, price) 18 | fmt.Println(v) 19 | v.log() 20 | } 21 | 22 | func TestEvaluateFeeForSell(t *testing.T) { 23 | code := "sh600178" 24 | price := 15.24 25 | volume := 5000 26 | 27 | v := EvaluateFeeForSell(code, price, volume) 28 | fmt.Println(v) 29 | v.log() 30 | } 31 | 32 | func TestEvaluatePriceForSell(t *testing.T) { 33 | fixedYield := 0.03 34 | code := "sh600178" 35 | price := 15.24 36 | volume := 5000 37 | baseAmount := price * float64(volume) 38 | fmt.Println(baseAmount) 39 | v := EvaluatePriceForSell(code, price, volume, fixedYield) 40 | fmt.Println(v, v.MarketValue/baseAmount >= (1+fixedYield)) 41 | v.log() 42 | } 43 | -------------------------------------------------------------------------------- /trader/holdingorders_test.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gitee.com/quant1x/gox/api" 7 | "testing" 8 | ) 9 | 10 | func Test_lazyLoadHoldingOrder(t *testing.T) { 11 | lazyLoadHoldingOrder() 12 | data, err := json.Marshal(holdingOrders) 13 | fmt.Println(data, err) 14 | if err != nil { 15 | return 16 | } 17 | text := api.Bytes2String(data) 18 | fmt.Println(text) 19 | } 20 | -------------------------------------------------------------------------------- /trader/orders.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "gitee.com/quant1x/exchange" 5 | "gitee.com/quant1x/gox/api" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | // GetOrderFilename 获得订单文件名 11 | // 12 | // qmt/账户id/orders.yyyy-mm-dd 13 | func GetOrderFilename(date ...string) string { 14 | var tradeDate string 15 | if len(date) > 0 { 16 | tradeDate = exchange.FixTradeDate(date[0]) 17 | } else { 18 | tradeDate = exchange.LastTradeDate() 19 | } 20 | filename := filepath.Join(traderQmtOrderPath, "orders."+tradeDate) 21 | return filename 22 | } 23 | 24 | // GetOrderList 获取指定日期的订单列表 25 | func GetOrderList(date string) []OrderDetail { 26 | filename := GetOrderFilename(date) 27 | var list []OrderDetail 28 | _ = api.CsvToSlices(filename, &list) 29 | return list 30 | } 31 | 32 | // GetLocalOrderDates 获取本地订单日期列表 33 | func GetLocalOrderDates() (list []string) { 34 | prefix := "orders." 35 | pattern := filepath.Join(traderQmtOrderPath, prefix+"*") 36 | files, err := filepath.Glob(pattern) 37 | if err != nil || len(files) == 0 { 38 | return list 39 | } 40 | for _, filename := range files { 41 | arr := strings.Split(filename, prefix) 42 | if len(arr) != 2 { 43 | continue 44 | } 45 | list = append(list, arr[1]) 46 | } 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /trader/orders_test.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetOrderDates(t *testing.T) { 9 | list := GetLocalOrderDates() 10 | fmt.Println(list) 11 | } 12 | -------------------------------------------------------------------------------- /trader/positions_test.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/models" 5 | "testing" 6 | ) 7 | 8 | func TestCacheSync(t *testing.T) { 9 | barIndex := 1 10 | models.SyncAllSnapshots(&barIndex) 11 | //UpdatePositions() 12 | SyncPositions() 13 | CacheSync() 14 | } 15 | -------------------------------------------------------------------------------- /trader/safes_test.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestProhibitTradingToBlackList(t *testing.T) { 9 | code := "sh000001" 10 | ProhibitTradingToBlackList(code) 11 | time.Sleep(1 * time.Second) 12 | ProhibitTradingToBlackList("sh000002") 13 | time.Sleep(20 * time.Second) 14 | } 15 | 16 | func TestAddCodeToBlackList(t *testing.T) { 17 | code := "sh603230" 18 | secureType := FreeTrading 19 | AddCodeToBlackList(code, secureType) 20 | } 21 | -------------------------------------------------------------------------------- /trader/trader_test.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/models" 6 | "gitee.com/quant1x/engine/strategies" 7 | "testing" 8 | ) 9 | 10 | func TestQueryAccount(t *testing.T) { 11 | info, err := QueryAccount() 12 | fmt.Println(info, err) 13 | } 14 | 15 | func TestQueryHolding(t *testing.T) { 16 | info, err := QueryHolding() 17 | fmt.Println(info, err) 18 | } 19 | 20 | func TestQueryOrders(t *testing.T) { 21 | info, err := QueryOrders() 22 | fmt.Println(info, err) 23 | } 24 | 25 | func TestTradeCancelOrder(t *testing.T) { 26 | orderId := 1086140321 27 | err := CancelOrder(orderId) 28 | fmt.Println(err) 29 | } 30 | 31 | func TestTradePlaceOrder(t *testing.T) { 32 | direction := BUY 33 | model := strategies.ModelNo1{} 34 | securityCode := "sh600178" 35 | price := 13.68 36 | volume := 100 37 | 38 | orderId, err := PlaceOrder(direction, model, securityCode, FIX_PRICE, price, volume) 39 | fmt.Println(orderId, err) 40 | } 41 | 42 | func TestCalculateFundForStrategy(t *testing.T) { 43 | var model models.Strategy 44 | model = new(TestModel) 45 | fund := CalculateFundForStrategy(model) 46 | fmt.Println(fund) 47 | } 48 | -------------------------------------------------------------------------------- /trader/trader_test_model.go: -------------------------------------------------------------------------------- 1 | package trader 2 | 3 | import ( 4 | "gitee.com/quant1x/engine/config" 5 | "gitee.com/quant1x/engine/factors" 6 | "gitee.com/quant1x/engine/models" 7 | "gitee.com/quant1x/gox/concurrent" 8 | ) 9 | 10 | type TestModel struct{} 11 | 12 | func (TestModel) Code() models.ModelKind { 13 | return 82 14 | } 15 | 16 | func (s TestModel) Name() string { 17 | //TODO implement me 18 | panic("implement me") 19 | } 20 | 21 | func (s TestModel) OrderFlag() string { 22 | //TODO implement me 23 | panic("implement me") 24 | } 25 | 26 | func (s TestModel) Filter(ruleParameter config.RuleParameter, snapshot factors.QuoteSnapshot) error { 27 | //TODO implement me 28 | panic("implement me") 29 | } 30 | 31 | func (s TestModel) Sort(snapshots []factors.QuoteSnapshot) models.SortedStatus { 32 | //TODO implement me 33 | panic("implement me") 34 | } 35 | 36 | func (s TestModel) Evaluate(securityCode string, result *concurrent.TreeMap[string, models.ResultInfo]) { 37 | //TODO implement me 38 | panic("implement me") 39 | } 40 | -------------------------------------------------------------------------------- /utils/brower.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os/exec" 5 | "runtime" 6 | ) 7 | 8 | func OpenURL(url string) error { 9 | var cmd *exec.Cmd 10 | switch runtime.GOOS { 11 | case "darwin": 12 | // macOS 13 | cmd = exec.Command("open", url) 14 | case "windows": 15 | // Windows 16 | cmd = exec.Command("cmd", "/c", "start", url) 17 | default: 18 | // Linux 或其他操作系统 19 | cmd = exec.Command("xdg-open", url) 20 | } 21 | err := cmd.Start() 22 | if err != nil { 23 | return err 24 | } 25 | 26 | err = cmd.Wait() 27 | if err != nil { 28 | return err 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /utils/datetime.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "time" 4 | 5 | // Timestamp 毫秒时间戳 6 | func Timestamp() (timestamp int64) { 7 | now := time.Now() 8 | timestamp = now.UnixMilli() 9 | return timestamp 10 | } 11 | -------------------------------------------------------------------------------- /utils/gitcmd.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | "syscall" 12 | ) 13 | 14 | // Config for git command 15 | type Config struct { 16 | Bin string // default "git" 17 | } 18 | 19 | // Client of git command 20 | type Client interface { 21 | CanExec() error 22 | Exec(string, ...string) (string, error) 23 | InsideWorkTree() error 24 | } 25 | 26 | type clientImpl struct { 27 | config *Config 28 | } 29 | 30 | // NewGit git command client 31 | func NewGit(config *Config) Client { 32 | bin := "git" 33 | 34 | if config != nil { 35 | if config.Bin != "" { 36 | bin = config.Bin 37 | } 38 | } 39 | 40 | return &clientImpl{ 41 | config: &Config{ 42 | Bin: bin, 43 | }, 44 | } 45 | } 46 | 47 | // CanExec check whether the git command is executable 48 | func (client *clientImpl) CanExec() error { 49 | _, err := exec.LookPath(client.config.Bin) 50 | if err != nil { 51 | return fmt.Errorf("\"%s\" does not exists", client.config.Bin) 52 | } 53 | return nil 54 | } 55 | 56 | // Exec executes the git command 57 | func (client *clientImpl) Exec(subcmd string, args ...string) (string, error) { 58 | arr := append([]string{subcmd}, args...) 59 | 60 | var out bytes.Buffer 61 | cmd := exec.Command(client.config.Bin, arr...) 62 | cmd.Stdout = &out 63 | cmd.Stderr = ioutil.Discard 64 | 65 | err := cmd.Run() 66 | var exitError *exec.ExitError 67 | if errors.As(err, &exitError) { 68 | if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok { 69 | if waitStatus.ExitStatus() != 0 { 70 | return "", err 71 | } 72 | } 73 | } 74 | 75 | return strings.TrimRight(strings.TrimSpace(out.String()), "\000"), nil 76 | } 77 | 78 | // InsideWorkTree check whether the current working directory is inside the git repository 79 | func (client *clientImpl) InsideWorkTree() error { 80 | out, err := client.Exec("rev-parse", "--is-inside-work-tree") 81 | if err != nil { 82 | return err 83 | } 84 | 85 | if out != "true" { 86 | cwd, err := os.Getwd() 87 | if err != nil { 88 | return err 89 | } 90 | return fmt.Errorf("\"%s\" is no git repository", cwd) 91 | } 92 | 93 | return nil 94 | } 95 | 96 | var ( 97 | git = NewGit(nil) 98 | ) 99 | -------------------------------------------------------------------------------- /utils/optimize.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "gitee.com/quant1x/gox/logger" 6 | "gitee.com/quant1x/num" 7 | "golang.org/x/sys/cpu" 8 | ) 9 | 10 | var ( 11 | ErrAccelerationNotSupported = errors.New("acceleration not supported on this platform") 12 | ) 13 | 14 | // Optimize 系统优化系列 15 | func Optimize() { 16 | // 如果支持AVX2就打开 17 | if cpu.X86.HasAVX2 && cpu.X86.HasFMA { 18 | num.SetAvx2Enabled(true) 19 | } else { 20 | logger.Warn(ErrAccelerationNotSupported) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /utils/paging.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // GetPages 计算页数 4 | func GetPages(pageSize, count int) (pages int) { 5 | //pages = int(math.Ceil(float64(raw.Data.TotalHits) / float64(EastmoneyNoticesPageSize))) 6 | pages = count / pageSize 7 | n := count % pageSize 8 | if n > 0 { 9 | pages++ 10 | } 11 | return pages 12 | } 13 | -------------------------------------------------------------------------------- /utils/paging_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetPages(t *testing.T) { 9 | fmt.Println(GetPages(100, 130)) 10 | } 11 | -------------------------------------------------------------------------------- /utils/series.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "gitee.com/quant1x/num" 5 | "gitee.com/quant1x/pandas" 6 | ) 7 | 8 | func IndexReverse(s pandas.Series) pandas.Series { 9 | var indexes []int 10 | rows := s.Len() 11 | s.Apply(func(idx int, v any) { 12 | indexes = append(indexes, rows-idx) 13 | }) 14 | return pandas.ToSeries(indexes...) 15 | } 16 | 17 | // SeriesIndexOf 获取序列第n索引的值 18 | // 19 | // Deprecated: 推荐使用 Float64IndexOf 20 | func SeriesIndexOf(s pandas.Series, n int) float64 { 21 | v := s.IndexOf(n) 22 | return num.AnyToFloat64(v) 23 | } 24 | 25 | // SeriesChangeRate 计算两个序列的净增长 26 | func SeriesChangeRate(base, v pandas.Series) pandas.Series { 27 | chg := v.Div(base).Sub(1.00).Mul(100) 28 | return chg 29 | } 30 | 31 | func StringIndexOf(s pandas.Series, n int) string { 32 | v := s.IndexOf(n) 33 | return num.AnyToString(v) 34 | } 35 | 36 | func BoolIndexOf(s pandas.Series, n int) bool { 37 | v := s.IndexOf(n) 38 | return num.AnyToBool(v) 39 | } 40 | 41 | func Float64IndexOf(s pandas.Series, n int) float64 { 42 | v := s.IndexOf(n) 43 | return num.AnyToFloat64(v) 44 | } 45 | 46 | func IntegerIndexOf(s pandas.Series, n int) int { 47 | v := s.IndexOf(n) 48 | return int(num.AnyToInt64(v)) 49 | } 50 | 51 | func Int64IndexOf(s pandas.Series, n int) int64 { 52 | v := s.IndexOf(n) 53 | return num.AnyToInt64(v) 54 | } 55 | -------------------------------------------------------------------------------- /utils/shell.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io/ioutil" 7 | "os/exec" 8 | "strings" 9 | "syscall" 10 | ) 11 | 12 | func shell(bin string, args ...string) (string, error) { 13 | var out bytes.Buffer 14 | cmd := exec.Command(bin, args...) 15 | cmd.Stdout = &out 16 | cmd.Stderr = ioutil.Discard 17 | 18 | err := cmd.Run() 19 | var exitError *exec.ExitError 20 | if errors.As(err, &exitError) { 21 | if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok { 22 | if waitStatus.ExitStatus() != 0 { 23 | return "", err 24 | } 25 | } 26 | } 27 | 28 | return strings.TrimRight(strings.TrimSpace(out.String()), "\000"), nil 29 | } 30 | -------------------------------------------------------------------------------- /utils/template_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/quant1x/engine/cache" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | var propertyList = ` 11 | 12 | 13 | 14 | KeepAlive 15 | 16 | Label 17 | {{.Name}} 18 | ProgramArguments 19 | 20 | {{.Path}} 21 | daemon 22 | 23 | RunAtLoad 24 | 25 | WorkingDirectory 26 | ${ROOT_PATH} 27 | StandardErrorPath 28 | ${LOG_PATH}/{{.Name}}.err 29 | StandardOutPath 30 | ${LOG_PATH}/{{.Name}}.log 31 | 32 | 33 | ` 34 | 35 | func TestTemplate(t *testing.T) { 36 | replacer := strings.NewReplacer("${ROOT_PATH}", cache.GetRootPath(), "${LOG_PATH}", cache.GetLoggerPath()) 37 | //v := strings.ReplaceAll(propertyList, "${PATH}", cache.GetRootPath()) 38 | v := replacer.Replace(propertyList) 39 | fmt.Println(v) 40 | } 41 | -------------------------------------------------------------------------------- /utils/version.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | const ( 8 | InvalidVersion = "0.0.0" 9 | ) 10 | 11 | // CurrentVersion 开发中获取版本号 12 | func CurrentVersion() string { 13 | minVersion := InvalidVersion 14 | latest, err := git.Exec("describe", "--tags", "--abbrev=0") 15 | if err == nil { 16 | minVersion = NormalizeVersion(latest) 17 | } 18 | return minVersion 19 | } 20 | 21 | // RequireVersion 依赖模块版本号 22 | // 23 | // 通过 go list -m 命令获取 24 | func RequireVersion(module string) string { 25 | minVersion := InvalidVersion 26 | mod, err := shell("go", "list", "-m", module) 27 | if err == nil { 28 | arr := strings.Split(mod, " ") 29 | if len(arr) >= 2 { 30 | minVersion = NormalizeVersion(arr[1]) 31 | } 32 | } 33 | return minVersion 34 | } 35 | 36 | // NormalizeVersion 表示将版本号格式化为标准形式 37 | // 38 | // 去掉版本号前的字符v或V 39 | func NormalizeVersion(version string) string { 40 | latest := strings.TrimSpace(version) 41 | for len(latest) > 0 && (latest[0] == 'v' || latest[0] == 'V') { 42 | latest = latest[1:] 43 | } 44 | return latest 45 | } 46 | --------------------------------------------------------------------------------