up
This commit is contained in:
		
							parent
							
								
									64fe4a18b0
								
							
						
					
					
						commit
						7202a98998
					
				
							
								
								
									
										26
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| package config | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"os" | ||||
| ) | ||||
| 
 | ||||
| type Config struct { | ||||
| 	ElasticsearchURL string `json:"elasticsearch_url"` | ||||
| } | ||||
| 
 | ||||
| // LoadConfig 从指定路径加载配置文件 | ||||
| func LoadConfig(path string) (*Config, error) { | ||||
| 	file, err := os.ReadFile(path) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	var config Config | ||||
| 	err = json.Unmarshal(file, &config) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &config, nil | ||||
| } | ||||
							
								
								
									
										5
									
								
								config/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								config/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| { | ||||
|     "elasticsearch_url": "http://fluentd.k8s.xunlang.home" | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										78
									
								
								config/fluentd.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								config/fluentd.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| apiVersion: v1 | ||||
| kind: ConfigMap | ||||
| metadata: | ||||
|   name: fluentd-config | ||||
|   namespace: efk | ||||
| data: | ||||
|   fluent.conf: | | ||||
|     <source> | ||||
|       @type http | ||||
|       @id input_http | ||||
|       port 8888 | ||||
|       tag sardine.log  # 初始 tag,客户端需指定完整 tag 如 sardine.log.candle.BTC-USDT.15M | ||||
|       @label @main | ||||
|       <parse> | ||||
|         @type json | ||||
|       </parse> | ||||
|     </source> | ||||
| 
 | ||||
|     <label @main> | ||||
|       <filter sardine.log.candle.**> | ||||
|         @type record_transformer | ||||
|         enable_ruby true | ||||
|         <record> | ||||
|           coin_pair ${tag_parts[3] || "UNKNOWN"}  # 从 tag 第 4 部分提取,如 BTC-USDT | ||||
|           timeframe ${tag_parts[4] || "UNKNOWN"}  # 从 tag 第 5 部分提取,如 15M | ||||
|           year ${time.strftime("%Y")}             # 从 timestamp 提取年份 | ||||
|           # 生成唯一 _id,例如 BTC-USDT_15M_2024-03-06-12:00:00 | ||||
|           unique_id "${tag_parts[3]}_${tag_parts[4]}_#{record['timestamp'].gsub(/[: ]/, '-')}" | ||||
|           # 修改索引名称,加入 candle | ||||
|           index_name "logstash_candle_${tag_parts[3]}_${time.strftime('%Y')}_${tag_parts[4]}" | ||||
|         </record> | ||||
|       </filter> | ||||
| 
 | ||||
|       <match sardine.log.candle.**> | ||||
|         @type copy | ||||
|         <store> | ||||
|           @type elasticsearch | ||||
|           @id output_elasticsearch_custom | ||||
|           host elasticsearch | ||||
|           port 9200 | ||||
|           scheme http | ||||
|           user fluentd_user | ||||
|           password fluentd_password | ||||
|           logstash_format false | ||||
|           index_name ${record["index_name"]}  # 动态索引,如 logstash_candle_BTC-USDT_2024_15M | ||||
|           id_key unique_id                    # 使用 unique_id 作为 _id 去重 | ||||
|           flush_interval 5s | ||||
|           @log_level debug | ||||
|           remove_keys _id, coin_pair, timeframe, year, unique_id, index_name | ||||
|         </store> | ||||
|         # 保留原有按日期切分的输出(不加 candle) | ||||
|         <store> | ||||
|           @type elasticsearch | ||||
|           @id output_elasticsearch | ||||
|           host elasticsearch | ||||
|           port 9200 | ||||
|           scheme http | ||||
|           user fluentd_user | ||||
|           password fluentd_password | ||||
|           logstash_format true | ||||
|           logstash_prefix logstash | ||||
|           logstash_dateformat %Y.%m.%d | ||||
|           id_key unique_id                    # 同样使用 unique_id 去重 | ||||
|           flush_interval 5s | ||||
|           @log_level debug | ||||
|           remove_keys _id, unique_id | ||||
|         </store> | ||||
|         <store> | ||||
|           @type stdout | ||||
|           @id output_stdout | ||||
|         </store> | ||||
|       </match> | ||||
|     </label> | ||||
| 
 | ||||
|     <match **> | ||||
|       @type stdout | ||||
|       @id output_stdout_all | ||||
|     </match> | ||||
							
								
								
									
										61
									
								
								fluentd.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								fluentd.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
|     <source> | ||||
|       @type http | ||||
|       @id input_http | ||||
|       port 8888 | ||||
|       @label @main | ||||
|     </source> | ||||
| 
 | ||||
|     <label @main> | ||||
|       <match sardine.log.**> | ||||
|         @type copy | ||||
|         <store> | ||||
|           @type elasticsearch | ||||
|           @id output_elasticsearch | ||||
|           host elasticsearch | ||||
|           port 9200 | ||||
|           scheme http | ||||
|           user fluentd_user | ||||
|           password fluentd_password | ||||
|           logstash_format true | ||||
|           logstash_prefix logstash | ||||
|           logstash_dateformat %Y.%m.%d | ||||
|           flush_interval 5s | ||||
|           @log_level debug | ||||
|           id_key _id | ||||
|           remove_keys _id | ||||
|         </store> | ||||
|         <store> | ||||
|           @type stdout | ||||
|           @id output_stdout | ||||
|         </store> | ||||
|       </match> | ||||
| 
 | ||||
|       <match tanya.**> | ||||
|         @type copy | ||||
|         <store> | ||||
|           @type elasticsearch | ||||
|           @id output_elasticsearch_tanya | ||||
|           host elasticsearch | ||||
|           port 9200 | ||||
|           scheme http | ||||
|           user fluentd_user | ||||
|           password fluentd_password | ||||
|           index_name candle_${tag_parts[1]}_${tag_parts[2]}_${tag_parts[3]}_${tag_parts[4]} | ||||
|           flush_interval 5s | ||||
|           @log_level debug | ||||
|           id_key _id | ||||
|           remove_keys _id | ||||
|           include_tag_key true | ||||
|           tag_key @log_name | ||||
|         </store> | ||||
|         <store> | ||||
|           @type stdout | ||||
|           @id output_stdout_tanya | ||||
|         </store> | ||||
|       </match> | ||||
|     </label> | ||||
| 
 | ||||
|     <match **> | ||||
|       @type stdout | ||||
|       @id output_stdout_all | ||||
|     </match> | ||||
| @ -1,9 +1,14 @@ | ||||
| package okx | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/csv" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 
 | ||||
| 	cfg "gitea.zjmud.xyz/phyer/tanya/config" // 请将your_project_path替换为实际的项目路径 | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| @ -15,15 +20,22 @@ func formatTimestamp(ts string) (string, error) { | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return time.UnixMilli(millis).Format("2006-01-02 15:04:05"), nil | ||||
| 	loc, _ := time.LoadLocation("Asia/Shanghai") | ||||
| 	return time.UnixMilli(millis).In(loc).Format("2006-01-02 15:04:05"), nil | ||||
| } | ||||
| 
 | ||||
