Skip to content

Commit 1358063

Browse files
committed
fix candlesticks (K-line) might be generated incorrectly in certain situations.
1 parent d26041b commit 1358063

11 files changed

Lines changed: 73 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
# [3.0.14] 2025-09-05
8+
9+
- fix candlesticks (K-line) might be generated incorrectly in certain situations.
10+
711
# [3.0.13] 2025-08-22
812

913
- fix [#298](https://github.com/longportapp/openapi/issues/298)

c/src/quote_context/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ impl From<Candlestick> for CCandlestickOwned {
503503
turnover,
504504
timestamp,
505505
trade_session,
506+
..
506507
} = candlestick;
507508
CCandlestickOwned {
508509
close: close.into(),

java/crates/macros/src/lib.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,22 @@ pub fn impl_java_class(input: TokenStream) -> TokenStream {
9292
fields
9393
};
9494

95+
let mut non_exhaustive = false;
96+
97+
for expr in exprs {
98+
if let Expr::Path(ExprPath { path, .. }) = &expr
99+
&& path.is_ident("non_exhaustive")
100+
{
101+
non_exhaustive = true;
102+
}
103+
}
104+
105+
let non_exhaustive = if non_exhaustive {
106+
Some(quote! { , .. })
107+
} else {
108+
None
109+
};
110+
95111
let signature = format!("L{classname};");
96112
let mut set_fields = Vec::new();
97113

@@ -155,7 +171,7 @@ pub fn impl_java_class(input: TokenStream) -> TokenStream {
155171

156172
impl crate::types::IntoJValue for #type_path {
157173
fn into_jvalue<'a>(self, env: &mut jni::JNIEnv<'a>) -> jni::errors::Result<jni::objects::JValueOwned<'a>> {
158-
let #type_path { #(#field_names),* } = self;
174+
let #type_path { #(#field_names),* #non_exhaustive } = self;
159175
let cls = <Self as crate::types::ClassLoader>::class_ref();
160176
let obj = env.new_object(cls.borrow(), "()V", &[])?;
161177
#(#set_fields)*

java/src/types/classes.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ impl_java_class!(
265265
turnover,
266266
timestamp,
267267
trade_session
268-
]
268+
],
269+
non_exhaustive
269270
);
270271

