├── .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 |
--------------------------------------------------------------------------------