| // CandleList 封装Candle数组并提供序列化方法 | ||||
| type CandleList struct { | ||||
| 	Candles []*Candle | ||||
| 	Candles  []*Candle | ||||
| 	CoinPair string // 交易对名称,如 BTC-USDT | ||||
| 	Period   string // 周期名称,如 15M, 1D | ||||
| } | ||||
| 
 | ||||
| func MakeCandleList(instId string, period string, startTime time.Time, endTime time.Time, blockSize int) (*CandleList, error) { | ||||
| 	cl := &CandleList{ | ||||
| 		CoinPair: instId, | ||||
| 		Period:   period, | ||||
| 	} | ||||
| 	service := NewOkxPublicDataService() | ||||
| 	var allCandles []*Candle | ||||
| 	currentTime := endTime | ||||
| @ -49,7 +61,7 @@ func MakeCandleList(instId string, period string, startTime time.Time, endTime t | ||||
| 		currentTime = newCurrentTime | ||||
| 	} | ||||
| 	fmt.Println("lens of allCandles: ", len(allCandles)) | ||||
| 	cl := &CandleList{Candles: allCandles} | ||||
| 	cl.Candles = allCandles | ||||
| 	return cl, nil | ||||
| } | ||||
| 
 | ||||
| @ -130,3 +142,188 @@ func (cl *CandleList) ToCsv() (string, error) { | ||||
| 
 | ||||
| 	return sb.String(), nil | ||||
| } | ||||
| 
 | ||||
