Skip to content

Commit d9c3a6e

Browse files
author
方佳
committed
Merge branch 'main_261024_merge' into 'main'
feat: support market and community See merge request webull/webull-openapi-cli!8
2 parents 8ecf2bf + b364f05 commit d9c3a6e

4 files changed

Lines changed: 567 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ Data is organized by asset class under `webull data <asset>`:
161161
| `webull data stock footprint` | Large order footprint |
162162
| `webull data stock noii-bars` | NOII (Net Order Imbalance) bars |
163163
| `webull data stock noii-snapshot` | NOII snapshot |
164+
| `webull data market-sentiment` | US market fear & greed sentiment index (US only) |
165+
| `webull data stock unusual-activities` | US stock unusual activity alerts (US only) |
166+
| `webull data stock insider-transactions` | US insider transactions (US only) |
167+
| `webull data stock insider-holdings-overview` | US insider ownership overview (US only) |
168+
| `webull data stock economic-calendar` | US macroeconomic calendar (US only) |
169+
| `webull data 13f search` | Search US institutions (US only) |
170+
| `webull data 13f holdings` | Institution 13F holdings (US only) |
171+
| `webull data 13f transactions` | Institution holding changes (US only) |
172+
| `webull data news search` | Search US market news by keyword (US only) |
173+
| `webull data news detail` | Get full US news content by ID (US only) |
174+
| `webull data news stock-news` | US stock-related news list (US only) |
164175
| `webull data option snapshot` | Option real-time snapshot |
165176
| `webull data option bars` | Option historical bars |
166177
| `webull data option tick` | Option tick data |
@@ -235,6 +246,17 @@ Data is organized by asset class under `webull data <asset>`:
235246
| `webull watchlist remove` | Remove instruments |
236247
| `webull watchlist sort` | Update instruments sort order |
237248

249+
### Community (`webull community`) — US only
250+
251+
| Command | Description |
252+
|---------|-------------|
253+
| `webull community post create` | Create a plain-text post (optional `--symbol` + `--category`) |
254+
| `webull community comment create` | Create a comment on a post |
255+
| `webull community hot-stocks` | List stocks ranked by community activity |
256+
| `webull community post by-symbol` | List posts associated with a symbol |
257+
| `webull community post get` | Get post detail |
258+
| `webull community my-posts` | List posts created by the current user |
259+
238260
### Account
239261

240262
| Command | Description |

