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.
 
 
 
 
 
 

477 lines
11 KiB

package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"time"
"github.com/valyala/fasthttp"
)
const (
baseURL = "https://api.worldquantbrain.com"
zeroStreakThreshold = 5 * 252
requiredDays = 2920
)
type Client struct {
client *fasthttp.Client
username string
password string
}
type AlphaRecord struct {
ID string `json:"id"`
Name string `json:"name"`
DateCreated string `json:"dateCreated"`
Sharpe float64 `json:"sharpe"`
Fitness float64 `json:"fitness"`
Turnover float64 `json:"turnover"`
Margin float64 `json:"margin"`
LongCount float64 `json:"longCount"`
ShortCount float64 `json:"shortCount"`
Decay int `json:"decay"`
Code string `json:"code"`
}
type AlphaResponse struct {
Results []struct {
ID string `json:"id"`
Name string `json:"name"`
DateCreated string `json:"dateCreated"`
Is map[string]interface{} `json:"is"`
Settings struct {
Decay int `json:"decay"`
} `json:"settings"`
Regular struct {
Code string `json:"code"`
} `json:"regular"`
} `json:"results"`
}
type PnlResponse struct {
Records [][]interface{} `json:"records"`
}
func NewClient(username, password string) *Client {
return &Client{
client: &fasthttp.Client{},
username: username,
password: password,
}
}
func (c *Client) getAuthHeader() string {
auth := base64.StdEncoding.EncodeToString([]byte(c.username + ":" + c.password))
return "Basic " + auth
}
func (c *Client) Login() error {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(resp)
req.SetRequestURI(baseURL + "/authentication")
req.Header.SetMethod("POST")
req.Header.Set("Authorization", c.getAuthHeader())
err := c.client.Do(req, resp)
if err != nil {
return err
}
fmt.Println(string(resp.Body()))
return nil
}
func (c *Client) WaitGet(url string, maxRetries int) (*fasthttp.Response, error) {
retries := 0
for retries < maxRetries {
for {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
req.SetRequestURI(url)
req.Header.SetMethod("GET")
req.Header.Set("Authorization", c.getAuthHeader())
err := c.client.Do(req, resp)
if err != nil {
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
return nil, err
}
retryAfter := resp.Header.Peek("Retry-After")
if len(retryAfter) == 0 {
fasthttp.ReleaseRequest(req)
if resp.StatusCode() < 400 {
return resp, nil
}
fasthttp.ReleaseResponse(resp)
break
}
sleepSec, _ := strconv.ParseFloat(string(retryAfter), 64)
time.Sleep(time.Duration(sleepSec) * time.Second)
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}
time.Sleep(time.Duration(math.Pow(2, float64(retries))) * time.Second)
retries++
}
return nil, fmt.Errorf("max retries exceeded")
}
func (c *Client) Get(url string) (*fasthttp.Response, error) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
req.SetRequestURI(url)
req.Header.SetMethod("GET")
req.Header.Set("Authorization", c.getAuthHeader())
err := c.client.Do(req, resp)
if err != nil {
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
return nil, err
}
return resp, nil
}
func (c *Client) Patch(url string, data map[string]interface{}) (*fasthttp.Response, error) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseRequest(req)
req.SetRequestURI(url)
req.Header.SetMethod("PATCH")
req.Header.Set("Authorization", c.getAuthHeader())
req.Header.SetContentType("application/json")
jsonData, _ := json.Marshal(data)
req.SetBody(jsonData)
err := c.client.Do(req, resp)
if err != nil {
fasthttp.ReleaseResponse(resp)
return nil, err
}
return resp, nil
}
func GetAlphas(c *Client, startDate, endDate string, sharpeTh, fitnessTh float64, region string, alphaNum int, usage string) ([][]interface{}, *Client, error) {
output := make([][]interface{}, 0)
count := 0
for i := 0; i < alphaNum; i += 100 {
fmt.Println(i)
urlE := fmt.Sprintf("%s/users/self/alphas?limit=100&offset=%d&status=UNSUBMITTED%%1FIS_FAIL&dateCreated%%3E=2025-%sT00:00:00-04:00&dateCreated%%3C2025-%sT00:00:00-04:00&is.fitness%%3E%f&is.sharpe%%3E%f&settings.region=%s&order=-is.sharpe&hidden=false&type!=SUPER",
baseURL, i, startDate, endDate, fitnessTh, sharpeTh, region)
urlC := fmt.Sprintf("%s/users/self/alphas?limit=100&offset=%d&status=UNSUBMITTED%%1FIS_FAIL&dateCreated%%3E=2025-%sT00:00:00-04:00&dateCreated%%3C2025-%sT00:00:00-04:00&is.fitness%%3C-%f&is.sharpe%%3C-%f&settings.region=%s&order=is.sharpe&hidden=false&type!=SUPER",
baseURL, i, startDate, endDate, fitnessTh, sharpeTh, region)
urls := []string{urlE}
if usage != "submit" {
urls = append(urls, urlC)
}
for _, url := range urls {
resp, err := c.Get(url)
if err != nil {
fmt.Printf("%d finished re-login\n", i)
c.Login()
continue
}
var alphaResp AlphaResponse
if err := json.Unmarshal(resp.Body(), &alphaResp); err != nil {
fasthttp.ReleaseResponse(resp)
fmt.Printf("%d finished re-login\n", i)
c.Login()
continue
}
fasthttp.ReleaseResponse(resp)
for _, item := range alphaResp.Results {
alphaID := item.ID
name := item.Name
dateCreated := item.DateCreated
sharpe := getFloat(item.Is, "sharpe")
fitness := getFloat(item.Is, "fitness")
turnover := getFloat(item.Is, "turnover")
margin := getFloat(item.Is, "margin")
longCount := getFloat(item.Is, "longCount")
shortCount := getFloat(item.Is, "shortCount")
decay := item.Settings.Decay
exp := item.Regular.Code
count++
if (longCount + shortCount) > 100 {
if sharpe < -sharpeTh {
exp = "-" + exp
}
rec := []interface{}{alphaID, exp, sharpe, turnover, fitness, margin, dateCreated, decay}
fmt.Println(rec)
if turnover > 0.7 {
rec = append(rec, float64(decay)*4)
} else if turnover > 0.6 {
rec = append(rec, float64(decay)*3+3)
} else if turnover > 0.5 {
rec = append(rec, float64(decay)*3)
} else if turnover > 0.4 {
rec = append(rec, float64(decay)*2)
} else if turnover > 0.35 {
rec = append(rec, float64(decay)+4)
} else if turnover > 0.3 {
rec = append(rec, float64(decay)+2)
}
output = append(output, rec)
}
}
}
}
fmt.Printf("count: %d\n", count)
return output, c, nil
}
func getFloat(m map[string]interface{}, key string) float64 {
if v, ok := m[key]; ok {
switch val := v.(type) {
case float64:
return val
case int:
return float64(val)
}
}
return 0
}
func CheckConsecutiveNonZeroValues(alphaID string, data [][]interface{}, requiredStreak int) bool {
if len(data) < requiredStreak {
return true
}
checkColumn := func(columnData []float64) bool {
if len(columnData) < requiredStreak {
return true
}
currentStreakCount := 0
var currentStreakValue interface{}
for _, value := range columnData {
if value != 0 {
if currentStreakValue != nil && value == currentStreakValue {
currentStreakCount++
} else {
currentStreakValue = value
currentStreakCount = 1
}
} else {
currentStreakValue = nil
currentStreakCount = 0
}
if currentStreakCount >= requiredStreak {
return false
}
}
return true
}
var column1Values, column2Values []float64
for _, row := range data {
if len(row) >= 3 {
if v, ok := row[1].(float64); ok {
column1Values = append(column1Values, v)
}
if v, ok := row[2].(float64); ok {
column2Values = append(column2Values, v)
}
}
}
if len(column1Values) > 0 && len(column2Values) > 0 {
isCol1AllZeros := allZeros(column1Values)
isCol2AllZeros := allZeros(column2Values)
if isCol1AllZeros || isCol2AllZeros {
fmt.Println(alphaID, "不合法")
return false
}
}
if !checkColumn(column1Values) {
fmt.Println(alphaID, "不合法")
return false
}
if !checkColumn(column2Values) {
fmt.Println(alphaID, "不合法")
return false
}
return true
}
func allZeros(arr []float64) bool {
for _, v := range arr {
if v != 0 {
return false
}
}
return true
}
func GetAlphaPnlLegal(c *Client, alphaID string) bool {
notLegalID := make([]string, 0)
url := baseURL + "/alphas/" + alphaID + "/recordsets/pnl"
resp, err := c.WaitGet(url, 10)
if err != nil {
return false
}
defer fasthttp.ReleaseResponse(resp)
var pnlResp PnlResponse
if err := json.Unmarshal(resp.Body(), &pnlResp); err != nil {
return false
}
records := pnlResp.Records
if len(records) == 0 {
return false
}
var dateList []time.Time
for _, record := range records {
if len(record) == 0 {
continue
}
dateStr, ok := record[0].(string)
if !ok {
return false
}
dateObj, err := time.Parse("2006-01-02", dateStr)
if err != nil {
return false
}
dateList = append(dateList, dateObj)
}
if len(dateList) == 0 {
return false
}
minDate := dateList[0]
maxDate := dateList[0]
for _, d := range dateList {
if d.Before(minDate) {
minDate = d
}
if d.After(maxDate) {
maxDate = d
}
}
totalDays := int(maxDate.Sub(minDate).Hours() / 24)
if totalDays < requiredDays {
return false
}
col1Zeros := make([]bool, 0)
for _, record := range records {
if len(record) >= 2 {
if v, ok := record[1].(float64); ok {
col1Zeros = append(col1Zeros, v == 0)
}
}
}
col1MaxZeroStreak := maxConsecutiveZeros(col1Zeros)
if col1MaxZeroStreak >= zeroStreakThreshold {
fmt.Printf("%s 不合法:存在连续%d年零值\n", alphaID, zeroStreakThreshold/252)
notLegalID = append(notLegalID, alphaID)
return false
}
if !CheckConsecutiveNonZeroValues(alphaID, records, 200) {
return false
}
_ = notLegalID
return true
}
func maxConsecutiveZeros(arr []bool) int {
maxStreak := 0
currentStreak := 0
for _, val := range arr {
if val {
currentStreak++
if currentStreak > maxStreak {
maxStreak = currentStreak
}
} else {
currentStreak = 0
}
}
return maxStreak
}
func Mute(c *Client, alphaID string) {
url := baseURL + "/alphas/" + alphaID
data := map[string]interface{}{
"hidden": true,
}
c.Patch(url, data)
}
func main() {
client := NewClient("", "")
client.Login()
foTracker, c, err := GetAlphas(client, "12-01", "12-31", 1, 0.5, "USA", 1000, "submit")
if err != nil {
fmt.Println("Error:", err)
return
}
fNum := len(foTracker)
fmt.Printf("%d 个alpha 进行pnl合法检测,请耐心等待\n", fNum)
fmt.Println(len(foTracker))
count := 0
for i := len(foTracker) - 1; i >= 0; i-- {
if count%25 == 0 {
fmt.Printf("=========== %d ===========\n", count)
}
count++
alphaID, ok := foTracker[i][0].(string)
if !ok {
continue
}
if !GetAlphaPnlLegal(c, alphaID) {
fmt.Println(alphaID, "已经隐藏")
Mute(c, alphaID)
}
}
}