| // ToEs 将Candle数据发送到Elasticsearch | ||||
| func (cl *CandleList) ToEs() error { | ||||
| 	// 获取当前年份 | ||||
| 	currentYear := time.Now().Year() | ||||
| 
 | ||||
| 	// 构造tag,格式为:tanya.candle.交易对.年份.周期(保持周期原样) | ||||
| 	tag := fmt.Sprintf("tanya.candle.%s.%d.%s", cl.CoinPair, currentYear, cl.Period) | ||||
| 
 | ||||
| 	// 解析周期 | ||||
| 	duration, err := parsePeriod(cl.Period) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("invalid period: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 分批发送,每批最多50条 | ||||
| 	batchSize := 50 | ||||
| 	for i := 0; i < len(cl.Candles); i += batchSize { | ||||
| 		end := i + batchSize | ||||
| 		if end > len(cl.Candles) { | ||||
| 			end = len(cl.Candles) | ||||
| 		} | ||||
| 
 | ||||
| 		// 准备批量数据 | ||||
| 		var records []map[string]interface{} | ||||
| 		for _, candle := range cl.Candles[i:end] { | ||||
| 			// 验证时间戳是否为周期的整数倍 | ||||
| 			ts, err := strconv.ParseInt(candle.Timestamp, 10, 64) | ||||
| 			if err != nil { | ||||
| 				fmt.Println("invalid timestamp:", ts, err) | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			// 转换为东八区时间 | ||||
| 			loc, _ := time.LoadLocation("Asia/Shanghai") | ||||
| 			cstTime := time.UnixMilli(ts).In(loc) | ||||
| 
 | ||||
| 			// 对于日线数据,检查是否为当天的 00:00:00 | ||||
| 			if cl.Period == "1D" { | ||||
| 				if cstTime.Hour() != 0 || cstTime.Minute() != 0 || cstTime.Second() != 0 { | ||||
| 					return fmt.Errorf("timestamp %d (%s) is not aligned with period %s", ts, cstTime.Format("2006-01-02 15:04:05"), cl.Period) | ||||
| 				} | ||||
| 			} else { | ||||
| 				// 对于其他周期,使用原来的对齐检查 | ||||
| 				if cstTime.UnixMilli()%duration.Milliseconds() != 0 { | ||||
| 					return fmt.Errorf("timestamp %d is not aligned with period %s", ts, cl.Period) | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// 格式化时间 | ||||
| 			formattedTime := cstTime.Format("2006-01-02 15:04:05") | ||||
| 
 | ||||
| 			// 构造记录 | ||||
| 			record := map[string]interface{}{ | ||||
| 				"_id":       ts, // 使用时间戳作为_id | ||||
| 				"dataTime":  formattedTime, | ||||
| 				"open":      candle.Open, | ||||
| 				"high":      candle.High, | ||||
| 				"low":       candle.Low, | ||||
| 				"close":     candle.Close, | ||||
| 				"volume":    candle.Volume, | ||||
| 				"volumeCcy": candle.VolumeCcy, | ||||
| 			} | ||||
| 
 | ||||
| 			records = append(records, record) | ||||
| 		} | ||||
| 
 | ||||
| 		// 构造请求体 | ||||
| 		payload := map[string]interface{}{ | ||||
| 			"tag":    tag, | ||||
| 			"record": records, | ||||
| 		} | ||||
| 
 | ||||
| 		jsonData, err := json.Marshal(payload) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to marshal data: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		// 尝试从不同层级加载配置 | ||||
| 		var config *cfg.Config | ||||
| 		configPaths := []string{ | ||||
| 			"config/config.json",       // 当前目录下的config目录 | ||||
| 			"../config/config.json",    // 上一级目录下的config目录 | ||||
| 			"../../config/config.json", // 上两级目录下的config目录 | ||||
| 		} | ||||
| 
 | ||||
| 		for _, path := range configPaths { | ||||
| 			config, err = cfg.LoadConfig(path) | ||||
| 			if err == nil { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to load config after trying paths: %v. Tried paths: %v", err, configPaths) | ||||
| 		} | ||||
| 
 | ||||
| 		// 构造完整URL,添加json参数 | ||||
| 		fullURL := fmt.Sprintf("%s/%s?json", strings.TrimRight(config.ElasticsearchURL, "/"), tag) | ||||
| 
 | ||||
| 		// 输出完整请求URL和请求体到日志 | ||||
| 		fmt.Printf("Sending request to URL: %s\n", fullURL) | ||||
| 		fmt.Printf("Request Body: %s\n", string(jsonData)) | ||||
| 
 | ||||
| 		// 创建带超时的HTTP客户端 | ||||
| 		client := &http.Client{ | ||||
| 			Timeout: 30 * time.Second, | ||||
| 		} | ||||
| 
 | ||||
| 		// 创建请求 | ||||
| 		req, err := http.NewRequest("POST", fullURL, bytes.NewBuffer(jsonData)) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to create request: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		// 设置请求头 | ||||
| 		req.Header.Set("Content-Type", "application/json") | ||||
| 		req.Header.Set("Accept", "application/json") | ||||
| 
 | ||||
| 		// 发送HTTP请求 | ||||
| 		resp, err := client.Do(req) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to send data to Fluentd: %v", err) | ||||
| 		} | ||||
| 		defer resp.Body.Close() | ||||
| 
 | ||||
| 		// 读取响应体 | ||||
| 		body, err := io.ReadAll(resp.Body) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to read response body: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		// 输出完整的响应信息 | ||||
| 		fmt.Printf("HTTP Response Status: %s\n", resp.Status) | ||||
| 		fmt.Printf("HTTP Response Headers: %v\n", resp.Header) | ||||
| 		fmt.Printf("HTTP Response Body: %s\n", string(body)) | ||||
| 
 | ||||
| 		if resp.StatusCode != http.StatusOK { | ||||
| 			return fmt.Errorf("unexpected status code: %d, response: %s", resp.StatusCode, string(body)) | ||||
| 		} | ||||
| 		//回头把response列出来看看,是不是有报错 | ||||
| 
 | ||||
| 		fmt.Printf("Successfully sent %d records to Fluentd\n", len(records)) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // parsePeriod 将周期字符串转换为time.Duration | ||||
| func parsePeriod(period string) (time.Duration, error) { | ||||
| 	switch period { | ||||
| 	case "1m": | ||||
| 		return time.Minute, nil | ||||
| 	case "3m": | ||||
| 		return 3 * time.Minute, nil | ||||
| 	case "5m": | ||||
| 		return 5 * time.Minute, nil | ||||
| 	case "15m": | ||||
| 		return 15 * time.Minute, nil | ||||
| 	case "30m": | ||||
| 		return 30 * time.Minute, nil | ||||
| 	case "1H": | ||||
| 		return time.Hour, nil | ||||
| 	case "2H": | ||||
| 		return 2 * time.Hour, nil | ||||
| 	case "4H": | ||||
| 		return 4 * time.Hour, nil | ||||
| 	case "6H": | ||||
| 		return 6 * time.Hour, nil | ||||
| 	case "12H": | ||||
| 		return 12 * time.Hour, nil | ||||
| 	case "1D": | ||||
| 		return 24 * time.Hour, nil | ||||
| 	case "D": | ||||
| 		return 24 * time.Hour, nil | ||||
| 	case "2D": | ||||
| 		return 48 * time.Hour, nil | ||||
| 	case "5D": | ||||
| 		return 120 * time.Hour, nil | ||||
| 	case "7D": | ||||
| 		return 168 * time.Hour, nil | ||||
| 	default: | ||||
| 		return 0, fmt.Errorf("unsupported period: %s", period) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,11 +1,14 @@ | ||||
| package okx | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/google/go-querystring/query" // 用于将 struct 转为 URL 参数,需要安装:go get github.com/google/go-querystring | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type OkxPublicDataService struct { | ||||
| @ -17,7 +20,9 @@ type OkxPublicDataService struct { | ||||
| func NewOkxPublicDataService() *OkxPublicDataService { | ||||
| 	return &OkxPublicDataService{ | ||||
| 		BaseURL: "https://aws.okx.com/api/v5", | ||||
| 		client:  &http.Client{}, | ||||
| 		client: &http.Client{ | ||||
| 			Timeout: 10 * time.Second, // 设置10秒超时 | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -48,7 +53,25 @@ func (s *OkxPublicDataService) GetInstruments(params InstrumentsRequest) ([]Inst | ||||
| 	} | ||||
| 
 | ||||
| 	if apiResp.Code != "0" { | ||||
| 		return nil, fmt.Errorf("API error: %s", apiResp.Msg) | ||||
| 		return nil, fmt.Errorf("API error: code=%s, msg=%s, request_url=%s", apiResp.Code, apiResp.Msg, u.String()) | ||||
| 	} | ||||
| 
 | ||||
| 	// 进行类型断言并检查长度 | ||||
| 	switch data := apiResp.Data.(type) { | ||||
| 	case []Instrument: | ||||
| 		if len(data) == 0 { | ||||
| 			return nil, fmt.Errorf("no data returned from API") | ||||
| 		} | ||||
| 	case [][]string: | ||||
| 		if len(data) == 0 { | ||||
| 			return nil, fmt.Errorf("no data returned from API") | ||||
| 		} | ||||
| 	case []*Ticker: | ||||
| 		if len(data) == 0 { | ||||
| 			return nil, fmt.Errorf("no data returned from API") | ||||
| 		} | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unexpected data type from API: %T", apiResp.Data) | ||||
| 	} | ||||
| 
 | ||||
| 	return *apiResp.Data.(*[]Instrument), nil | ||||
| @ -68,12 +91,53 @@ func (s *OkxPublicDataService) GetCandles(params CandlesRequest) ([]*Candle, err | ||||
| 	} | ||||
| 	u.RawQuery = q.Encode() | ||||
| 
 | ||||
| 	resp, err := http.Get(u.String()) | ||||
| 	// 添加调试日志 | ||||
| 	fmt.Printf("Making request to: %s\n", u.String()) | ||||
| 
 | ||||
| 	// 创建自定义HTTP请求 | ||||
| 	req, err := http.NewRequest("GET", u.String(), nil) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return nil, fmt.Errorf("failed to create request: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 设置请求头 | ||||
| 	req.Header.Set("Accept", "application/json") | ||||
| 	req.Header.Set("Content-Type", "application/json") | ||||
| 
 | ||||
| 	// 发送请求 | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("request failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	defer resp.Body.Close() | ||||
| 
 | ||||
| 	// 打印详细调试信息 | ||||
| 	fmt.Printf("Request URL: %s\n", u.String()) | ||||
| 	fmt.Printf("Response Status: %s\n", resp.Status) | ||||
| 	fmt.Printf("Response Headers: %v\n", resp.Header) | ||||
| 
 | ||||
| 	// 记录响应状态 | ||||
| 	fmt.Printf("Response Status: %s\n", resp.Status) | ||||
| 
 | ||||
| 	// 读取并记录响应体 | ||||
| 	body, err := io.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to read response body: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 打印响应体长度和内容 | ||||
| 	fmt.Printf("Response Body Length: %d\n", len(body)) | ||||
| 	if len(body) > 0 { | ||||
| 		fmt.Printf("Response Body: %s\n", string(body)) | ||||
| 	} else { | ||||
| 		fmt.Println("Response Body is empty") | ||||
| 	} | ||||
| 	fmt.Printf("Response Body: %s\n", string(body)) | ||||
| 
 | ||||
| 	// 重新设置resp.Body以便后续解码 | ||||
| 	resp.Body = io.NopCloser(bytes.NewBuffer(body)) | ||||
| 
 | ||||
| 	var apiResp struct { | ||||
| 		Code string     `json:"code"` | ||||
| 		Msg  string     `json:"msg"` | ||||
|  | ||||
| @ -8,8 +8,8 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func TestCandleList_ToJson(t *testing.T) { | ||||
| 	startTime := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) | ||||
| 	endTime := time.Date(2022, 12, 31, 0, 0, 0, 0, time.UTC) | ||||
| 	startTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) | ||||
| 	endTime := time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC) | ||||
| 	cl, err := MakeCandleList("BTC-USDT", "1D", startTime, endTime, 50) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("ToJson failed: %v", err) | ||||
| @ -30,8 +30,8 @@ func TestCandleList_ToJson(t *testing.T) { | ||||
| } | ||||
| 
 | ||||
| func TestCandleList_ToCsv(t *testing.T) { | ||||
| 	startTime := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) | ||||
| 	endTime := time.Date(2022, 12, 31, 0, 0, 0, 0, time.UTC) | ||||
| 	startTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) | ||||
| 	endTime := time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC) | ||||
| 	cl, err := MakeCandleList("BTC-USDT", "1D", startTime, endTime, 50) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("ToJson failed: %v", err) | ||||
| @ -50,3 +50,16 @@ func TestCandleList_ToCsv(t *testing.T) { | ||||
| 
 | ||||
| 	t.Logf("CSV output written to %s", tmpFile) | ||||
| } | ||||
| 
 | ||||
| func TestCandleList_ToEs(t *testing.T) { | ||||
| 	startTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) | ||||
| 	endTime := time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC) | ||||
| 	cl, err := MakeCandleList("BTC-USDT", "1D", startTime, endTime, 50) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("ToEs failed: %v", err) | ||||
| 	} | ||||
| 	err = cl.ToEs() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("ToEs failed: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -21,10 +21,23 @@ func TestGetCandles(t *testing.T) { | ||||
| 		t.Fatalf("Expected no error, got %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 添加详细验证 | ||||
| 	if len(candles) == 0 { | ||||
| 		t.Fatal("Expected at least one candle, got none") | ||||
| 	} | ||||
| 
 | ||||
| 	// 检查第一个蜡烛数据 | ||||
| 	firstCandle := candles[0] | ||||
| 	if firstCandle.Timestamp == "" { | ||||
| 		t.Error("Expected non-empty Timestamp") | ||||
| 	} | ||||
| 	if firstCandle.Open == "" || firstCandle.High == "" || firstCandle.Low == "" || firstCandle.Close == "" { | ||||
| 		t.Error("Expected non-empty OHLC values") | ||||
| 	} | ||||
| 	if firstCandle.Volume == "" || firstCandle.VolumeCcy == "" { | ||||
| 		t.Error("Expected non-empty Volume and VolumeCcy") | ||||
| 	} | ||||
| 
 | ||||
| 	// 检查返回数据的结构 | ||||
| 	for _, candle := range candles { | ||||
| 		if candle.Timestamp == "" { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zhangkun9038@dingtalk.com
						zhangkun9038@dingtalk.com