Jack há 1 mês atrás
pai
commit
e4ae219ed3

+ 56 - 0
wqb-demo/alphas.json

@@ -0,0 +1,56 @@
+[
+  {
+    "name": "负债资产比因子",
+    "expression": "liabilities/assets",
+    "settings": {
+      "instrumentType": "EQUITY",
+      "region": "USA",
+      "universe": "TOP3000",
+      "delay": 1,
+      "decay": 0,
+      "neutralization": "INDUSTRY",
+      "truncation": 0.08,
+      "pasteurization": "ON",
+      "unitHandling": "VERIFY",
+      "nanHandling": "OFF",
+      "language": "FASTEXPR",
+      "visualization": false
+    }
+  },
+  {
+    "name": "营收权益比因子",
+    "expression": "revenue/equity",
+    "settings": {
+      "instrumentType": "EQUITY",
+      "region": "USA",
+      "universe": "TOP3000",
+      "delay": 1,
+      "decay": 0,
+      "neutralization": "INDUSTRY",
+      "truncation": 0.1,
+      "pasteurization": "ON",
+      "unitHandling": "VERIFY",
+      "nanHandling": "OFF",
+      "language": "FASTEXPR",
+      "visualization": false
+    }
+  },
+  {
+    "name": "流动比率因子",
+    "expression": "current_assets/current_liabilities",
+    "settings": {
+      "instrumentType": "EQUITY",
+      "region": "USA",
+      "universe": "TOP2000",
+      "delay": 1,
+      "decay": 0,
+      "neutralization": "INDUSTRY",
+      "truncation": 0.12,
+      "pasteurization": "ON",
+      "unitHandling": "VERIFY",
+      "nanHandling": "OFF",
+      "language": "FASTEXPR",
+      "visualization": false
+    }
+  }
+]

+ 1 - 0
wqb-demo/brain_credentials.txt

@@ -0,0 +1 @@
+['jack0210_@hotmail.com', '!QAZ2wsx+0913']

+ 7 - 0
wqb-demo/go.mod

@@ -0,0 +1,7 @@
+module worldquant-brain-client
+
+go 1.24.0
+
+toolchain go1.24.10
+
+require golang.org/x/sync v0.18.0

+ 2 - 0
wqb-demo/go.sum

@@ -0,0 +1,2 @@
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=

+ 422 - 0
wqb-demo/main.go

