From 525267fd51272b03626a731df9833d2f0b641a0a Mon Sep 17 00:00:00 2001 From: ld Date: Tue, 6 May 2025 11:48:06 +0800 Subject: [PATCH 1/2] =?UTF-8?q?1.=E9=80=89=E8=82=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + go.mod | 8 ++++ go.sum | 7 ++++ main.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b3fcd8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +stock-strategy.exe diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0fceae7 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module stock-strategy + +go 1.21 + +require ( + github.com/go-sql-driver/mysql v1.7.1 + github.com/jmoiron/sqlx v1.3.5 +) \ No newline at end of file diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..61a486d --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= diff --git a/main.go b/main.go new file mode 100644 index 0000000..da34d72 --- /dev/null +++ b/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "fmt" + "log" + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/jmoiron/sqlx" +) + +type StockData struct { + Symbol string `db:"symbol"` + Exchange string `db:"exchange"` + DateTime time.Time `db:"datetime"` + Interval string `db:"interval"` + Volume float64 `db:"volume"` + Turnover float64 `db:"turnover"` + OpenInterest float64 `db:"open_interest"` + OpenPrice float64 `db:"open_price"` + HighPrice float64 `db:"high_price"` + LowPrice float64 `db:"low_price"` + ClosePrice float64 `db:"close_price"` +} + +func main() { + // 数据库连接配置 + db, err := sqlx.Connect("mysql", "ubuntu:Ld123456.@tcp(119.29.231.143:3306)/new_quant?parseTime=true") + if err != nil { + log.Fatalf("数据库连接失败: %v", err) + } + defer db.Close() + + // 获取最近一个月的股票数据 + oneMonthAgo := time.Now().AddDate(0, -1, 0) + query := ` + SELECT symbol, exchange, datetime, ` + "`interval`" + `, volume, turnover, open_interest, + open_price, high_price, low_price, close_price + FROM dbbardata + WHERE datetime >= ? + AND ` + "`interval`" + ` = 'd' + AND (symbol LIKE '60%' OR symbol LIKE '00%') + ORDER BY symbol, datetime DESC + ` + + var stocks []StockData + err = db.Select(&stocks, query, oneMonthAgo) + if err != nil { + log.Fatalf("查询数据失败: %v", err) + } + + // 按股票代码分组处理数据 + stockMap := make(map[string][]StockData) + for _, stock := range stocks { + key := stock.Symbol + stockMap[key] = append(stockMap[key], stock) + } + + // 筛选符合条件的股票 + var qualifiedStocks []string + for symbol, data := range stockMap { + if isQualified(data) { + qualifiedStocks = append(qualifiedStocks, symbol) + } + } + + // 输出结果 + fmt.Println("符合条件的股票代码:") + for _, symbol := range qualifiedStocks { + fmt.Println(symbol) + } +} + +// 判断股票是否满足条件 +func isQualified(data []StockData) bool { + if len(data) < 10 { + return false + } + + // 查找最近10个交易日内的涨停 + var lastLimitUpIndex int = -1 + for i := 0; i < min(10, len(data)); i++ { + if isLimitUp(data[i]) { + lastLimitUpIndex = i + } + } + + if lastLimitUpIndex == -1 { + return false + } + + // 检查最后一次涨停后的所有收盘价是否都大于涨停价 + limitUpPrice := data[lastLimitUpIndex].ClosePrice + for i := 0; i < lastLimitUpIndex; i++ { + if data[i].ClosePrice <= limitUpPrice { + return false + } + } + + return true +} + +// 判断是否涨停 +func isLimitUp(data StockData) bool { + // 涨停判断逻辑:当日收盘价等于当日最高价,且涨幅大于等于9.9% + // 由于没有前收盘价,我们使用前一天的收盘价作为参考 + limitUpRate := 0.099 + return data.ClosePrice == data.HighPrice && data.ClosePrice >= data.OpenPrice*(1+limitUpRate) +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} -- Gitee From 30d71365c649b31a3dc1dc9c2898ca48cc6c236a Mon Sep 17 00:00:00 2001 From: ld Date: Wed, 7 May 2025 17:06:38 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E7=AD=96=E7=95=A5=E9=80=89=E8=82=A1?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=85=A5=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.json | 9 ++++ create_table.sql | 14 ++++++ main.go | 117 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 config.json create mode 100644 create_table.sql diff --git a/config.json b/config.json new file mode 100644 index 0000000..7e58bdc --- /dev/null +++ b/config.json @@ -0,0 +1,9 @@ +{ + "database": { + "host": "119.29.231.143", + "port": 3306, + "username": "ubuntu", + "password": "Ld123456.", + "dbname": "new_quant" + } +} \ No newline at end of file diff --git a/create_table.sql b/create_table.sql new file mode 100644 index 0000000..538f92d --- /dev/null +++ b/create_table.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS `stock_pool` ( + `id` int NOT NULL AUTO_INCREMENT, + `exchange` varchar(10) NOT NULL COMMENT '市场', + `symbol` varchar(10) NOT NULL COMMENT '股票代码', + `name` varchar(50) DEFAULT NULL COMMENT '股票名称', + `in_date` date NOT NULL COMMENT '入选日期', + `out_date` date DEFAULT NULL COMMENT '调出日期', + `region` varchar(50) DEFAULT NULL COMMENT '所属地区', + `industry` varchar(50) DEFAULT NULL COMMENT '所属行业', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_symbol_exchange_in_date` (`symbol`,`exchange`,`in_date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='股票池'; \ No newline at end of file diff --git a/main.go b/main.go index da34d72..7f8a982 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,27 @@ package main import ( + "database/sql" + "encoding/json" "fmt" "log" + "os" "time" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) +type Config struct { + Database struct { + Host string `json:"host"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + DBName string `json:"dbname"` + } `json:"database"` +} + type StockData struct { Symbol string `db:"symbol"` Exchange string `db:"exchange"` @@ -23,9 +36,95 @@ type StockData struct { ClosePrice float64 `db:"close_price"` } +type StockInfo struct { + TsCode string `db:"ts_code"` + Symbol string `db:"symbol"` + Name string `db:"name"` + Area string `db:"area"` + Industry string `db:"industry"` + Fullname string `db:"fullname"` + Enname string `db:"enname"` + Cnspell string `db:"cnspell"` + Market string `db:"market"` + Exchange string `db:"exchange"` + CurrType string `db:"curr_type"` + ListStatus string `db:"list_status"` + ListDate sql.NullString `db:"list_date"` + DelistDate sql.NullString `db:"delist_date"` + IsHs string `db:"is_hs"` + ActName sql.NullString `db:"act_name"` + ActEntType sql.NullString `db:"act_ent_type"` +} + +func loadConfig() (*Config, error) { + file, err := os.Open("config.json") + if err != nil { + return nil, fmt.Errorf("打开配置文件失败: %v", err) + } + defer file.Close() + + var config Config + decoder := json.NewDecoder(file) + if err := decoder.Decode(&config); err != nil { + return nil, fmt.Errorf("解析配置文件失败: %v", err) + } + + return &config, nil +} + +func getStockInfo(db *sqlx.DB, symbol string) (*StockInfo, error) { + query := ` + SELECT ts_code, symbol, name, area, industry, fullname, enname, cnspell, + market, exchange, curr_type, list_status, list_date, delist_date, + is_hs, act_name, act_ent_type + FROM stock_basic + WHERE symbol = ? + LIMIT 1 + ` + var stockInfo StockInfo + err := db.Get(&stockInfo, query, symbol) + if err != nil { + return nil, err + } + return &stockInfo, nil +} + +func insertStockPool(db *sqlx.DB, stockInfo *StockInfo) error { + query := ` + INSERT INTO stock_pool (exchange, symbol, name, in_date, region, industry) + VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + out_date = NULL + ` + _, err := db.Exec(query, + stockInfo.Exchange, + stockInfo.Symbol, + stockInfo.Name, + time.Now().Format("2006-01-02"), + stockInfo.Area, + stockInfo.Industry, + ) + return err +} + func main() { + // 加载配置文件 + config, err := loadConfig() + if err != nil { + log.Fatalf("加载配置失败: %v", err) + } + + // 构建数据库连接字符串 + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true", + config.Database.Username, + config.Database.Password, + config.Database.Host, + config.Database.Port, + config.Database.DBName, + ) + // 数据库连接配置 - db, err := sqlx.Connect("mysql", "ubuntu:Ld123456.@tcp(119.29.231.143:3306)/new_quant?parseTime=true") + db, err := sqlx.Connect("mysql", dsn) if err != nil { log.Fatalf("数据库连接失败: %v", err) } @@ -64,10 +163,24 @@ func main() { } } - // 输出结果 + // 输出结果并写入股票池 fmt.Println("符合条件的股票代码:") for _, symbol := range qualifiedStocks { fmt.Println(symbol) + + // 获取股票详细信息 + stockInfo, err := getStockInfo(db, symbol) + if err != nil { + log.Printf("获取股票 %s 信息失败: %v", symbol, err) + continue + } + + // 写入股票池 + err = insertStockPool(db, stockInfo) + if err != nil { + log.Printf("写入股票池失败 %s: %v", symbol, err) + continue + } } } -- Gitee