‎internal/cmd/community.go‎

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Copyright 2024-2026 Webull Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0.
3+
// See LICENSE file in the project root for full license information.
4+
5+
package cmd
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"os"
11+
12+
"github.com/spf13/cobra"
13+
"github.com/webull-inc/webull-openapi-cli/internal/client"
14+
"github.com/webull-inc/webull-openapi-cli/internal/output"
15+
)
16+
17+
var communityCmd = &cobra.Command{
18+
Use: "community",
19+
Short: "Community (posts, comments, hot stocks)",
20+
}
21+
22+
var communityPostCmd = &cobra.Command{Use: "post", Short: "Community posts"}
23+
var communityCommentCmd = &cobra.Command{Use: "comment", Short: "Community comments"}
24+
25+
func init() {
26+
communityCmd.AddCommand(communityPostCmd)
27+
communityCmd.AddCommand(communityCommentCmd)
28+
communityCmd.AddCommand(communityHotStocksCmd)
29+
communityCmd.AddCommand(communityMyPostsCmd)
30+
31+
communityPostCmd.AddCommand(communityPostCreateCmd)
32+
communityPostCmd.AddCommand(communityPostGetCmd)
33+
communityPostCmd.AddCommand(communityPostsBySymbolCmd)
34+
35+
communityCommentCmd.AddCommand(communityCommentCreateCmd)
36+
}
37+
38+
// ==================== Create Post ====================
39+
40+
var communityPostCreateCmd = &cobra.Command{
41+
Use: "create",
42+
Short: "Create a plain-text community post",
43+
RunE: func(cmd *cobra.Command, args []string) error {
44+
text, _ := cmd.Flags().GetString("text")
45+
symbol, _ := cmd.Flags().GetString("symbol")
46+
category, _ := cmd.Flags().GetString("category")
47+
48+
if text == "" {
49+
output.PrintError("MISSING_PARAMETER", "--text must not be empty", 0)
50+
return errSilent
51+
}
52+
// symbol and category must be provided together.
53+
if (symbol == "") != (category == "") {
54+
output.PrintError("INVALID_ARGUMENT", "--symbol and --category must be provided together", 0)
55+
return errSilent
56+
}
57+
58+
body := map[string]interface{}{"text": text}
59+
if symbol != "" {
60+
body["symbol"] = symbol
61+
body["category"] = category
62+
}
63+
return doUSDataPost("/market-data/communities/posts/create", body)
64+
},
65+
}
66+
67+
func init() {
68+
communityPostCreateCmd.Flags().String("text", "", "Post content (required)")
69+
communityPostCreateCmd.Flags().String("symbol", "", "Ticker symbol (optional, requires --category)")
70+
communityPostCreateCmd.Flags().String("category", "", "Market category (optional, requires --symbol): US_STOCK, HK_STOCK")
71+
communityPostCreateCmd.MarkFlagRequired("text")
72+
}
73+
74+
// ==================== Create Comment ====================
75+
76+
var communityCommentCreateCmd = &cobra.Command{
77+
Use: "create",
78+
Short: "Create a plain-text comment on a community post",
79+
RunE: func(cmd *cobra.Command, args []string) error {
80+
postID, _ := cmd.Flags().GetString("post-id")
81+
text, _ := cmd.Flags().GetString("text")
82+
if postID == "" {
83+
output.PrintError("MISSING_PARAMETER", "--post-id must not be empty", 0)
84+
return errSilent
85+
}
86+
if text == "" {
87+
output.PrintError("MISSING_PARAMETER", "--text must not be empty", 0)
88+
return errSilent
89+
}
90+
body := map[string]interface{}{
91+
"post_id": postID,
92+
"text": text,
93+
}
94+
return doUSDataPost("/market-data/communities/comments/create", body)
95+
},
96+
}
97+
98+
func init() {
99+
communityCommentCreateCmd.Flags().String("post-id", "", "Parent post identifier (required)")
100+
communityCommentCreateCmd.Flags().String("text", "", "Comment content (required)")
101+
communityCommentCreateCmd.MarkFlagRequired("post-id")
102+
communityCommentCreateCmd.MarkFlagRequired("text")
103+
}
104+
105+
// ==================== Hot Stocks ====================
106+
107+
var communityHotStocksCmd = &cobra.Command{
108+
Use: "hot-stocks",
109+
Short: "List stocks ranked by community activity",
110+
RunE: func(cmd *cobra.Command, args []string) error {
111+
return doUSDataGet("/market-data/communities/stock-rankings/list", map[string]string{})
112+
},
113+
}
114+
115+
// ==================== List Posts by Symbol ====================
116+
117+
var communityPostsBySymbolCmd = &cobra.Command{
118+
Use: "by-symbol",
119+
Short: "List community posts associated with a symbol",
120+
RunE: func(cmd *cobra.Command, args []string) error {
121+
symbol, _ := cmd.Flags().GetString("symbol")
122+
category, _ := cmd.Flags().GetString("category")
123+
keyword, _ := cmd.Flags().GetString("keyword")
124+
paginationKey, _ := cmd.Flags().GetString("pagination-key")
125+
if symbol == "" {
126+
output.PrintError("MISSING_PARAMETER", "--symbol must not be empty", 0)
127+
return errSilent
128+
}
129+
q := map[string]string{"symbol": symbol, "category": category}
130+
if keyword != "" {
131+
q["keyword"] = keyword
132+
}
133+
if paginationKey != "" {
134+
q["pagination_key"] = paginationKey
135+
}
136+
return doUSDataGet("/market-data/communities/posts/symbols/list", q)
137+
},
138+
}
139+
140+
func init() {
141+
communityPostsBySymbolCmd.Flags().String("symbol", "", "Stock symbol (required)")
142+
communityPostsBySymbolCmd.Flags().String("category", "US_STOCK", "Market category: US_STOCK, HK_STOCK")
143+
communityPostsBySymbolCmd.Flags().String("keyword", "", "Optional search keyword")
144+
communityPostsBySymbolCmd.Flags().String("pagination-key", "", "Cursor from the previous response")
145+
communityPostsBySymbolCmd.MarkFlagRequired("symbol")
146+
}
147+
148+
// ==================== Get Post Detail ====================
149+
150+
var communityPostGetCmd = &cobra.Command{
151+
Use: "get",
152+
Short: "Get detailed information for a community post",
153+
RunE: func(cmd *cobra.Command, args []string) error {
154+
postID, _ := cmd.Flags().GetString("post-id")
155+
if postID == "" {
156+
output.PrintError("MISSING_PARAMETER", "--post-id must not be empty", 0)
157+
return errSilent
158+
}
159+
return doUSDataGet("/market-data/communities/posts/get", map[string]string{"post_id": postID})
160+
},
161+
}
162+
163+
func init() {
164+
communityPostGetCmd.Flags().String("post-id", "", "Post identifier (required)")
165+
communityPostGetCmd.MarkFlagRequired("post-id")
166+
}
167+
168+
// ==================== List My Posts ====================
169+
170+
var communityMyPostsCmd = &cobra.Command{
171+
Use: "my-posts",
172+
Short: "List posts created by the current user",
173+
RunE: func(cmd *cobra.Command, args []string) error {
174+
paginationKey, _ := cmd.Flags().GetString("pagination-key")
175+
q := map[string]string{}
176+
if paginationKey != "" {
177+
q["pagination_key"] = paginationKey
178+
}
179+
return doUSDataGet("/market-data/communities/my-posts/list", q)
180+
},
181+
}
182+
183+
func init() {
184+
communityMyPostsCmd.Flags().String("pagination-key", "", "Cursor from the previous response")
185+
}
186+
187+
// ==================== Helper ====================
188+
189+
// doUSDataPost issues a US-only POST with a JSON body and prints the response.
190+
func doUSDataPost(path string, body map[string]interface{}) error {
191+
cl, err := newUSDataClient()
192+
if err != nil {
193+
output.PrintError("CONFIG_ERROR", err.Error(), 0)
194+
return err
195+
}
196+
resp, err := cl.Do(context.Background(), "POST", path, &client.RequestOptions{
197+
Body: body,
198+
})
199+
if err != nil {
200+
output.PrintError("REQUEST_ERROR", err.Error(), 0)
201+
return err
202+
}
203+
if resp.StatusCode != 200 {
204+
fmt.Fprintln(os.Stderr, string(resp.Body))
205+
return errSilent
206+
}
207+
if flagCSV {
208+
return output.PrintCSV(resp.Body)
209+
}
210+
output.PrintRawJSON(resp.Body)
211+
return nil
212+
}

0 commit comments

Comments
 (0)