@@ -0,0 +1,422 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"golang.org/x/sync/semaphore"
+)
+
+// AlphaConfig Alpha因子配置
+type AlphaConfig struct {
+	Name       string                 `json:"name"`
+	Expression string                 `json:"expression"`
+	Settings   map[string]interface{} `json:"settings,omitempty"`
+}
+
+// SimulationResult 模拟结果
+type SimulationResult struct {
+	Name       string      `json:"name"`
+	Expression string      `json:"expression"`
+	AlphaID    string      `json:"alpha_id,omitempty"`
+	Success    bool        `json:"success"`
+	Error      string      `json:"error,omitempty"`
+	ResultData interface{} `json:"result_data,omitempty"`
+}
+
+// WorldQuantBrainAPI WorldQuant Brain API客户端
+type WorldQuantBrainAPI struct {
+	BaseURL         string
+	CredentialsFile string
+	HTTPClient      *http.Client
+	Username        string
+	Password        string
+}
+
+// NewWorldQuantBrainAPI 创建新的API客户端
+func NewWorldQuantBrainAPI(credentialsFile string) *WorldQuantBrainAPI {
+	if credentialsFile == "" {
+		credentialsFile = "brain_credentials.txt"
+	}
+
+	// 扩展文件路径
+	fullPath := expandUser(credentialsFile)
+
+	return &WorldQuantBrainAPI{
+		BaseURL:         "https://api.worldquantbrain.com",
+		CredentialsFile: fullPath,
+		HTTPClient:      &http.Client{Timeout: 30 * time.Second},
+	}
+}
+
+// expandUser 扩展用户主目录路径
+func expandUser(path string) string {
+	if strings.HasPrefix(path, "~/") {
+		home, err := os.UserHomeDir()
+		if err != nil {
+			return path
+		}
+		return filepath.Join(home, path[2:])
+	}
+	return path
+}
+
+// Login 登录认证
+func (wq *WorldQuantBrainAPI) Login() error {
+	// 读取凭证文件
+	data, err := os.ReadFile(wq.CredentialsFile)
+	if err != nil {
+		return fmt.Errorf("读取凭证文件失败: %v", err)
+	}
+
+	content := strings.TrimSpace(string(data))
+
+	var credentials []string
+
+	// 方法1: 尝试标准JSON解析
+	if err := json.Unmarshal([]byte(content), &credentials); err == nil && len(credentials) >= 2 {
+		// JSON解析成功
+	} else {
+		// 方法2: 尝试处理单引号JSON
+		singleQuoteContent := strings.ReplaceAll(content, "'", "\"")
+		if err := json.Unmarshal([]byte(singleQuoteContent), &credentials); err == nil && len(credentials) >= 2 {
+			// 单引号JSON解析成功
+		} else {
+			// 方法3: 尝试其他文本格式
+			credentials = wq.parseTextCredentials(content)
+		}
+	}
+
+	if len(credentials) < 2 {
+		return fmt.Errorf("无法解析凭证文件,需要用户名和密码。当前内容: %s", content)
+	}
+
+	wq.Username = strings.TrimSpace(credentials[0])
+	wq.Password = strings.TrimSpace(credentials[1])
+
+	// 创建认证请求
+	req, err := http.NewRequest("POST", wq.BaseURL+"/authentication", nil)
+	if err != nil {
+		return fmt.Errorf("创建请求失败: %v", err)
+	}
+	req.SetBasicAuth(wq.Username, wq.Password)
+
+	// 发送认证请求
+	resp, err := wq.HTTPClient.Do(req)
+	if err != nil {
+		return fmt.Errorf("认证请求失败: %v", err)
+	}
+	defer resp.Body.Close()
+
+	fmt.Printf("认证状态: %d\n", resp.StatusCode)
+
+	// 修复:检查 200 和 201 状态码都表示成功
+	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
+		body, _ := io.ReadAll(resp.Body)
+		return fmt.Errorf("登录失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
+	}
+
+	// 读取成功响应
+	body, _ := io.ReadAll(resp.Body)
+	fmt.Printf("登录成功响应: %s\n", string(body))
+
+	fmt.Println("登录成功!")
+	return nil
+}
+
+func (wq *WorldQuantBrainAPI) parseTextCredentials(content string) []string {
+	panic("unimplemented")
+}
+
+// SimulateAlpha 模拟单个Alpha因子
+func (wq *WorldQuantBrainAPI) SimulateAlpha(config AlphaConfig) SimulationResult {
+	if wq.HTTPClient == nil {
+		return SimulationResult{
+			Name:    config.Name,
+			Success: false,
+			Error:   "请先调用Login方法登录",
+		}
+	}
+
+	name := config.Name
+	if name == "" {
+		name = "未命名Alpha"
+	}
+
+	fmt.Printf("\n🚀 开始模拟: %s\n", name)
+	fmt.Printf("📝 表达式: %s\n", config.Expression)
+
+	// 构建模拟数据
+	simulationData := map[string]interface{}{
+		"type":     "REGULAR",
+		"settings": config.Settings,
+		"regular":  config.Expression,
+	}
+
+	jsonData, err := json.Marshal(simulationData)
+	if err != nil {
+		return SimulationResult{
+			Name:    name,
+			Success: false,
+			Error:   fmt.Sprintf("序列化模拟数据失败: %v", err),
+		}
+	}
+
+	// 发送模拟请求
+	req, err := http.NewRequest("POST", wq.BaseURL+"/simulations", strings.NewReader(string(jsonData)))
+	if err != nil {
+		return SimulationResult{
+			Name:    name,
+			Success: false,
+			Error:   fmt.Sprintf("创建请求失败: %v", err),
+		}
+	}
+	req.SetBasicAuth(wq.Username, wq.Password)
+	req.Header.Set("Content-Type", "application/json")
+
+	resp, err := wq.HTTPClient.Do(req)
+	if err != nil {
+		return SimulationResult{
+			Name:    name,
+			Success: false,
+			Error:   fmt.Sprintf("模拟请求失败: %v", err),
+		}
+	}
+	defer resp.Body.Close()
+
+	fmt.Printf("📤 模拟提交状态: %d\n", resp.StatusCode)
+
+	if resp.StatusCode != http.StatusOK {
+		// 尝试读取错误响应体
+		body, _ := io.ReadAll(resp.Body)
+		return SimulationResult{
+			Name:    name,
+			Success: false,
+			Error:   fmt.Sprintf("提交失败: %d, 响应: %s", resp.StatusCode, string(body)),
+		}
+	}
+
+	// 获取进度URL
+	progressURL := resp.Header.Get("Location")
+	if progressURL == "" {
+		return SimulationResult{
+			Name:    name,
+			Success: false,
+			Error:   "未找到进度URL",
+		}
+	}
+
+	fmt.Printf("⏳ %s 进度URL获取成功\n", name)
+
+	// 轮询进度
+	var resultData map[string]interface{}
+	for {
+		progressReq, err := http.NewRequest("GET", progressURL, nil)
+		if err != nil {
+			return SimulationResult{
+				Name:    name,
+				Success: false,
+				Error:   fmt.Sprintf("创建进度请求失败: %v", err),
+			}
+		}
+		progressReq.SetBasicAuth(wq.Username, wq.Password)
+
+		progressResp, err := wq.HTTPClient.Do(progressReq)
+		if err != nil {
+			return SimulationResult{
+				Name:    name,
+				Success: false,
+				Error:   fmt.Sprintf("进度请求失败: %v", err),
+			}
+		}
+
+		retryAfter := progressResp.Header.Get("Retry-After")
+		if retryAfter == "" || retryAfter == "0" {
+			// 模拟完成
+			if err := json.NewDecoder(progressResp.Body).Decode(&resultData); err != nil {
+				progressResp.Body.Close()
+				return SimulationResult{
+					Name:    name,
+					Success: false,
+					Error:   fmt.Sprintf("解析结果失败: %v", err),
+				}
+			}
+			progressResp.Body.Close()
+			break
+		}
+
+		progressResp.Body.Close()
+
+		// 等待指定时间
+		waitSeconds, _ := strconv.ParseFloat(retryAfter, 64)
+		fmt.Printf("⏰ %s 等待 %.1f 秒...\n", name, waitSeconds)
+		time.Sleep(time.Duration(waitSeconds * float64(time.Second)))
+	}
+
+	// 提取Alpha ID
+	alphaID, ok := resultData["alpha"].(string)
+	if !ok {
+		return SimulationResult{
+			Name:    name,
+			Success: false,
+			Error:   "未找到Alpha ID",
+		}
+	}
+
+	fmt.Printf("✅ %s 模拟完成! Alpha ID: %s\n", name, alphaID)
+
+	return SimulationResult{
+		Name:       name,
+		Expression: config.Expression,
+		AlphaID:    alphaID,
+		Success:    true,
+		ResultData: resultData,
+	}
+}
+
+// BatchSimulateAlphas 批量测试多个Alpha因子
+func (wq *WorldQuantBrainAPI) BatchSimulateAlphas(configFile string, maxWorkers int) []SimulationResult {
+	// 读取Alpha配置
+	configs, err := wq.loadAlphaConfigs(configFile)
+	if err != nil {
+		log.Printf("❌ 读取配置文件失败: %v", err)
+		return nil
+	}
+
+	fmt.Printf("📁 读取到 %d 个Alpha配置\n", len(configs))
+
+	// 登录
+	if err := wq.Login(); err != nil {
+		log.Printf("❌ 登录失败: %v", err)
+		return nil
+	}
+
+	var results []SimulationResult
+	var mu sync.Mutex
+	var wg sync.WaitGroup
+
+	// 使用信号量控制并发数量
+	sem := semaphore.NewWeighted(int64(maxWorkers))
+	ctx := context.Background()
+
+	for _, config := range configs {
+		wg.Add(1)
+
+		go func(c AlphaConfig) {
+			defer wg.Done()
+
+			// 获取信号量
+			if err := sem.Acquire(ctx, 1); err != nil {
+				log.Printf("❌ 获取信号量失败: %v", err)
+				return
+			}
+			defer sem.Release(1)
+
+			// 执行模拟
+			result := wq.SimulateAlpha(c)
+
+			// 安全地添加结果
+			mu.Lock()
+			results = append(results, result)
+			mu.Unlock()
+		}(config)
+	}
+
+	wg.Wait()
+
+	// 打印汇总结果
+	wq.printSummary(results)
+	return results
+}
+
+// loadAlphaConfigs 加载Alpha配置
+func (wq *WorldQuantBrainAPI) loadAlphaConfigs(configFile string) ([]AlphaConfig, error) {
+	data, err := os.ReadFile(configFile)
+	if err != nil {
+		return nil, fmt.Errorf("读取配置文件 %s 失败: %v", configFile, err)
+	}
+
+	var configs []AlphaConfig
+	if err := json.Unmarshal(data, &configs); err != nil {
+		return nil, fmt.Errorf("解析配置文件 %s 失败: %v", configFile, err)
+	}
+
+	return configs, nil
+}
+
+// printSummary 打印测试结果汇总
+func (wq *WorldQuantBrainAPI) printSummary(results []SimulationResult) {
+	fmt.Println("\n" + strings.Repeat("=", 50))
+	fmt.Println("📊 Alpha测试结果汇总")
+	fmt.Println(strings.Repeat("=", 50))
+
+	successCount := 0
+	for _, result := range results {
+		if result.Success {
+			successCount++
+		}
+	}
+	failedCount := len(results) - successCount
+
+	fmt.Printf("✅ 成功: %d 个\n", successCount)
+	fmt.Printf("❌ 失败: %d 个\n", failedCount)
+
+	fmt.Println("\n成功详情:")
+	for _, result := range results {
+		if result.Success {
+			fmt.Printf("  ✓ %s: %s\n", result.Name, result.AlphaID)
+		}
+	}
+
+	if failedCount > 0 {
+		fmt.Println("\n失败详情:")
+		for _, result := range results {
+			if !result.Success {
+				fmt.Printf("  ✗ %s: %s\n", result.Name, result.Error)
+			}
+		}
+	}
+}
+
+// Close 关闭客户端
+func (wq *WorldQuantBrainAPI) Close() {
+	// 如果有需要关闭的资源,可以在这里处理
+}
+
+// 保存结果到文件
+func saveResultsToFile(results []SimulationResult, filename string) error {
+	data, err := json.MarshalIndent(results, "", "  ")
+	if err != nil {
+		return err
+	}
+	return os.WriteFile(filename, data, 0644)
+}
+
+func main() {
+	// 创建API实例
+	wqAPI := NewWorldQuantBrainAPI("")
+
+	// 确保在程序结束时关闭连接
+	defer wqAPI.Close()
+
+	fmt.Println("🎯 开始批量测试Alpha因子...")
+	startTime := time.Now()
+
+	// 批量测试所有Alpha
+	results := wqAPI.BatchSimulateAlphas("alphas.json", 2) // 并发数量,根据API限制调整
+
+	duration := time.Since(startTime)
+	fmt.Printf("\n⏱️  总耗时: %.2f 秒\n", duration.Seconds())
+
+	fmt.Println(results)
+}

