Skip to content

Commit e7f941e

Browse files
hogan-yuanclaude
andcommitted
feat(screener): typed ScreenerCondition for Mode B, fix Java market param, fix C++ signatures
- Add ScreenerCondition struct (Rust/Python/Node.js) for typed screener_search Mode B conditions instead of KEY:MIN:MAX strings - Python: ScreenerCondition pyclass with key/min/max/tech_values(JSON string) - Node.js: ScreenerCondition napi object with same fields - Java: add market param to getRecommendStrategies/getUserStrategies and SdkNative - C++: add market param to screener_recommend/user_strategies hpp/cpp Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent 75e33ae commit e7f941e

12 files changed

Lines changed: 177 additions & 33 deletions

File tree

java/javasrc/src/main/java/com/longbridge/SdkNative.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,11 @@ public static native void marketContextRankList(long context, Object opts,
335335
public static native void freeScreenerContext(long context);
336336

337337
public static native void screenerContextRecommendStrategies(long context,
338+
String market,
338339
AsyncCallback callback);
339340

340341
public static native void screenerContextUserStrategies(long context,
342+
String market,
341343
AsyncCallback callback);
342344

343345
public static native void screenerContextStrategy(long context, Object opts,

java/javasrc/src/main/java/com/longbridge/screener/ScreenerContext.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,24 @@ public void close() throws Exception {
2020
SdkNative.freeScreenerContext(raw);
2121
}
2222

23-
/** Get platform-recommended screener strategies. */
23+
/** Get platform-preset screener strategies for the given market (default "US"). */
24+
public CompletableFuture<ScreenerRecommendStrategiesResponse> getRecommendStrategies(String market) throws OpenApiException {
25+
return AsyncCallback.executeTask((callback) -> SdkNative.screenerContextRecommendStrategies(raw, market, callback));
26+
}
27+
28+
/** Get platform-preset screener strategies (defaults to US market). */
2429
public CompletableFuture<ScreenerRecommendStrategiesResponse> getRecommendStrategies() throws OpenApiException {
25-
return AsyncCallback.executeTask((callback) -> SdkNative.screenerContextRecommendStrategies(raw, callback));
30+
return getRecommendStrategies("US");
31+
}
32+
33+
/** Get the current user's saved screener strategies for the given market (default "US"). */
34+
public CompletableFuture<ScreenerUserStrategiesResponse> getUserStrategies(String market) throws OpenApiException {
35+
return AsyncCallback.executeTask((callback) -> SdkNative.screenerContextUserStrategies(raw, market, callback));
2636
}
2737

28-
/** Get the current user's saved screener strategies. */
38+
/** Get the current user's saved screener strategies (defaults to US market). */
2939
public CompletableFuture<ScreenerUserStrategiesResponse> getUserStrategies() throws OpenApiException {
30-
return AsyncCallback.executeTask((callback) -> SdkNative.screenerContextUserStrategies(raw, callback));
40+
return getUserStrategies("US");
3141
}
3242

3343
/** Get detail for one screener strategy by ID. */

nodejs/src/screener/context.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl ScreenerContext {
6767
/// strategy response.
6868
///
6969
/// When `strategyId` is `null` / `undefined` (Mode B), `conditions` must be
70-
/// `"KEY:MIN:MAX"` strings and `market` is used directly.
70+
/// `ScreenerCondition` objects and `market` is used directly.
7171
///
7272
/// `filter_` is stripped from every `items[].indicators[].key` in the
7373
/// response before it is returned.
@@ -76,14 +76,16 @@ impl ScreenerContext {
7676
&self,
7777
market: String,
7878
strategy_id: Option<i64>,
79-
#[napi(ts_arg_type = "string[]")] conditions: Vec<String>,
80-
#[napi(ts_arg_type = "string[]")] show: Vec<String>,
79+
conditions: Vec<ScreenerCondition>,
80+
show: Vec<String>,
8181
page: u32,
8282
size: u32,
8383
) -> Result<ScreenerSearchResponse> {
84+
let lb_conditions: Vec<longbridge::screener::ScreenerCondition> =
85+
conditions.into_iter().map(Into::into).collect();
8486
Ok(self
8587
.ctx
86-
.screener_search(market, strategy_id, conditions, show, page, size)
88+
.screener_search(market, strategy_id, lb_conditions, show, page, size)
8789
.await
8890
.map_err(ErrorNewType)?
8991
.into())

nodejs/src/screener/types.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,33 @@ impl From<lb::ScreenerIndicatorsResponse> for ScreenerIndicatorsResponse {
8989
}
9090
}
9191
}
92+
93+
// ── ScreenerCondition ─────────────────────────────────────────────
94+
95+
/// A filter condition for screener_search Mode B.
96+
#[napi_derive::napi(object)]
97+
#[derive(Debug, Clone, Default)]
98+
pub struct ScreenerCondition {
99+
/// Indicator key without filter_ prefix, e.g. "pettm", "roe", "macd_day"
100+
pub key: String,
101+
/// Lower bound (empty = no lower bound)
102+
pub min: String,
103+
/// Upper bound (empty = no upper bound)
104+
pub max: String,
105+
/// Technical indicator params as JSON string (empty object "{}" for
106+
/// fundamental indicators)
107+
pub tech_values: String,
108+
}
109+
110+
impl From<ScreenerCondition> for longbridge::screener::ScreenerCondition {
111+
fn from(v: ScreenerCondition) -> Self {
112+
let tv: serde_json::Value =
113+
serde_json::from_str(&v.tech_values).unwrap_or(serde_json::json!({}));
114+
Self {
115+
key: v.key,
116+
min: v.min,
117+
max: v.max,
118+
tech_values: tv,
119+
}
120+
}
121+
}

python/pysrc/longbridge/openapi.pyi

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10530,6 +10530,28 @@ class RankListResponse:
1053010530

1053110531
# ── ScreenerContext ───────────────────────────────────────────────
1053210532

10533+
class ScreenerCondition:
10534+
"""A filter condition for :meth:`ScreenerContext.screener_search` Mode B."""
10535+
10536+
key: str
10537+
"""Indicator key without ``filter_`` prefix, e.g. ``"pettm"``, ``"roe"``, ``"macd_day"``"""
10538+
min: str
10539+
"""Lower bound (empty string = no lower bound)"""
10540+
max: str
10541+
"""Upper bound (empty string = no upper bound)"""
10542+
tech_values: str
10543+
"""Technical indicator params as JSON string. Use ``"{}"`` for fundamental indicators.
10544+
Example: ``'{"category": "goldenfork", "period": "day"}'``"""
10545+
10546+
def __init__(
10547+
self,
10548+
key: str,
10549+
min: str = "",
10550+
max: str = "",
10551+
tech_values: str = "{}",
10552+
) -> None: ...
10553+
10554+
1053310555
class ScreenerRecommendStrategiesResponse:
1053410556
"""Recommended screener strategies response. ``data`` is a Python dict/list from JSON."""
1053510557

@@ -10586,7 +10608,7 @@ class ScreenerContext:
1058610608
self,
1058710609
market: str,
1058810610
strategy_id: Optional[int] = None,
10589-
conditions: List[str] = [],
10611+
conditions: List["ScreenerCondition"] = [],
1059010612
show: List[str] = [],
1059110613
page: int = 0,
1059210614
size: int = 20,
@@ -10598,7 +10620,7 @@ class ScreenerContext:
1059810620
``market`` is taken from the strategy response.
1059910621
1060010622
When *strategy_id* is ``None`` (Mode B), *conditions* must be provided as
10601-
``"KEY:MIN:MAX"`` strings and *market* is used directly.
10623+
:class:`ScreenerCondition` objects and *market* is used directly.
1060210624
1060310625
``filter_`` is stripped from every ``items[].indicators[].key`` in the
1060410626
response before it is returned.
@@ -10640,7 +10662,7 @@ class AsyncScreenerContext:
1064010662
self,
1064110663
market: str,
1064210664
strategy_id: Optional[int] = None,
10643-
conditions: List[str] = [],
10665+
conditions: List["ScreenerCondition"] = [],
1064410666
show: List[str] = [],
1064510667
page: int = 0,
1064610668
size: int = 20,
@@ -10653,7 +10675,7 @@ class AsyncScreenerContext:
1065310675
``market`` is taken from the strategy response.
1065410676
1065510677
When *strategy_id* is ``None`` (Mode B), *conditions* must be provided as
10656-
``"KEY:MIN:MAX"`` strings and *market* is used directly.
10678+
:class:`ScreenerCondition` objects and *market* is used directly.
1065710679
1065810680
``filter_`` is stripped from every ``items[].indicators[].key`` in the
1065910681
response before it is returned.

python/src/screener/context.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,16 @@ impl ScreenerContext {
5252
&self,
5353
market: String,
5454
strategy_id: Option<i64>,
55-
conditions: Vec<String>,
55+
conditions: Vec<ScreenerCondition>,
5656
show: Vec<String>,
5757
page: u32,
5858
size: u32,
5959
) -> PyResult<ScreenerSearchResponse> {
60+
let lb_conditions: Vec<longbridge::screener::ScreenerCondition> =
61+
conditions.into_iter().map(Into::into).collect();
6062
Ok(self
6163
.ctx
62-
.screener_search(market, strategy_id, conditions, show, page, size)
64+
.screener_search(market, strategy_id, lb_conditions, show, page, size)
6365
.map_err(ErrorNewType)?
6466
.into())
6567
}

python/src/screener/context_async.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,17 @@ impl AsyncScreenerContext {
6666
py: Python<'_>,
6767
market: String,
6868
strategy_id: Option<i64>,
69-
conditions: Vec<String>,
69+
conditions: Vec<ScreenerCondition>,
7070
show: Vec<String>,
7171
page: u32,
7272
size: u32,
7373
) -> PyResult<Py<PyAny>> {
7474
let ctx = self.ctx.clone();
75+
let lb_conditions: Vec<longbridge::screener::ScreenerCondition> =
76+
conditions.into_iter().map(Into::into).collect();
7577
pyo3_async_runtimes::tokio::future_into_py(py, async move {
7678
Ok(ScreenerSearchResponse::from(
77-
ctx.screener_search(market, strategy_id, conditions, show, page, size)
79+
ctx.screener_search(market, strategy_id, lb_conditions, show, page, size)
7880
.await
7981
.map_err(ErrorNewType)?,
8082
))

python/src/screener/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub(crate) fn register_types(parent: &Bound<PyModule>) -> PyResult<()> {
1111
parent.add_class::<ScreenerStrategyResponse>()?;
1212
parent.add_class::<ScreenerSearchResponse>()?;
1313
parent.add_class::<ScreenerIndicatorsResponse>()?;
14+
parent.add_class::<ScreenerCondition>()?;
1415
parent.add_class::<context::ScreenerContext>()?;
1516
parent.add_class::<context_async::AsyncScreenerContext>()?;
1617
Ok(())

python/src/screener/types.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,47 @@ impl From<lb::ScreenerIndicatorsResponse> for ScreenerIndicatorsResponse {
105105
}
106106
}
107107
}
108+
109+
// ── ScreenerCondition ─────────────────────────────────────────────
110+
111+
/// A filter condition for screener_search Mode B.
112+
#[pyclass(get_all, set_all)]
113+
#[derive(Debug, Clone, Default)]
114+
pub(crate) struct ScreenerCondition {
115+
/// Indicator key without filter_ prefix, e.g. "pettm", "roe", "macd_day"
116+
pub key: String,
117+
/// Lower bound (empty = no lower bound)
118+
pub min: String,
119+
/// Upper bound (empty = no upper bound)
120+
pub max: String,
121+
/// Technical indicator params as JSON string (empty object "{}" for
122+
/// fundamental indicators)
123+
pub tech_values: String,
124+
}
125+
126+
#[pymethods]
127+
impl ScreenerCondition {
128+
#[new]
129+
#[pyo3(signature = (key, min="", max="", tech_values="{}"))]
130+
pub fn new(key: String, min: &str, max: &str, tech_values: &str) -> Self {
131+
Self {
132+
key,
133+
min: min.to_string(),
134+
max: max.to_string(),
135+
tech_values: tech_values.to_string(),
136+
}
137+
}
138+
}
139+
140+
impl From<ScreenerCondition> for longbridge::screener::ScreenerCondition {
141+
fn from(v: ScreenerCondition) -> Self {
142+
let tv: serde_json::Value =
143+
serde_json::from_str(&v.tech_values).unwrap_or(serde_json::json!({}));
144+
Self {
145+
key: v.key,
146+
min: v.min,
147+
max: v.max,
148+
tech_values: tv,
149+
}
150+
}
151+
}

rust/src/blocking/screener.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl ScreenerContextSync {
6363
&self,
6464
market: impl Into<String> + Send + 'static,
6565
strategy_id: Option<i64>,
66-
conditions: Vec<String>,
66+
conditions: Vec<crate::screener::ScreenerCondition>,
6767
show: Vec<String>,
6868
page: u32,
6969
size: u32,

0 commit comments

Comments
 (0)