├── project.json ├── trading ├── holding.go ├── trend.go ├── turtle.go └── test.go ├── .gitignore ├── README.md ├── config └── config.go ├── data ├── peroid_exterma.go ├── turtle.go └── query.go └── main.go /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerAddress":"http://52.69.228.175:602" 3 | } -------------------------------------------------------------------------------- /trading/holding.go: -------------------------------------------------------------------------------- 1 | package trading 2 | 3 | import "time" 4 | 5 | // 头寸 6 | type Holding struct { 7 | StartTime time.Time // 起始时间 8 | StartPrice float32 // 起始价格 9 | Direction bool // 做多 10 | Quantity int // 数量 11 | // EndTime time.Time // 结束时间 12 | // EndPrice float32 // 结束价格 13 | // Profit float32 // 利润 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # regimentation 2 | ### 纪律性和系统化是一个交易系统的灵魂,regimentation就是一个发掘股票盈利交易系统的项目 3 | 4 | 功能实现 5 | - [ ] 交易系统测试框架 6 | - [ ] 交易系统迭代 7 | - [ ] 操作 8 | - [ ] 参数迭代 9 | - [ ] 初始化 10 | - [ ] 启动 11 | - [ ] 结束 12 | - [ ] 取消 13 | - [ ] 注资 14 | - [ ] 撤资 15 | - [ ] 结果记录 16 | - [ ] 过程图形显示 17 | - [ ] 数据准备指标计算 18 | - [ ] 分时数据(1min) 19 | - [ ] 区间数据(>1min) 20 | - [ ] 交易系统实现 21 | - [ ] 海归交易系统 22 | - [ ] 海归指标 23 | - [ ] 交易系统 24 | -------------------------------------------------------------------------------- /trading/trend.go: -------------------------------------------------------------------------------- 1 | package trading 2 | 3 | import "time" 4 | 5 | // 趋势 6 | type Trend struct { 7 | StartTime time.Time // 起始时间 8 | StartPrice float32 // 起始价格 9 | StartReason string // 起始原因 10 | Direction bool // 做多 11 | EndTime time.Time // 结束时间 12 | EndPrice float32 // 结束价格 13 | EndReason string // 结束原因 14 | Profit float32 // 利润 15 | ProfitPercent float32 // 利润率 16 | Holdings []Holding // 头寸 17 | } 18 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "path/filepath" 7 | 8 | "github.com/nzai/go-utility/io" 9 | "github.com/nzai/go-utility/path" 10 | ) 11 | 12 | const ( 13 | configFile = "project.json" 14 | ) 15 | 16 | type Config struct { 17 | ServerAddress string 18 | } 19 | 20 | // 当前系统配置 21 | var configValue *Config = nil 22 | 23 | // 设置配置文件 24 | func ReadConfig() error { 25 | 26 | root, err := path.GetStartupDir() 27 | if err != nil { 28 | return fmt.Errorf("获取起始目录失败:%s", err.Error()) 29 | } 30 | 31 | // 构造配置文件路径 32 | filePath := filepath.Join(root, configFile) 33 | if !io.IsExists(filePath) { 34 | return fmt.Errorf("配置文件%s不存在", filePath) 35 | } 36 | 37 | // 读取文件 38 | buffer, err := io.ReadAllBytes(filePath) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | // 解析配置项 44 | configValue = &Config{} 45 | err = json.Unmarshal(buffer, configValue) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | if configValue == nil { 51 | return fmt.Errorf("配置文件错误") 52 | } 53 | 54 | return nil 55 | } 56 | 57 | // 获取当前系统配置 58 | func Get() *Config { 59 | return configValue 60 | } 61 | -------------------------------------------------------------------------------- /data/peroid_exterma.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type PeroidExtermaIndex struct { 9 | High float32 // 最大值 10 | Low float32 // 最小值 11 | } 12 | 13 | type PeroidExtermaIndexes struct { 14 | indexes map[int]map[time.Time]PeroidExtermaIndex // 字典 15 | } 16 | 17 | func (t *PeroidExtermaIndexes) Init(histories []PeroidHistory, peroids ...int) error { 18 | if t.indexes == nil { 19 | t.indexes = make(map[int]map[time.Time]PeroidExtermaIndex) 20 | } 21 | 22 | for _, peroid := range peroids { 23 | dict, err := t.calculate(histories, peroid) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | t.indexes[peroid] = dict 29 | } 30 | 31 | return nil 32 | } 33 | 34 | // 计算 35 | func (t *PeroidExtermaIndexes) calculate(histories []PeroidHistory, peroid int) (map[time.Time]PeroidExtermaIndex, error) { 36 | 37 | if peroid < 2 { 38 | return nil, fmt.Errorf("peroid必须大于等于2") 39 | } 40 | 41 | start := 0 42 | dict := make(map[time.Time]PeroidExtermaIndex) 43 | for index, history := range histories { 44 | 45 | if index >= peroid { 46 | start = index - peroid 47 | } 48 | 49 | high := histories[start].High 50 | low := histories[start].Low 51 | 52 | for hi := start + 1; hi < index; hi++ { 53 | if histories[hi].High > high { 54 | high = histories[hi].High 55 | } 56 | 57 | if histories[hi].Low < low { 58 | low = histories[hi].Low 59 | } 60 | } 61 | 62 | dict[history.Time] = PeroidExtermaIndex{High: high, Low: low} 63 | } 64 | 65 | return dict, nil 66 | } 67 | 68 | // 查询 69 | func (t *PeroidExtermaIndexes) Get(peroid int, _time time.Time) (*PeroidExtermaIndex, error) { 70 | 71 | indexes, found := t.indexes[peroid] 72 | if !found { 73 | return nil, fmt.Errorf("没有找到区间%d在%s的极值", peroid, _time.Format("2006-01-02")) 74 | } 75 | 76 | index, found := indexes[_time] 77 | if !found { 78 | return nil, fmt.Errorf("没有找到区间%d在%s的极值", peroid, _time.Format("2006-01-02")) 79 | } 80 | 81 | return &index, nil 82 | } 83 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/nzai/regimentation/config" 8 | 9 | "github.com/nzai/regimentation/trading" 10 | ) 11 | 12 | func main() { 13 | 14 | defer func() { 15 | // 捕获panic异常 16 | if err := recover(); err != nil { 17 | log.Print("发生了致命错误:", err) 18 | } 19 | }() 20 | 21 | // 读取配置文件 22 | err := config.ReadConfig() 23 | if err != nil { 24 | log.Fatal("读取配置文件错误: ", err) 25 | } 26 | 27 | start, err := time.Parse("20060102", "20150801") 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | end, err := time.Parse("20060102", "20160201") 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | // 海龟交易系统演算范围 37 | system := trading.TurtleSystem{ 38 | Market: "America", 39 | Code: "AAPL", 40 | StartTime: start, 41 | StartAmount: 100000, 42 | EndTime: end, 43 | Peroid: 240, 44 | MinSetting: trading.TurtleSetting{Holding: 1, N: 2, Enter: 2, Exit: 2, Stop: 2}, 45 | MaxSetting: trading.TurtleSetting{Holding: 8, N: 50, Enter: 50, Exit: 50, Stop: 50}} 46 | 47 | // 初始化 48 | err = system.Init() 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | // 演算 54 | system.Simulate() 55 | 56 | // go system.SimulateResultProcess() 57 | 58 | // system.SimulateOnce(system.MinSetting) 59 | // log.Printf("Profit:%.3f", system.CurrentProfit) 60 | 61 | // time.Sleep(time.Second * 5) 62 | // 极值测试 63 | // for index, history := range system.MinutePeroids { 64 | // if index >= 40 { 65 | // break 66 | // } 67 | 68 | // // pe, err := system.PeroidExtermaIndexes.Get(4, history.Time) 69 | // // if err != nil { 70 | // // log.Fatal(err) 71 | // // } 72 | 73 | // // log.Printf("%s %.3f %.3f [4] %.3f %.3f", history.Time.Format("2006-01-02 15:04"), history.High, history.Low, pe.Max, pe.Min) 74 | 75 | // tu, err := system.TurtleIndexes.Get(4, history.Time) 76 | // if err != nil { 77 | // log.Fatal(err) 78 | // } 79 | 80 | // log.Printf("%s %.3f %.3f %.3f [4] %.3f %.3f", 81 | // history.Time.Format("2006-01-02 15:04"), 82 | // history.High, 83 | // history.Low, 84 | // history.Close, 85 | // tu.TR, 86 | // tu.N) 87 | // } 88 | } 89 | -------------------------------------------------------------------------------- /data/turtle.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type TurtleIndex struct { 9 | N float32 // 波动性均值 10 | TR float32 // 真实波动性 11 | } 12 | 13 | type TurtleIndexes struct { 14 | indexes map[int]map[time.Time]TurtleIndex // 字典 15 | } 16 | 17 | // 初始化 18 | func (t *TurtleIndexes) Init(histories []PeroidHistory, peroids ...int) error { 19 | if t.indexes == nil { 20 | t.indexes = make(map[int]map[time.Time]TurtleIndex) 21 | } 22 | 23 | for _, peroid := range peroids { 24 | dict, err := t.calculate(histories, peroid) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | t.indexes[peroid] = dict 30 | } 31 | 32 | return nil 33 | } 34 | 35 | // 计算 36 | func (t *TurtleIndexes) calculate(histories []PeroidHistory, peroid int) (map[time.Time]TurtleIndex, error) { 37 | 38 | if peroid < 2 { 39 | return nil, fmt.Errorf("peroid必须大于等于2") 40 | } 41 | 42 | var yesterdayN float32 = 0 43 | var tr float32 = 0 44 | var n float32 = 0 45 | 46 | dict := make(map[time.Time]TurtleIndex) 47 | for index, history := range histories { 48 | 49 | // 真实波动幅度TR = Max(high – low,history - yesterdayClose,yesterdayClose - low) 50 | 51 | if index <= 1 { 52 | tr = histories[0].High - histories[0].Low 53 | n = tr / float32(peroid) 54 | } else { 55 | tr = histories[index-1].High - histories[index-1].Low 56 | 57 | if histories[index-1].High-histories[index-2].Close > tr { 58 | tr = histories[index-1].High - histories[index-2].Close 59 | } 60 | 61 | if histories[index-2].Close-histories[index-1].Low > tr { 62 | tr = histories[index-2].Close - histories[index-1].Low 63 | } 64 | 65 | // 真实波动幅度的20日指数移动平均值 N = (19 * PDN + TR) / 20 66 | n = (float32(peroid-1)*yesterdayN + tr) / float32(peroid) 67 | } 68 | 69 | dict[history.Time] = TurtleIndex{N: n, TR: tr} 70 | 71 | yesterdayN = n 72 | } 73 | 74 | return dict, nil 75 | } 76 | 77 | // 查询 78 | func (t *TurtleIndexes) Get(peroid int, _time time.Time) (*TurtleIndex, error) { 79 | 80 | indexes, found := t.indexes[peroid] 81 | if !found { 82 | return nil, fmt.Errorf("没有找到区间%d在%s的海龟指标", peroid, _time.Format("2006-01-02")) 83 | } 84 | 85 | index, found := indexes[_time] 86 | if !found { 87 | return nil, fmt.Errorf("没有找到区间%d在%s的海龟指标", peroid, _time.Format("2006-01-02")) 88 | } 89 | 90 | return &index, nil 91 | } 92 | -------------------------------------------------------------------------------- /data/query.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/nzai/go-utility/net" 12 | utime "github.com/nzai/go-utility/time" 13 | "github.com/nzai/regimentation/config" 14 | "github.com/nzai/stockrecorder/server/result" 15 | ) 16 | 17 | // 每分钟历史 18 | type MinuteHistory struct { 19 | Market string 20 | Code string 21 | Time time.Time 22 | Open float32 23 | Close float32 24 | High float32 25 | Low float32 26 | Volume int64 27 | } 28 | 29 | // 查询分时数据 30 | func QueryMinuteHistories(market, code string, start, end time.Time) ([]MinuteHistory, error) { 31 | 32 | log.Print("ServerAddress:", config.Get().ServerAddress) 33 | // url := path.Join(config.Get().ServerAddress, market, code, start, end, "1m") 34 | url := fmt.Sprintf("%s/%s/%s/%s/%s/1m", 35 | config.Get().ServerAddress, 36 | strings.ToLower(market), 37 | strings.ToLower(code), 38 | start.Format("20060102"), 39 | end.Format("20060102")) 40 | // url := "http://52.69.228.175:602/america/aapl/20151101/20151111/1m" 41 | // url := "http://localhost:602/america/aapl/20151101/20151111/1m" 42 | // log.Print("url:", url) 43 | content, err := net.DownloadStringRetry(url, 3, 10) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | r := result.HttpResult{} 49 | err = json.Unmarshal([]byte(content), &r) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | if !r.Success { 55 | return nil, fmt.Errorf("从服务器查询分时数据出错:%s", r.Message) 56 | } 57 | 58 | objs, ok := r.Data.([]interface{}) 59 | if !ok { 60 | return nil, fmt.Errorf("转换Data出错:%v", r.Data) 61 | } 62 | 63 | upperMarket := strings.Title(market) 64 | upperCode := strings.ToUpper(code) 65 | 66 | var currentTradingDate time.Time 67 | histories := make([]MinuteHistory, 0) 68 | for _, obj := range objs { 69 | //log.Print(reflect.TypeOf(obj).String()) 70 | values, ok := obj.([]interface{}) 71 | if !ok { 72 | return nil, fmt.Errorf("转换Data item出错:%v", obj) 73 | } 74 | 75 | _time, err := time.Parse("0601021504", strconv.FormatInt(int64(values[0].(float64)), 10)) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | date := utime.BeginOfDay(_time) 81 | if !date.Equal(currentTradingDate) { 82 | currentTradingDate = date 83 | } 84 | 85 | histories = append(histories, MinuteHistory{ 86 | Market: upperMarket, 87 | Code: upperCode, 88 | Time: _time, 89 | Open: float32(values[1].(float64)) / 1000, 90 | Close: float32(values[2].(float64)) / 1000, 91 | High: float32(values[3].(float64)) / 1000, 92 | Low: float32(values[4].(float64)) / 1000, 93 | Volume: int64(values[5].(float64))}) 94 | } 95 | 96 | return histories, nil 97 | } 98 | 99 | // 区间历史 100 | type PeroidHistory struct { 101 | Time time.Time 102 | Open float32 103 | Close float32 104 | High float32 105 | Low float32 106 | Volume int64 107 | Minutes []MinuteHistory 108 | } 109 | 110 | // 转化区间历史 111 | func ParsePeroidHistory(histories []MinuteHistory, peroid int) ([]PeroidHistory, error) { 112 | if len(histories) == 0 { 113 | return []PeroidHistory{}, nil 114 | } 115 | 116 | var ph *PeroidHistory = nil 117 | end := histories[0].Time.Add(-time.Minute) 118 | 119 | phs := make([]PeroidHistory, 0) 120 | for _, history := range histories { 121 | 122 | if history.Time.After(end) { 123 | if ph != nil { 124 | phs = append(phs, *ph) 125 | } 126 | 127 | end = history.Time.Add(time.Minute * time.Duration(peroid)) 128 | 129 | ph = &PeroidHistory{ 130 | Time: history.Time, 131 | Open: history.Open, 132 | Close: history.Close, 133 | High: history.High, 134 | Low: history.Low, 135 | Volume: history.Volume, 136 | Minutes: make([]MinuteHistory, 0)} 137 | 138 | continue 139 | } 140 | 141 | if history.High > ph.High { 142 | ph.High = history.High 143 | } 144 | 145 | if history.Low < ph.Low { 146 | ph.Low = history.Low 147 | } 148 | 149 | ph.Close = history.Close 150 | ph.Volume += history.Volume 151 | ph.Minutes = append(ph.Minutes, history) 152 | } 153 | 154 | if ph != nil { 155 | phs = append(phs, *ph) 156 | } 157 | 158 | return phs, nil 159 | } 160 | -------------------------------------------------------------------------------- /trading/turtle.go: -------------------------------------------------------------------------------- 1 | package trading 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math" 7 | "sync" 8 | "time" 9 | 10 | "github.com/nzai/regimentation/data" 11 | ) 12 | 13 | const ( 14 | SimulateGCCount = 32 15 | ProgressDelaySecond = 5 16 | ) 17 | 18 | // 海龟设定 19 | type TurtleSetting struct { 20 | Holding int // 头寸划分 21 | N int // 实波动幅度区间 22 | Enter int // 入市区间 23 | Exit int // 退市区间 24 | Stop int // 止损区间 25 | } 26 | 27 | // 海龟演算结果 28 | type TurtleSimulateResult struct { 29 | Setting TurtleSetting // 海龟设定 30 | Profit float32 // 利润 31 | ProfitPercent float32 // 利润率 32 | } 33 | 34 | // 海龟系统 35 | type TurtleSystem struct { 36 | Market string // 市场 37 | Code string // 上市公司 38 | StartTime time.Time // 起始时间 39 | StartAmount float32 // 起始资金 40 | EndTime time.Time // 结束时间 41 | EndAmount float32 // 结束资金 42 | Peroid int // 区间 43 | MinSetting TurtleSetting // 最小设定 44 | MaxSetting TurtleSetting // 最大设定 45 | 46 | CurrentSetting *TurtleSetting // 当前设定 47 | CurrentProfit float32 // 当前利润 48 | CurrentProfitPercent float32 // 当前利润率 49 | 50 | Caculated int // 当前的计算量 51 | TotalAmount int // 总的计算量 52 | 53 | BestSetting *TurtleSetting // 最佳设定 54 | BestProfit float32 // 最佳利润 55 | BestProfitPercent float32 // 最佳利润率 56 | 57 | PeroidHistories []data.PeroidHistory // 区间历史 58 | 59 | PeroidExtermaIndexes *data.PeroidExtermaIndexes // 区间极值 60 | TurtleIndexes *data.TurtleIndexes // 海龟指标 61 | 62 | simulateResultChannel chan TurtleSimulateResult // 验算结果发送通道 63 | } 64 | 65 | // 初始化 66 | func (t *TurtleSystem) Init() error { 67 | 68 | log.Print("初始化开始") 69 | 70 | // 分时数据 71 | mhs, err := data.QueryMinuteHistories(t.Market, t.Code, t.StartTime, t.EndTime) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | // 转化为区间历史 77 | phs, err := data.ParsePeroidHistory(mhs, t.Peroid) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | t.PeroidHistories = phs 83 | 84 | // 区间极值 85 | minPeroid := t.MinSetting.Enter 86 | if t.MinSetting.Exit < minPeroid { 87 | minPeroid = t.MinSetting.Exit 88 | } 89 | 90 | maxPeroid := t.MaxSetting.Enter 91 | if t.MaxSetting.Exit > maxPeroid { 92 | maxPeroid = t.MaxSetting.Exit 93 | } 94 | 95 | peroids := make([]int, 0) 96 | for peroid := minPeroid; peroid <= maxPeroid; peroid++ { 97 | peroids = append(peroids, peroid) 98 | } 99 | // log.Printf("peroids:%v", peroids) 100 | t.PeroidExtermaIndexes = &data.PeroidExtermaIndexes{} 101 | t.PeroidExtermaIndexes.Init(t.PeroidHistories, peroids...) 102 | 103 | // 海龟指标 104 | ns := make([]int, 0) 105 | for n := t.MinSetting.N; n <= t.MaxSetting.N; n++ { 106 | ns = append(ns, n) 107 | } 108 | // log.Printf("ns:%v", ns) 109 | t.TurtleIndexes = &data.TurtleIndexes{} 110 | t.TurtleIndexes.Init(t.PeroidHistories, ns...) 111 | 112 | t.TotalAmount = (t.MaxSetting.Holding - t.MinSetting.Holding + 1) * 113 | (t.MaxSetting.N - t.MinSetting.N + 1) * 114 | (t.MaxSetting.Enter - t.MinSetting.Enter + 1) * 115 | (t.MaxSetting.Exit - t.MinSetting.Exit + 1) * 116 | (t.MaxSetting.Stop - t.MinSetting.Stop + 1) 117 | 118 | t.Caculated = 0 119 | 120 | // 初始化演算结果通道 121 | t.simulateResultChannel = make(chan TurtleSimulateResult) 122 | 123 | log.Print("初始化结束") 124 | 125 | return nil 126 | } 127 | 128 | // 演算 129 | func (t *TurtleSystem) Simulate() { 130 | 131 | log.Print("[Simulate]\t演算开始") 132 | 133 | t.EndAmount = t.StartAmount 134 | t.BestProfit = -math.MaxFloat32 135 | 136 | // 进度显示 137 | go t.simulateProgress() 138 | 139 | // 演算结果处理 140 | go t.SimulateResultProcess() 141 | 142 | defer close(t.simulateResultChannel) 143 | 144 | chanSend := make(chan int, SimulateGCCount) 145 | defer close(chanSend) 146 | 147 | var wg sync.WaitGroup 148 | wg.Add(t.TotalAmount) 149 | 150 | for stop := t.MinSetting.Stop; stop <= t.MaxSetting.Stop; stop++ { 151 | for exit := t.MinSetting.Exit; exit <= t.MaxSetting.Exit; exit++ { 152 | for enter := t.MinSetting.Enter; enter <= t.MaxSetting.Enter; enter++ { 153 | for n := t.MinSetting.N; n <= t.MaxSetting.N; n++ { 154 | for holding := t.MinSetting.Holding; holding <= t.MaxSetting.Holding; holding++ { 155 | 156 | // 并发演算 157 | go func(setting TurtleSetting) { 158 | err := t.SimulateOnce(setting) 159 | if err != nil { 160 | log.Print(err.Error()) 161 | } 162 | 163 | <-chanSend 164 | wg.Done() 165 | }(TurtleSetting{Holding: holding, N: n, Enter: enter, Exit: exit, Stop: stop}) 166 | 167 | chanSend <- 1 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | // 阻塞,直到演算完所有组合 175 | wg.Wait() 176 | 177 | log.Printf("[Simulate]\t演算结束,最佳设定Holding:%d N:%d Enter:%d Exit:%d Stop:%d Profit:%f ProfitPercent:%.3f%%", 178 | t.BestSetting.Holding, 179 | t.BestSetting.N, 180 | t.BestSetting.Enter, 181 | t.BestSetting.Exit, 182 | t.BestSetting.Stop, 183 | t.BestProfit, 184 | t.BestProfitPercent*100) 185 | } 186 | 187 | // 演算一种配置 188 | func (t *TurtleSystem) SimulateOnce(setting TurtleSetting) error { 189 | 190 | // 新的测试 191 | newTest := &TurtleSystemTest{ 192 | TurtleSystem: t, 193 | TurtleSetting: &setting, 194 | StartAmount: t.StartAmount, 195 | EndAmount: t.StartAmount, 196 | Trends: []Trend{}} 197 | 198 | // 演算 199 | err := newTest.Simulate() 200 | if err != nil { 201 | return fmt.Errorf("[Error]\tHolding:%d N:%d Enter:%d Exit:%d Stop:%d Error:%s", 202 | t.CurrentSetting.Holding, 203 | t.CurrentSetting.N, 204 | t.CurrentSetting.Enter, 205 | t.CurrentSetting.Exit, 206 | t.CurrentSetting.Stop, 207 | err.Error()) 208 | } 209 | 210 | // 发送演算结果 211 | t.simulateResultChannel <- TurtleSimulateResult{ 212 | Setting: setting, 213 | Profit: newTest.Profit, 214 | ProfitPercent: newTest.ProfitPercent} 215 | 216 | return nil 217 | } 218 | 219 | // 演算进度 220 | func (t *TurtleSystem) simulateProgress() { 221 | // 定时任务 222 | ticker := time.NewTicker(time.Second * ProgressDelaySecond) 223 | start := time.Now() 224 | 225 | for _ = range ticker.C { 226 | if t.CurrentSetting != nil { 227 | dur := time.Now().Sub(start) 228 | speed := float64(t.Caculated) / dur.Seconds() 229 | remainSeconds := float64(t.TotalAmount-t.Caculated) / speed 230 | 231 | remain := time.Second * time.Duration(remainSeconds) 232 | 233 | log.Printf("[Current]\tHolding:%d N:%d Enter:%d Exit:%d Stop:%d Profit:%.3f ProfitPercent:%.3f%%\t%d %d %03.2f%% %s", 234 | t.CurrentSetting.Holding, 235 | t.CurrentSetting.N, 236 | t.CurrentSetting.Enter, 237 | t.CurrentSetting.Exit, 238 | t.CurrentSetting.Stop, 239 | t.CurrentProfit, 240 | t.CurrentProfitPercent*100, 241 | t.Caculated, 242 | t.TotalAmount, 243 | float32(t.Caculated)/float32(t.TotalAmount)*100, 244 | remain.String()) 245 | } 246 | 247 | if t.BestSetting != nil { 248 | log.Printf("[Best]\tHolding:%d N:%d Enter:%d Exit:%d Stop:%d Profit:%.3f ProfitPercent:%.3f%%", 249 | t.BestSetting.Holding, 250 | t.BestSetting.N, 251 | t.BestSetting.Enter, 252 | t.BestSetting.Exit, 253 | t.BestSetting.Stop, 254 | t.BestProfit, 255 | t.BestProfitPercent*100) 256 | } 257 | 258 | } 259 | } 260 | 261 | // 演算结果处理 262 | func (t *TurtleSystem) SimulateResultProcess() { 263 | 264 | for { 265 | // 从通道中读取演算结果 266 | result := <-t.simulateResultChannel 267 | 268 | t.CurrentSetting = &result.Setting 269 | t.CurrentProfit = result.Profit 270 | t.CurrentProfitPercent = result.ProfitPercent 271 | 272 | if t.CurrentProfit > t.BestProfit { 273 | t.BestSetting = t.CurrentSetting 274 | t.BestProfit = t.CurrentProfit 275 | t.BestProfitPercent = t.CurrentProfitPercent 276 | 277 | log.Printf("[Best]\tHolding:%d N:%d Enter:%d Exit:%d Stop:%d Profit:%.3f ProfitPercent:%.3f%%", 278 | t.BestSetting.Holding, 279 | t.BestSetting.N, 280 | t.BestSetting.Enter, 281 | t.BestSetting.Exit, 282 | t.BestSetting.Stop, 283 | t.BestProfit, 284 | t.BestProfitPercent*100) 285 | } 286 | 287 | t.Caculated++ 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /trading/test.go: -------------------------------------------------------------------------------- 1 | package trading 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/nzai/regimentation/data" 7 | ) 8 | 9 | type TurtleSystemTest struct { 10 | TurtleSystem *TurtleSystem // 海龟系统 11 | TurtleSetting *TurtleSetting // 海龟设定 12 | 13 | StartAmount float32 // 起始资金 14 | EndAmount float32 // 结束资金 15 | Profit float32 // 利润 16 | ProfitPercent float32 // 利润率 17 | 18 | CurrentTrend *Trend // 当前趋势 19 | Trends []Trend // 趋势 20 | } 21 | 22 | // 是否入市 23 | func (t *TurtleSystemTest) Enter(peroid data.PeroidHistory) (bool, bool, string, error) { 24 | // 当前在趋势中就不重复进入了 25 | if t.CurrentTrend != nil { 26 | return false, false, "", nil 27 | } 28 | 29 | // 查上一交易日的区间极值 30 | peroid_exterma_last_trading_day, err := t.TurtleSystem.PeroidExtermaIndexes.Get(t.TurtleSetting.Enter, peroid.Time) 31 | if err != nil { 32 | return false, false, "", fmt.Errorf("[TutleSystem]\t%s", err.Error()) 33 | } 34 | 35 | // 是否超过极值 36 | if peroid.High > peroid_exterma_last_trading_day.High { 37 | return true, true, fmt.Sprintf("分时价格%.2f突破%d日最大值%.2f", peroid.High, t.TurtleSetting.Enter, peroid_exterma_last_trading_day.High), nil 38 | } 39 | 40 | if peroid.Low < peroid_exterma_last_trading_day.Low { 41 | return true, false, fmt.Sprintf("分时价格%.2f突破%d日最小值%.2f", peroid.Low, t.TurtleSetting.Enter, peroid_exterma_last_trading_day.Low), nil 42 | } 43 | 44 | return false, false, "", nil 45 | } 46 | 47 | // 是否增持 48 | func (t *TurtleSystemTest) Increase(peroid data.PeroidHistory) (bool, string, error) { 49 | // 没有趋势就没有增持 50 | if t.CurrentTrend == nil { 51 | return false, "", nil 52 | } 53 | 54 | // 满仓了就不增持了 55 | if len(t.CurrentTrend.Holdings) >= t.TurtleSetting.Holding { 56 | return false, "", nil 57 | } 58 | 59 | // 海龟指标 60 | turtle_last_trading_date, err := t.TurtleSystem.TurtleIndexes.Get(t.TurtleSetting.N, peroid.Time) 61 | if err != nil { 62 | return false, "", fmt.Errorf("[TutleSystem]\t%s", err.Error()) 63 | } 64 | 65 | lastHolding := t.CurrentTrend.Holdings[len(t.CurrentTrend.Holdings)-1] 66 | 67 | // 是否超过海龟加仓指标 68 | if t.CurrentTrend.Direction && peroid.High > lastHolding.StartPrice+turtle_last_trading_date.N/2 { 69 | return true, fmt.Sprintf("分时价格%.2f突破做多加仓最小值%.2f", peroid.High, lastHolding.StartPrice+turtle_last_trading_date.N/2), nil 70 | } 71 | 72 | if !t.CurrentTrend.Direction && peroid.Low < lastHolding.StartPrice-turtle_last_trading_date.N/2 { 73 | return true, fmt.Sprintf("分时价格%.2f突破做空加仓最小值%.2f", peroid.Low, lastHolding.StartPrice-turtle_last_trading_date.N/2), nil 74 | } 75 | 76 | return false, "", nil 77 | } 78 | 79 | // 是否止盈 80 | func (t *TurtleSystemTest) Exit(peroid data.PeroidHistory) (bool, string, error) { 81 | // 没有趋势就没有止盈 82 | if t.CurrentTrend == nil { 83 | return false, "", nil 84 | } 85 | 86 | // 查昨天的区间极值 87 | peroid_exterma_last_trading_day, err := t.TurtleSystem.PeroidExtermaIndexes.Get(t.TurtleSetting.Exit, peroid.Time) 88 | if err != nil { 89 | return false, "", fmt.Errorf("[TutleSystem]\t%s", err.Error()) 90 | } 91 | 92 | if t.CurrentTrend.Direction && peroid.Low < peroid_exterma_last_trading_day.Low { 93 | return true, fmt.Sprintf("趋势结束 分时价格%.2f突破%d日最小值%.2f", peroid.Low, t.TurtleSetting.Exit, peroid_exterma_last_trading_day.Low), nil 94 | } 95 | 96 | if !t.CurrentTrend.Direction && peroid.High > peroid_exterma_last_trading_day.High { 97 | return true, fmt.Sprintf("趋势结束 分时价格%.2f突破%d日最大值%.2f", peroid.High, t.TurtleSetting.Exit, peroid_exterma_last_trading_day.High), nil 98 | } 99 | 100 | return false, "", nil 101 | } 102 | 103 | // 是否止损 104 | func (t *TurtleSystemTest) Stop(peroid data.PeroidHistory) (bool, string, error) { 105 | // 没有趋势就没有止损 106 | if t.CurrentTrend == nil { 107 | return false, "", nil 108 | } 109 | 110 | lastHolding := t.CurrentTrend.Holdings[len(t.CurrentTrend.Holdings)-1] 111 | 112 | // 海龟指标 113 | turtle_last_trading_day, err := t.TurtleSystem.TurtleIndexes.Get(t.TurtleSetting.N, peroid.Time) 114 | if err != nil { 115 | return false, "", fmt.Errorf("[TutleSystem]\t%s", err.Error()) 116 | } 117 | 118 | if t.CurrentTrend.Direction && peroid.Low < lastHolding.StartPrice-turtle_last_trading_day.N*float32(t.TurtleSetting.Stop) { 119 | return true, fmt.Sprintf("止损 分时价格%.2f突破%d日止损价%.2f", peroid.Low, t.TurtleSetting.Stop, lastHolding.StartPrice-turtle_last_trading_day.N*float32(t.TurtleSetting.Stop)), nil 120 | } 121 | 122 | if !t.CurrentTrend.Direction && peroid.High > lastHolding.StartPrice+turtle_last_trading_day.N*float32(t.TurtleSetting.Stop) { 123 | return true, fmt.Sprintf("止损 分时价格%.2f突破%d日止损价%.2f", peroid.High, t.TurtleSetting.Stop, lastHolding.StartPrice+turtle_last_trading_day.N*float32(t.TurtleSetting.Stop)), nil 124 | } 125 | 126 | return false, "", nil 127 | } 128 | 129 | // 入市 130 | func (t *TurtleSystemTest) DoEnter(peroid data.PeroidHistory, direction bool, reason string) { 131 | 132 | startPrice := peroid.High 133 | if !direction { 134 | startPrice = peroid.Low 135 | } 136 | 137 | quantity := int(t.EndAmount / (startPrice * float32(t.TurtleSetting.Holding))) 138 | 139 | // 启动新趋势 140 | trend := Trend{ 141 | StartTime: peroid.Time, 142 | StartPrice: startPrice, 143 | StartReason: reason, 144 | Direction: direction, 145 | Holdings: make([]Holding, 0)} 146 | 147 | // 第一个头寸 148 | trend.Holdings = append(trend.Holdings, Holding{ 149 | StartTime: trend.StartTime, 150 | StartPrice: trend.StartPrice, 151 | Direction: trend.Direction, 152 | Quantity: quantity}) 153 | 154 | t.CurrentTrend = &trend 155 | } 156 | 157 | // 增持 158 | func (t *TurtleSystemTest) DoIncrease(peroid data.PeroidHistory, reason string) { 159 | 160 | startPrice := peroid.High 161 | if !t.CurrentTrend.Direction { 162 | startPrice = peroid.Low 163 | } 164 | 165 | quantity := int(t.EndAmount / (startPrice * float32(t.TurtleSetting.Holding))) 166 | 167 | t.CurrentTrend.Holdings = append(t.CurrentTrend.Holdings, Holding{ 168 | StartTime: peroid.Time, 169 | StartPrice: startPrice, 170 | Direction: t.CurrentTrend.Direction, 171 | Quantity: quantity}) 172 | } 173 | 174 | // 止盈 175 | func (t *TurtleSystemTest) DoExit(peroid data.PeroidHistory, reason string) { 176 | 177 | endPrice := peroid.Low 178 | if !t.CurrentTrend.Direction { 179 | endPrice = peroid.High 180 | } 181 | 182 | var profit float32 = 0 183 | for _, holding := range t.CurrentTrend.Holdings { 184 | if holding.Direction { 185 | profit += (endPrice - holding.StartPrice) * float32(holding.Quantity) 186 | } else { 187 | profit += (holding.StartPrice - endPrice) * float32(holding.Quantity) 188 | } 189 | } 190 | 191 | t.CurrentTrend.EndPrice = endPrice 192 | t.CurrentTrend.EndTime = peroid.Time 193 | t.CurrentTrend.EndReason = reason 194 | t.CurrentTrend.Profit = profit 195 | t.CurrentTrend.ProfitPercent = profit / t.EndAmount 196 | 197 | // log.Printf("profit:%f q:%d s:%f e:%f", profit, len(t.CurrentTrend.Holdings), t.CurrentTrend.StartPrice, t.CurrentTrend.EndPrice) 198 | t.EndAmount += profit 199 | t.Profit = t.EndAmount - t.StartAmount 200 | t.ProfitPercent = t.Profit / t.StartAmount 201 | 202 | t.Trends = append(t.Trends, *t.CurrentTrend) 203 | 204 | // 趋势结束 205 | t.CurrentTrend = nil 206 | } 207 | 208 | // 演算 209 | func (t *TurtleSystemTest) Simulate() error { 210 | 211 | for _, peroid := range t.TurtleSystem.PeroidHistories { 212 | // 是否入市 213 | enter, direction, reason, err := t.Enter(peroid) 214 | if err != nil { 215 | return err 216 | } 217 | 218 | // 入市 219 | if enter { 220 | t.DoEnter(peroid, direction, reason) 221 | continue 222 | } 223 | 224 | // 是否增持 225 | increase, reason, err := t.Increase(peroid) 226 | if err != nil { 227 | return err 228 | } 229 | 230 | // 增持 231 | if increase { 232 | t.DoIncrease(peroid, reason) 233 | continue 234 | } 235 | 236 | // 是否止盈 237 | exit, reason, err := t.Exit(peroid) 238 | if err != nil { 239 | return err 240 | } 241 | 242 | // 止盈 243 | if exit { 244 | t.DoExit(peroid, reason) 245 | continue 246 | } 247 | 248 | // 是否止损 249 | stop, reason, err := t.Stop(peroid) 250 | if err != nil { 251 | return err 252 | } 253 | 254 | // 止损 255 | if stop { 256 | t.DoExit(peroid, reason) 257 | continue 258 | } 259 | } 260 | 261 | if t.CurrentTrend != nil { 262 | t.DoExit(t.TurtleSystem.PeroidHistories[len(t.TurtleSystem.PeroidHistories)-1], "演算结束") 263 | } 264 | 265 | return nil 266 | } 267 | --------------------------------------------------------------------------------