+ 231 - 0
wqb-demo/main.py

@@ -0,0 +1,231 @@
+import httpx
+import json
+import asyncio
+import threading
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from os.path import expanduser, join, dirname, abspath
+from httpx import BasicAuth
+from time import sleep
+from datetime import datetime
+
+
+class WorldQuantBrainAPI:
+    def __init__(self, credentials_file='brain_credentials_copy.txt'):
+        """初始化API客户端"""
+        self.credentials_file = expanduser(credentials_file)
+        self.client = None
+        self.brain_api_url = 'https://api.worldquantbrain.com'
+
+    def login(self):
+        """登录认证"""
+        # Load credentials
+        with open(self.credentials_file) as f:
+            credentials = json.load(f)
+
+        # Extract username and password from the list
+        username, password = credentials
+
+        # Create a client with basic authentication
+        self.client = httpx.Client(auth=BasicAuth(username, password))
+
+        # Send authentication request
+        response = self.client.post(f'{self.brain_api_url}/authentication')
+        print(f"认证状态: {response.status_code}")
+
+        if response.status_code == 200:
+            print("登录成功!")
+            return True
+        else:
+            print(f"登录失败: {response.json()}")
+            return False
+
+    def simulate_alpha(self, alpha_config):
+        """模拟单个Alpha因子"""
+        if self.client is None:
+            raise Exception("请先调用 login() 方法登录")
+
+        name = alpha_config.get('name', '未命名Alpha')
+        expression = alpha_config['expression']
+        settings = alpha_config.get('settings', {})
+
+        print(f"\n🚀 开始模拟: {name}")
+        print(f"📝 表达式: {expression}")
+
+        simulation_data = {
+            'type': 'REGULAR',
+            'settings': settings,
+            'regular': expression
+        }
+
+        try:
+            # 发送模拟数据
+            sim_resp = self.client.post(
+                f'{self.brain_api_url}/simulations',
+                json=simulation_data,
+            )
+            print(f"📤 模拟提交状态: {sim_resp.status_code}")
+
+            if sim_resp.status_code != 200:
+                print(f"❌ {name} 提交失败")
+                return {'name': name, 'success': False, 'error': f'提交失败: {sim_resp.status_code}'}
+
+            # 获取进度URL并轮询结果
+            sim_progress_url = sim_resp.headers['location']
+            print(f"⏳ {name} 进度URL获取成功")
+
+            while True:
+                sim_progress_resp = self.client.get(sim_progress_url)
+                retry_after_sec = float(sim_progress_resp.headers.get("Retry-After", 0))
+
+                if retry_after_sec == 0:  # simulation done!
+                    break
+                print(f"⏰ {name} 等待 {retry_after_sec} 秒...")
+                sleep(retry_after_sec)
+
+            # 获取最终的alpha ID
+            result_data = sim_progress_resp.json()
+            alpha_id = result_data["alpha"]
+            print(f"✅ {name} 模拟完成! Alpha ID: {alpha_id}")
+
+            return {
+                'name': name,
+                'expression': expression,
+                'alpha_id': alpha_id,
+                'success': True,
+                'result_data': result_data
+            }
+
+        except Exception as e:
+            print(f"❌ {name} 模拟过程中出错: {str(e)}")
+            return {'name': name, 'success': False, 'error': str(e)}
+
+    def batch_simulate_alphas(self, alphas_config_file='alphas.json', max_workers=3):
+        """批量测试多个Alpha因子(使用线程池)"""
+        # 读取Alpha配置
+        try:
+            with open(alphas_config_file, 'r', encoding='utf-8') as f:
+                alpha_configs = json.load(f)
+        except FileNotFoundError:
+            print(f"❌ 配置文件 {alphas_config_file} 不存在")
+            return []
+        except json.JSONDecodeError:
+            print(f"❌ 配置文件 {alphas_config_file} JSON格式错误")
+            return []
+
+        print(f"📁 读取到 {len(alpha_configs)} 个Alpha配置")
+
+        # 登录
+        if not self.login():
+            return []
+
+        results = []
+
+        # 使用线程池并发执行
+        with ThreadPoolExecutor(max_workers=max_workers) as executor:
+            # 提交所有任务
+            future_to_alpha = {
+                executor.submit(self.simulate_alpha, config): config
+                for config in alpha_configs
+            }
+
+            # 收集结果
+            for future in as_completed(future_to_alpha):
+                config = future_to_alpha[future]
+                try:
+                    result = future.result()
+                    results.append(result)
+                except Exception as e:
+                    print(f"❌ {config.get('name', '未知Alpha')} 执行失败: {str(e)}")
+                    results.append({
+                        'name': config.get('name', '未知Alpha'),
+                        'success': False,
+                        'error': str(e)
+                    })
+
+        # 打印汇总结果
+        self._print_summary(results)
+        return results
+
+    async def batch_simulate_alphas_async(self, alphas_config_file='alphas.json'):
+        """异步批量测试多个Alpha因子"""
+        # 注意:httpx 的异步客户端需要额外处理
+        # 这里先提供同步版本,异步版本需要重写
+
+        print("⚠️  异步版本暂未实现,使用同步版本")
+        return self.batch_simulate_alphas(alphas_config_file)
+
+    def _print_summary(self, results):
+        """打印测试结果汇总"""
+        print("\n" + "=" * 50)
+        print("📊 Alpha测试结果汇总")
+        print("=" * 50)
+
+        success_count = sum(1 for r in results if r.get('success', False))
+        failed_count = len(results) - success_count
+
+        print(f"✅ 成功: {success_count} 个")
+        print(f"❌ 失败: {failed_count} 个")
+
+        print("\n成功详情:")
+        for result in results:
+            if result.get('success'):
+                print(f"  ✓ {result['name']}: {result['alpha_id']}")
+
+        if failed_count > 0:
+            print("\n失败详情:")
+            for result in results:
+                if not result.get('success'):
+                    print(f"  ✗ {result['name']}: {result.get('error', '未知错误')}")
+
+    def submit_alpha(self, alpha_id):
+        if self.client is None:
+            raise Exception("请先调用 login() 方法登录")
+
+        result = self.client.post(f'{self.brain_api_url}/alphas/{alpha_id}/submit')
+
+        while True:
+            if "retry-after" in result.headers:
+                sleep_time = float(result.headers["retry-after"])
+                print(f"提交等待 {sleep_time} 秒...")
+                sleep(sleep_time)
+                result = self.client.get(f'{self.brain_api_url}/alphas/{alpha_id}/submit')
+                print('检查提交状态...')
+            else:
+                break
+
+        success = result.status_code == 200
+        if success:
+            print(f"Alpha {alpha_id} 提交成功!")
+        else:
+            print(f"Alpha {alpha_id} 提交失败,状态码: {result.status_code}")
+
+        return success
+
+    def close(self):
+        """关闭客户端连接"""
+        if self.client:
+            self.client.close()
+            self.client = None
+
+
+# 使用示例
+if __name__ == "__main__":
+    # 创建API实例
+    wq_api = WorldQuantBrainAPI()
+
+    try:
+        # 批量测试所有Alpha
+        print("🎯 开始批量测试Alpha因子...")
+        start_time = datetime.now()
+
+        results = wq_api.batch_simulate_alphas(
+            alphas_config_file='alphas.json',
+            max_workers=2  # 并发数量,根据API限制调整
+        )
+
+        end_time = datetime.now()
+        print(f"\n⏱️  总耗时: {(end_time - start_time).total_seconds():.2f} 秒")
+
+    finally:
+        # 确保连接关闭
+        wq_api.close()