271272
impl_java_class!(

nodejs/crates/macros/src/jsobject.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ struct ObjectArgs {
3232
data: Data<Ignored, ObjectField>,
3333

3434
remote: TypePath,
35+
#[darling(default)]
36+
non_exhaustive: bool,
3537
}
3638

3739
pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
3840
let ObjectArgs {
3941
ident,
4042
data,
4143
remote,
44+
non_exhaustive,
4245
} = ObjectArgs::from_derive_input(&args)?;
4346

4447
let s = match data {
@@ -50,6 +53,11 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
5053
let mut getters = Vec::new();
5154
let mut from_fields = Vec::new();
5255
let mut json_fields = Vec::new();
56+
let non_exhaustive = if non_exhaustive {
57+
Some(quote! { , .. })
58+
} else {
59+
None
60+
};
5361

5462
for field in &s.fields {
5563
let field_ident = field.ident.as_ref().unwrap();
@@ -147,7 +155,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
147155
impl ::std::convert::TryFrom<#remote> for #ident {
148156
type Error = ::napi::Error;
149157

150-
fn try_from(#remote { #(#fields),* }: #remote) -> ::std::result::Result<Self, Self::Error> {
158+
fn try_from(#remote { #(#fields),* #non_exhaustive }: #remote) -> ::std::result::Result<Self, Self::Error> {
151159
use ::std::convert::TryInto;
152160
use ::std::iter::Iterator;
153161

nodejs/src/quote/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ pub struct IntradayLine {
665665
/// Candlestick
666666
#[napi_derive::napi]
667667
#[derive(Debug, JsObject, Copy, Clone)]
668-
#[js(remote = "longport::quote::Candlestick")]
668+
#[js(remote = "longport::quote::Candlestick", non_exhaustive)]
669669
pub struct Candlestick {
670670
/// Close price
671671
close: Decimal,

python/crates/macros/src/pyobject.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@ struct ObjectArgs {
2828
data: Data<Ignored, ObjectField>,
2929

3030
remote: TypePath,
31+
#[darling(default)]
32+
non_exhaustive: bool,
3133
}
3234

3335
pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
3436
let ObjectArgs {
3537
ident,
3638
data,
3739
remote,
40+
non_exhaustive,
3841
} = ObjectArgs::from_derive_input(&args)?;
3942

4043
let s = match data {
@@ -46,6 +49,11 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
4649
let mut getters = Vec::new();
4750
let mut from_fields = Vec::new();
4851
let mut set_dictitem = Vec::new();
52+
let non_exhaustive = if non_exhaustive {
53+
Some(quote! { , .. })
54+
} else {
55+
None
56+
};
4957

5058
for field in &s.fields {
5159
let field_ident = field.ident.as_ref().unwrap();
@@ -55,7 +63,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
5563
set_dictitem.push(quote! {
5664
d.set_item(#name, self.#field_ident.clone().into_pyobject(py)?)?;
5765
});
58-
fields.push(field_ident);
66+
fields.push(field_ident.clone());
5967
getters.push(quote! {
6068
#[getter]
6169
#[inline]
@@ -119,7 +127,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
119127
impl ::std::convert::TryFrom<#remote> for #ident {
120128
type Error = ::pyo3::PyErr;
121129

122-
fn try_from(#remote { #(#fields),* }: #remote) -> ::std::result::Result<Self, Self::Error> {
130+
fn try_from(#remote { #(#fields),* #non_exhaustive }: #remote) -> ::std::result::Result<Self, Self::Error> {
123131
use ::std::convert::TryInto;
124132
use ::std::iter::Iterator;
125133

python/src/quote/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ pub(crate) struct IntradayLine {
632632
/// Candlestick
633633
#[pyclass]
634634
#[derive(Debug, PyObject, Clone)]
635-
#[py(remote = "longport::quote::Candlestick")]
635+
#[py(remote = "longport::quote::Candlestick", non_exhaustive)]
636636
pub(crate) struct Candlestick {
637637
/// Close price
638638
close: PyDecimal,

rust/crates/candlesticks/src/candlestick.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub struct CandlestickComponents<PriceType, VolumeType, TurnoverType, TradeSessi
1212
pub volume: VolumeType,
1313
pub turnover: TurnoverType,
1414
pub trade_session: TradeSessionType,
15+
pub open_updated: bool,
1516
}
1617

1718
pub trait CandlestickType {
@@ -51,4 +52,7 @@ pub trait CandlestickType {
5152
fn set_turnover(&mut self, turnover: Self::TurnoverType);
5253

5354
fn trade_session(&self) -> Self::TradeSessionType;
55+
56+
fn set_open_updated(&mut self, open_updated: bool);
57+
fn open_updated(&self) -> bool;
5458
}

rust/crates/candlesticks/src/market.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ impl Market {
233233
let mut candlestick = prev;
234234

235235
if update_fields.contains(UpdateFields::PRICE) {
236+
if !candlestick.open_updated() {
237+
candlestick.set_open(trade.price());
238+
candlestick.set_open_updated(true);
239+
}
240+
236241
candlestick.set_high(if trade.price() > candlestick.high() {
237242
trade.price()
238243
} else {
@@ -267,6 +272,7 @@ impl Market {
267272
volume: trade.volume(),
268273
turnover: trade.turnover(self.lot_size),
269274
trade_session,
275+
open_updated: true,
270276
});
271277
UpdateAction::AppendNew {
272278
confirmed: None,
@@ -286,13 +292,15 @@ impl Market {
286292
volume: V::zero(),
287293
turnover: R::zero(),
288294
trade_session,
295+
open_updated: false,
289296
});
290297

291298
if update_fields.contains(UpdateFields::PRICE) {
292299
new_candlestick.set_open(trade.price());
293300
new_candlestick.set_high(trade.price());
294301
new_candlestick.set_low(trade.price());
295302
new_candlestick.set_close(trade.price());
303+
new_candlestick.set_open_updated(true);
296304
}
297305

298306
if update_fields.contains(UpdateFields::VOLUME) {
@@ -340,6 +348,7 @@ impl Market {
340348
volume: quote.volume(),
341349
turnover: quote.turnover(),
342350
trade_session,
351+
open_updated: true,
343352
}))
344353
}
345354
None => UpdateAction::AppendNew {
@@ -353,6 +362,7 @@ impl Market {
353362
volume: quote.volume(),
354363
turnover: quote.turnover(),
355364
trade_session,
365+
open_updated: true,
356366
}),
357367
},
358368
Some(prev) if time > prev.time() => UpdateAction::AppendNew {
@@ -366,6 +376,7 @@ impl Market {
366376
volume: quote.volume(),
367377
turnover: quote.turnover(),
368378
trade_session,
379+
open_updated: true,
369380
}),
370381
},
371382
_ => UpdateAction::None,

0 commit comments

Comments
 (0)