增加 alpha 检测工具
This commit is contained in:
@@ -0,0 +1,477 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user