+ 100 - 0
wqb-demo/main_simple.py

@@ -0,0 +1,100 @@
+import httpx
+import json
+from httpx import BasicAuth
+from time import sleep
+
+class WorldQuantBrainAPI:
+    def __init__(self, credentials_file='brain_credentials.txt'):
+        self.credentials_file = credentials_file
+        self.client = None
+        self.brain_api_url = 'https://api.worldquantbrain.com'
+    
+    def load_credentials(self):
+        """读取本地账号密码"""
+        with open(self.credentials_file) as f:
+            credentials = eval(f.read())
+        return credentials[0], credentials[1]
+    
+    def login(self):
+        """登录认证"""
+        username, password = self.load_credentials()
+        self.client = httpx.Client(auth=BasicAuth(username, password))
+        
+        response = self.client.post(f'{self.brain_api_url}/authentication')
+        print(f"登录状态: {response.status_code}")
+        
+        if response.status_code == 201:
+            print("登录成功!")
+            return True
+        else:
+            print(f"登录失败: {response.json()}")
+            return False
+    
+    def simulate_alpha(self, expression, settings=None):
+        """模拟Alpha因子"""
+        if self.client is None:
+            raise Exception("请先登录")
+        
+        default_settings = {
+            'instrumentType': 'EQUITY',
+            'region': 'USA', 
+            'universe': 'TOP3000',
+            'delay': 1,
+            'decay': 0,
+            'neutralization': 'INDUSTRY',
+            'truncation': 0.08,
+            'pasteurization': 'ON',
+            'unitHandling': 'VERIFY',
+            'nanHandling': 'OFF',
+            'language': 'FASTEXPR',
+            'visualization': False,
+        }
+        
+        if settings:
+            default_settings.update(settings)
+        
+        simulation_data = {
+            'type': 'REGULAR',
+            'settings': default_settings,
+            'regular': expression
+        }
+        
+        sim_resp = self.client.post(
+            f'{self.brain_api_url}/simulations',
+            json=simulation_data,
+        )
+        print(f"模拟提交状态: {sim_resp.status_code}")
+        
+        sim_progress_url = sim_resp.headers['location']
+        print(f"进度URL: {sim_progress_url}")
+        
+        while True:
+            sim_progress_resp = self.client.get(sim_progress_url)
+            retry_after_sec = float(sim_progress_resp.headers.get("Retry-After", 0))
+            
+            if retry_after_sec == 0:
+                break
+            print(sim_progress_resp.json())
+            print(f"等待 {retry_after_sec} 秒...")
+            sleep(retry_after_sec)
+        
+        alpha_id = sim_progress_resp.json()["alpha"]
+        print(f"生成的Alpha ID: {alpha_id}")
+        return alpha_id
+
+    def close(self):
+        """关闭连接"""
+        if self.client:
+            self.client.close()
+
+if __name__ == "__main__":
+    api = WorldQuantBrainAPI()
+    
+    try:
+        # 读取账号密码并登录
+        if api.login():
+            # 模拟Alpha因子
+            alpha_id = api.simulate_alpha("liabilities/assets")
+            print(f"模拟完成,Alpha ID: {alpha_id}")
+    finally:
+        api.close()

+ 1 - 0
wqb-demo/simulation_results.json

@@ -0,0 +1 @@
+null