You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

261 lines
7.1 KiB

package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strconv"
"strings"
"time"
)
const (
totalRetryCount = 100000
nacosURL = "http://192.168.31.41:30848/nacos/v1/cs/configs?dataId=wq_account&group=quantify"
)
// NacosConfig 对应 nacos 返回的账号配置
type NacosConfig struct {
UserName string `json:"user_name"`
Password string `json:"password"`
}
// basicAuthTransport 为每个请求自动添加 Basic Auth 头
type basicAuthTransport struct {
username string
password string
base http.RoundTripper
}
func (t *basicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(t.username, t.password)
return t.base.RoundTrip(req)
}
// Login 登录 WorldQuant Brain API,返回带 BasicAuth 的 HTTP Client
func Login() (*http.Client, error) {
// 1. 从 nacos 获取账号密码
resp, err := http.Get(nacosURL)
if err != nil {
log.Printf("获取账号配置失败: %v", err)
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("nacos 返回非 200 状态码: %d", resp.StatusCode)
}
var config NacosConfig
if err := json.NewDecoder(resp.Body).Decode(&config); err != nil {
return nil, fmt.Errorf("解析 nacos 配置失败: %w", err)
}
log.Printf("正在登录账户: %s", config.UserName)
// 2. 创建 HTTP Client,后续所有请求都会使用 BasicAuth
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &basicAuthTransport{
username: config.UserName,
password: config.Password,
base: http.DefaultTransport,
},
}
// 3. 发送登录请求
loginReq, _ := http.NewRequest("POST", "https://api.worldquantbrain.com/authentication", nil)
loginResp, err := client.Do(loginReq)
if err != nil {
return nil, fmt.Errorf("登录请求失败: %w", err)
}
defer loginResp.Body.Close()
log.Printf("登录状态: %d", loginResp.StatusCode)
if loginResp.StatusCode == http.StatusCreated {
log.Println("登录成功!")
return client, nil
}
body, _ := io.ReadAll(loginResp.Body)
return nil, fmt.Errorf("登录失败: %s", string(body))
}
// SubmitAlpha 提交 alpha,自动重试直到成功或达到最大重试次数
// 返回 nil 表示提交成功,否则返回错误
func SubmitAlpha(alphaID string) error {
retryCount := 0
var client *http.Client
for retryCount < totalRetryCount {
// 如果没有有效 client,则重新登录
if client == nil {
var err error
client, err = Login()
if err != nil {
log.Printf("登录失败: %v, 10秒后重试 (重试次数: %d)", err, retryCount)
time.Sleep(10 * time.Second)
retryCount++
continue
}
}
url := fmt.Sprintf("https://api.worldquantbrain.com/alphas/%s/submit", alphaID)
log.Printf("请求 URL: %s", url)
// 发送提交请求
resp, err := client.Post(url, "application/json", nil)
if err != nil {
log.Printf("网络请求异常 (alpha=%s, retry=%d): %v", alphaID, retryCount, err)
retryCount++
time.Sleep(10 * time.Second)
continue
}
// 处理 400 特殊情形:已提交,进入轮询
if resp.StatusCode == http.StatusBadRequest {
bodyBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
bodyStr := string(bodyBytes)
if strings.Contains(bodyStr, "The plain HTTP request was sent to HTTPS port") {
log.Println("Alpha 已提交,正在轮询状态...")
pollInterval := 1.0 // 秒
for {
time.Sleep(time.Duration(pollInterval) * time.Second)
fmt.Print(".")
// 重新 GET 查询状态
pollResp, err := client.Get(url)
if err != nil {
log.Printf("轮询请求失败: %v", err)
break
}
// 检查 Retry-After 头
if retryAfter := pollResp.Header.Get("Retry-After"); retryAfter != "" {
if f, err := strconv.ParseFloat(retryAfter, 64); err == nil && f > 0 {
pollInterval = max(f, 3.0)
} else {
pollInterval = 3.0
}
} else {
pollInterval = 3.0
}
// 当状态不再是 400 且不包含该特定消息时,退出轮询
if pollResp.StatusCode != http.StatusBadRequest {
resp = pollResp
break
}
bodyBytes2, _ := io.ReadAll(pollResp.Body)
pollResp.Body.Close()
if !strings.Contains(string(bodyBytes2), "The plain HTTP request was sent to HTTPS port") {
resp = pollResp
break
}
pollResp.Body.Close()
}
log.Printf("轮询结束,最终状态码: %d", resp.StatusCode)
} else {
// 非特殊 400,按普通错误处理
resp.Body.Close()
}
}
// 确保 resp 不为 nil(如果上面分支没有设置 resp,则跳过本次循环)
if resp == nil {
retryCount++
continue
}
// 根据状态码处理
switch resp.StatusCode {
case http.StatusTooManyRequests: // 429
log.Println("触发限流 (429),休眠 60 秒后重试")
resp.Body.Close()
time.Sleep(60 * time.Second)
retryCount++
continue
case http.StatusUnauthorized: // 401
log.Println("认证失效,重新登录")
resp.Body.Close()
client = nil
retryCount++
continue
case http.StatusNotFound: // 404
log.Printf("Alpha %s 不存在或超时,重试 (%d/%d)", alphaID, retryCount+1, totalRetryCount)
resp.Body.Close()
retryCount++
continue
case http.StatusForbidden: // 403
log.Printf("%s 提交失败 (403)", alphaID)
var failChecks []map[string]interface{}
var bodyMap map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&bodyMap); err == nil {
if isObj, ok := bodyMap["is"].(map[string]interface{}); ok {
if checks, ok := isObj["checks"].([]interface{}); ok {
for _, c := range checks {
if ch, ok := c.(map[string]interface{}); ok {
if result, ok := ch["result"]; ok && result == "FAIL" {
failChecks = append(failChecks, ch)
}
}
}
}
}
}
resp.Body.Close()
log.Printf("失败的检查项: %v", failChecks)
// 如果因为提交次数超限而失败,放弃重试
for _, ch := range failChecks {
if name, ok := ch["name"]; ok {
if name == "REGULAR_SUBMISSION" || name == "SUPER_SUBMISSION" {
return fmt.Errorf("提交次数超过限制: %v", failChecks)
}
}
}
return fmt.Errorf("提交失败,HTTP 403")
case http.StatusOK: // 200
log.Printf("%s 提交成功", alphaID)
resp.Body.Close()
return nil
default:
// 处理 5xx 错误
if resp.StatusCode >= 500 && resp.StatusCode < 600 {
log.Printf("服务器错误 %d,5 秒后重试", resp.StatusCode)
resp.Body.Close()
time.Sleep(5 * time.Second)
retryCount++
continue
}
// 其他非 2xx 状态码视为失败
bodyBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return fmt.Errorf("未处理的响应状态码 %d: %s", resp.StatusCode, string(bodyBytes))
}
}
return fmt.Errorf("达到最大重试次数 %d,提交失败", totalRetryCount)
}
// max 返回两个 float64 中的较大值
func max(a, b float64) float64 {
if a > b {
return a
}
return b
}
func main() {
// 使用示例
alphaID := "your_alpha_id_here"
if err := SubmitAlpha(alphaID); err != nil {
log.Fatalf("提交失败: %v", err)
}
log.Println("提交完成")
}