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
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("提交完成")
|
|
}
|
|
|