diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..8b3fcd8f7d7d59e63c17aaab183e62f3b5294d49 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +stock-strategy.exe diff --git a/config.json b/config.json new file mode 100644 index 0000000000000000000000000000000000000000..7e58bdc7e2b231796e0e8750f16515a35a7bc5d2 --- /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 0000000000000000000000000000000000000000..538f92d3a67eed3e8bae97ee50778540b09e97d6 --- /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/go.mod b/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..0fceae7c988e2c6a444f2221dbd2ae32247eeb64 --- /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 0000000000000000000000000000000000000000..61a486d978d3231a2e52e8a7eaff5d7ea6d269a1 --- /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 0000000000000000000000000000000000000000..7f8a982392c67064590cbd177e8da14560c40950 --- /dev/null +++ b/main.go @@ -0,0 +1,229 @@ +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"` + 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"` +} + +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", dsn) + 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) + + // 获取股票详细信息 + 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 + } + } +} + +// 判断股票是否满足条件 +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 +}