Compare commits
68 Commits
5b08e9cc8a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6179137e0d | |||
| 958bc459b2 | |||
| 7eec44f21c | |||
| 5f42d36436 | |||
| 9a31b8cae4 | |||
| 482eb70465 | |||
| 7703f88d7f | |||
| db8a094c8f | |||
| f4fef2bd95 | |||
| 53a6d33fd5 | |||
| 83574e1229 | |||
| e2c5729f87 | |||
| 47f8b30a02 | |||
| 452d1c681d | |||
| 42b402d4ef | |||
| 8c4e3baacb | |||
| a075adf4b6 | |||
| 963c2a9878 | |||
| e387fc839f | |||
| b9a02194e2 | |||
| b0c2530e63 | |||
| 49a03adf21 | |||
| 5390a879e7 | |||
| 69020852b9 | |||
| 846c6fe819 | |||
| d3f337e2c4 | |||
| d4fec4c426 | |||
| 0dd421ca43 | |||
| 3991effa88 | |||
| 02508a5426 | |||
| edd18061eb | |||
| 3d428d9d0a | |||
| b4e2c81d36 | |||
| 1edd74e35d | |||
| 7d3b3758f3 | |||
| d6aab42892 | |||
| 5cd4875cf9 | |||
| f046427480 | |||
| 01690bbcd6 | |||
| 8011a4f2cb | |||
| a9b2561be1 | |||
| dd81ca1150 | |||
| 01bd5ed178 | |||
| a4db463dbd | |||
| 68aa6ff33f | |||
| 19dd19a9f8 | |||
| 5953c9b9f2 | |||
| f8ee51c0ed | |||
| 0b9cb55788 | |||
| 585b37a1cc | |||
| 868feeb34d | |||
| d2b3305ca6 | |||
| 10a0e14024 | |||
| a9621a10ac | |||
| b5688bd3ab | |||
| 17c96e96fc | |||
| 596e3caa59 | |||
| f28360e6ec | |||
| 7d0062eae0 | |||
| 697a58a0e4 | |||
| b569d62a25 | |||
| d3538ddce0 | |||
| 12b622956a | |||
| aee6673c64 | |||
| 9f781ce794 | |||
| 4cc7d2344f | |||
| 7fa524b8d5 | |||
| acab6978d4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -252,3 +252,4 @@ gradle-app.setting
|
||||
!.yarn/cache
|
||||
nohup.out
|
||||
!leopard*/bin
|
||||
archive
|
||||
|
||||
54
.idea/compiler.xml
generated
54
.idea/compiler.xml
generated
@@ -17,13 +17,11 @@
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.38/lombok-1.18.38.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/orm/hibernate-jpamodelgen/6.6.8.Final/hibernate-jpamodelgen-6.6.8.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/orm/hibernate-core/6.6.8.Final/hibernate-core-6.6.8.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/persistence/jakarta.persistence-api/3.2.0/jakarta.persistence-api-3.2.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/transaction/jakarta.transaction-api/2.0.1/jakarta.transaction-api-2.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging/3.5.0.Final/jboss-logging-3.5.0.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/fasterxml/classmate/1.5.1/classmate-1.5.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/common/hibernate-commons-annotations/7.0.3.Final/hibernate-commons-annotations-7.0.3.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/jandex/3.2.0/jandex-3.2.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/fasterxml/classmate/1.5.1/classmate-1.5.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.15.11/byte-buddy-1.15.11.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/xml/bind/jakarta.xml.bind-api/4.0.0/jakarta.xml.bind-api-4.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/activation/jakarta.activation-api/2.1.0/jakarta.activation-api-2.1.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/glassfish/jaxb/jaxb-runtime/4.0.2/jaxb-runtime-4.0.2.jar" />
|
||||
@@ -31,10 +29,11 @@
|
||||
<entry name="$MAVEN_REPOSITORY$/org/eclipse/angus/angus-activation/2.0.0/angus-activation-2.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/glassfish/jaxb/txw2/4.0.2/txw2-4.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/sun/istack/istack-commons-runtime/4.1.1/istack-commons-runtime-4.1.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.0/antlr4-runtime-4.13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/validation/jakarta.validation-api/3.0.2/jakarta.validation-api-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.0/antlr4-runtime-4.13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.15.11/byte-buddy-1.15.11.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging/3.5.0.Final/jboss-logging-3.5.0.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-apt/7.0/querydsl-apt-7.0-jpa.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-codegen/7.0/querydsl-codegen-7.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-core/7.0/querydsl-core-7.0.jar" />
|
||||
@@ -43,48 +42,7 @@
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-codegen-utils/7.0/querydsl-codegen-utils-7.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/eclipse/jdt/ecj/3.40.0/ecj-3.40.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.179/classgraph-4.8.179.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/26.0.2/annotations-26.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/dev/morphia/morphia/morphia-core/2.5.0/morphia-core-2.5.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/config/smallrye-config/3.10.1/smallrye-config-3.10.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/config/smallrye-config-core/3.10.1/smallrye-config-core-3.10.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/eclipse/microprofile/config/microprofile-config-api/3.1/microprofile-config-api-3.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-annotation/2.8.0/smallrye-common-annotation-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-expression/2.8.0/smallrye-common-expression-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-function/2.8.0/smallrye-common-function-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-constraint/2.8.0/smallrye-common-constraint-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-classloader/2.8.0/smallrye-common-classloader-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.8/asm-9.8.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/config/smallrye-config-common/3.10.1/smallrye-config-common-3.10.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/mongodb-driver-sync/5.4.0/mongodb-driver-sync-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/bson/5.4.0/bson-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/mongodb-driver-core/5.4.0/mongodb-driver-core-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/bson-record-codec/5.4.0/bson-record-codec-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/mongodb-driver-legacy/5.4.0/mongodb-driver-legacy-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/github/spotbugs/spotbugs-annotations/4.8.6/spotbugs-annotations-4.8.6.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/semver4j/semver4j/5.6.0/semver4j-5.6.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jsoup/jsoup/1.18.3/jsoup-1.18.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/orm/hibernate-envers/7.0.0.Beta1/hibernate-envers-7.0.0.Beta1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/models/hibernate-models/0.8.6/hibernate-models-0.8.6.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-core/7.0/querydsl-core-7.0-tests.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/joda/joda-money/2.0.2/joda-money-2.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.13.1/junit-jupiter-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.13.1/junit-jupiter-api-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.13.1/junit-platform-commons-1.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.13.1/junit-jupiter-params-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.13.1/junit-jupiter-engine-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.13.1/junit-platform-engine-1.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/assertj/assertj-core/3.27.3/assertj-core-3.27.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/vintage/junit-vintage-engine/5.13.1/junit-vintage-engine-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/junit/junit/4.13.2/junit-4.13.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/easymock/easymock/5.6.0/easymock-5.6.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.4/objenesis-3.4.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/javassist/javassist/3.30.2-GA/javassist-3.30.2-GA.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/persistence/jakarta.persistence-api/3.2.0/jakarta.persistence-api-3.2.0.jar" />
|
||||
</processorPath>
|
||||
<module name="leopard-core" />
|
||||
</profile>
|
||||
|
||||
1
.idea/data_source_mapping.xml
generated
1
.idea/data_source_mapping.xml
generated
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourcePerFileMappings">
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/63824900-a456-4883-8de4-8f436cd00c71/console.sql" value="63824900-a456-4883-8de4-8f436cd00c71" />
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f7d817dc-8c9c-479f-b469-583df17cb013/console.sql" value="f7d817dc-8c9c-479f-b469-583df17cb013" />
|
||||
</component>
|
||||
</project>
|
||||
15
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
15
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="NullableProblems" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
|
||||
<option name="REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL" value="true" />
|
||||
<option name="REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE" value="true" />
|
||||
<option name="REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL" value="true" />
|
||||
<option name="REPORT_NOT_ANNOTATED_GETTER" value="true" />
|
||||
<option name="REPORT_NOT_ANNOTATED_SETTER_PARAMETER" value="true" />
|
||||
<option name="REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS" value="true" />
|
||||
<option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,7 +8,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="temurin-17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -20,10 +20,33 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.icepear.echarts</groupId>
|
||||
<artifactId>echarts-java</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.ralfkonrad.quantlib_for_maven</groupId>
|
||||
<artifactId>quantlib</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ta4j</groupId>
|
||||
<artifactId>ta4j-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate.orm</groupId>
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.Comment;
|
||||
import org.hibernate.annotations.DynamicInsert;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* 资产负债表
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@DynamicInsert
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = Constants.DATABASE_PREFIX + "balance_sheet")
|
||||
public class BalanceSheet extends SimpleEntity {
|
||||
@ManyToOne
|
||||
private Stock stock;
|
||||
@Comment("年报年度")
|
||||
private Integer year;
|
||||
@Comment("原始名称:total_share,描述:期末总股本")
|
||||
private Double endingTotalShares;
|
||||
@Comment("原始名称:cap_rese,描述:资本公积金")
|
||||
private Double capitalSurplus;
|
||||
@Comment("原始名称:undistr_porfit,描述:未分配利润")
|
||||
private Double undistributedProfit;
|
||||
@Comment("原始名称:money_cap,描述:货币资金")
|
||||
private Double monetaryFunds;
|
||||
@Comment("原始名称:accounts_receiv,描述:应收账款")
|
||||
private Double accountsReceivable;
|
||||
@Comment("原始名称:inventories,描述:存货")
|
||||
private Double inventories;
|
||||
@Comment("原始名称:total_cur_assets,描述:流动资产合计")
|
||||
private Double totalCurrentAssets;
|
||||
@Comment("原始名称:lt_eqt_invest,描述:长期股权投资")
|
||||
private Double longTermEquityInvestments;
|
||||
@Comment("原始名称:lt_rec,描述:长期应收款")
|
||||
private Double longTermReceivables;
|
||||
@Comment("原始名称:fix_assets,描述:固定资产")
|
||||
private Double fixedAssets;
|
||||
@Comment("原始名称:r_and_d,描述:研发支出")
|
||||
private Double researchAndDevelopmentExpenditures;
|
||||
@Comment("原始名称:goodwill,描述:商誉")
|
||||
private Double goodwill;
|
||||
@Comment("原始名称:total_nca,描述:非流动资产合计")
|
||||
private Double totalNonCurrentAssets;
|
||||
@Comment("原始名称:total_assets,描述:资产总计")
|
||||
private Double totalAssets;
|
||||
@Comment("原始名称:lt_borr,描述:长期借款")
|
||||
private Double longTermBorrowings;
|
||||
@Comment("原始名称:st_borr,描述:短期借款")
|
||||
private Double shortTermBorrowings;
|
||||
@Comment("原始名称:acct_payable,描述:应付账款")
|
||||
private Double accountsPayable;
|
||||
@Comment("原始名称:adv_receipts,描述:预收款项")
|
||||
private Double advancesReceived;
|
||||
@Comment("原始名称:total_cur_liab,描述:流动负债合计")
|
||||
private Double totalCurrentLiabilities;
|
||||
@Comment("原始名称:total_ncl,描述:非流动负债合计")
|
||||
private Double totalNonCurrentLiabilities;
|
||||
@Comment("原始名称:total_liab,描述:负债合计")
|
||||
private Double totalLiabilities;
|
||||
@Comment("原始名称:total_hldr_eqy_exc_min_int,描述:股东权益合计(不含少数股东权益)")
|
||||
private Double totalShareholdersEquityExcludingMinorityInterest;
|
||||
@Comment("原始名称:total_hldr_eqy_inc_min_int,描述:股东权益合计(含少数股东权益)")
|
||||
private Double totalShareholdersEquityIncludingMinorityInterest;
|
||||
@Comment("原始名称:total_liab_hldr_eqy,描述:负债及股东权益总计")
|
||||
private Double totalLiabilitiesAndShareholdersEquity;
|
||||
@Comment("原始名称:acc_receivable,描述:应收款项")
|
||||
private Double receivables;
|
||||
@Comment("原始名称:payables,描述:应付款项")
|
||||
private Double payables;
|
||||
@Comment("原始名称:accounts_receiv_bill,描述:应收票据及应收账款")
|
||||
private Double notesAndAccountsReceivable;
|
||||
@Comment("原始名称:accounts_pay,描述:应付票据及应付账款")
|
||||
private Double notesAndAccountsPayable;
|
||||
@Comment("原始名称:oth_rcv_total,描述:其他应收款(合计)(元)")
|
||||
private Double otherReceivablesTotal;
|
||||
@Comment("原始名称:fix_assets_total,描述:固定资产(合计)(元)")
|
||||
private Double fixedAssetsTotal;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.Comment;
|
||||
import org.hibernate.annotations.DynamicInsert;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* 现金流量表
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@DynamicInsert
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = Constants.DATABASE_PREFIX + "cash_flow")
|
||||
public class CashFlow extends SimpleEntity {
|
||||
@ManyToOne
|
||||
private Stock stock;
|
||||
@Comment("年报年度")
|
||||
private Integer year;
|
||||
@Comment("原始名称:net_profit,描述:净利润")
|
||||
private Double netProfit;
|
||||
@Comment("原始名称:finan_exp,描述:财务费用")
|
||||
private Double financialExpense;
|
||||
@Comment("原始名称:c_fr_sale_sg,描述:销售商品、提供劳务收到的现金")
|
||||
private Double cashReceivedFromSalesAndServices;
|
||||
@Comment("原始名称:c_inf_fr_operate_a,描述:经营活动现金流入小计")
|
||||
private Double subtotalOfCashInflowsFromOperatingActivities;
|
||||
@Comment("原始名称:c_paid_to_for_empl,描述:支付给职工以及为职工支付的现金")
|
||||
private Double cashPaidToAndForEmployees;
|
||||
@Comment("原始名称:c_paid_for_taxes,描述:支付的各项税费")
|
||||
private Double cashPaidForVariousTaxes;
|
||||
@Comment("原始名称:n_cashflow_act,描述:经营活动产生的现金流量净额")
|
||||
private Double netCashFlowFromOperatingActivities;
|
||||
@Comment("原始名称:stot_inflows_inv_act,描述:投资活动现金流入小计")
|
||||
private Double subtotalOfCashInflowsFromInvestingActivities;
|
||||
@Comment("原始名称:c_pay_acq_const_fiolta,描述:购置固定资产、无形资产和其他长期资产支付的现金")
|
||||
private Double cashPaidForLongTermAssets;
|
||||
@Comment("原始名称:stot_out_inv_act,描述:投资活动现金流出小计")
|
||||
private Double subtotalOfCashOutflowsFromInvestingActivities;
|
||||
@Comment("原始名称:stot_cashout_fnc_act,描述:筹资活动现金流出小计")
|
||||
private Double subtotalOfCashOutflowsFromFinancingActivities;
|
||||
@Comment("原始名称:c_cash_equ_beg_period,描述:期初现金及现金等价物余额")
|
||||
private Double beginningBalanceOfCashAndCashEquivalents;
|
||||
@Comment("原始名称:c_cash_equ_end_period,描述:期末现金及现金等价物余额")
|
||||
private Double endingBalanceOfCashAndCashEquivalents;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.Column;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
@Table(name = Constants.DATABASE_PREFIX + "daily")
|
||||
public class Daily extends SimpleEntity {
|
||||
@Column(nullable = false)
|
||||
@Comment("交易日")
|
||||
private LocalDate tradeDate;
|
||||
@Comment("开盘价")
|
||||
private Double open;
|
||||
@@ -55,4 +57,20 @@ public class Daily extends SimpleEntity {
|
||||
@JoinColumn(nullable = false)
|
||||
@ToString.Exclude
|
||||
private Stock stock;
|
||||
|
||||
public Double getHfqOpen() {
|
||||
return open * ObjectUtil.defaultIfNull(factor, 1.0);
|
||||
}
|
||||
|
||||
public Double getHfqClose() {
|
||||
return close * ObjectUtil.defaultIfNull(factor, 1.0);
|
||||
}
|
||||
|
||||
public Double getHfqHigh() {
|
||||
return high * ObjectUtil.defaultIfNull(factor, 1.0);
|
||||
}
|
||||
|
||||
public Double getHfqLow() {
|
||||
return low * ObjectUtil.defaultIfNull(factor, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.Comment;
|
||||
import org.hibernate.annotations.DynamicInsert;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@DynamicInsert
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = Constants.DATABASE_PREFIX + "finance_indicator")
|
||||
public class FinanceIndicator extends SimpleEntity {
|
||||
@ManyToOne
|
||||
private Stock stock;
|
||||
@Column(name = "`year`", nullable = false)
|
||||
@Comment("年报年度")
|
||||
private Integer year;
|
||||
@Comment("总股本")
|
||||
private Double totalShareCapital;
|
||||
@Comment("资本公积金")
|
||||
private Double capitalSurplus;
|
||||
@Comment("盈余公积金")
|
||||
private Double surplusReserve;
|
||||
@Comment("未分配利润")
|
||||
private Double undistributedProfit;
|
||||
@Comment("现金及现金等价物")
|
||||
private Double cashAndCashEquivalents;
|
||||
@Comment("现金及现金等价物占总资产比率")
|
||||
private Double cashAndCashEquivalentsToTotalAssetsRatio;
|
||||
@Comment("应收账款")
|
||||
private Double accountsReceivable;
|
||||
@Comment("应收账款占总资产比率")
|
||||
private Double accountsReceivableToTotalAssetsRatio;
|
||||
@Comment("应付账款")
|
||||
private Double accountsPayable;
|
||||
@Comment("应付账款占总资产比率")
|
||||
private Double accountsPayableToTotalAssetsRatio;
|
||||
@Comment("存货")
|
||||
private Double inventory;
|
||||
@Comment("存货占总资产比率")
|
||||
private Double inventoryToTotalAssetsRatio;
|
||||
@Comment("商誉")
|
||||
private Double goodwill;
|
||||
@Comment("商誉占总资产比率")
|
||||
private Double goodwillToTotalAssetsRatio;
|
||||
@Comment("流动资产")
|
||||
private Double currentAssets;
|
||||
@Comment("流动资产占总资产比率")
|
||||
private Double currentAssetsToTotalAssetsRatio;
|
||||
@Comment("固定资产")
|
||||
private Double fixedAssets;
|
||||
@Comment("固定资产占总资产比率")
|
||||
private Double fixedAssetsToTotalAssetsRatio;
|
||||
@Comment("流动负债")
|
||||
private Double currentLiabilities;
|
||||
@Comment("流动负债占总资产比率")
|
||||
private Double currentLiabilitiesToTotalAssetsRatio;
|
||||
@Comment("流动负债占总负债比率")
|
||||
private Double currentLiabilitiesToTotalLiabilitiesRatio;
|
||||
@Comment("长期负债")
|
||||
private Double longTermLiabilities;
|
||||
@Comment("长期负债占总资产比率")
|
||||
private Double longTermLiabilitiesToTotalAssetsRatio;
|
||||
@Comment("长期负债占总负债比率")
|
||||
private Double longTermLiabilitiesToTotalLiabilitiesRatio;
|
||||
@Comment("总负债")
|
||||
private Double totalLiabilities;
|
||||
@Comment("负债占总资产比率")
|
||||
private Double liabilitiesToTotalAssetsRatio;
|
||||
@Comment("股东权益")
|
||||
private Double shareholdersEquity;
|
||||
@Comment("股东权益占总资产比率")
|
||||
private Double shareholdersEquityToTotalAssetsRatio;
|
||||
@Comment("总资产")
|
||||
private Double totalAssets;
|
||||
@Comment("营业收入")
|
||||
private Double operatingRevenue;
|
||||
@Comment("营业成本")
|
||||
private Double operatingCost;
|
||||
@Comment("营业利润")
|
||||
private Double operatingProfit;
|
||||
@Comment("营业支出")
|
||||
private Double operatingExpenses;
|
||||
@Comment("净利润")
|
||||
private Double netProfit;
|
||||
@Comment("经营活动现金流净额")
|
||||
private Double netCashFlowFromOperatingActivities;
|
||||
@Comment("营业活动现金流量")
|
||||
private Double cashFlowFromOperatingActivities;
|
||||
@Comment("投资活动现金流量")
|
||||
private Double cashFlowFromInvestingActivities;
|
||||
@Comment("筹资活动现金流量")
|
||||
private Double cashFlowFromFinancingActivities;
|
||||
@Comment("流动比率")
|
||||
private Double currentRatio;
|
||||
@Comment("速动比率")
|
||||
private Double quickRatio;
|
||||
@Comment("长期资金占固定资产比率")
|
||||
private Double longTermFundsToFixedAssetsRatio;
|
||||
@Comment("应收账款周转率")
|
||||
private Double accountsReceivableTurnover;
|
||||
@Comment("应收账款周转天数(平均收现天数)")
|
||||
private Double daysAccountsReceivableTurnover;
|
||||
@Comment("存货周转率")
|
||||
private Double inventoryTurnover;
|
||||
@Comment("存货周转天数(平均销货天数)")
|
||||
private Double daysInventoryTurnover;
|
||||
@Comment("固定资产周转率")
|
||||
private Double fixedAssetsTurnover;
|
||||
@Comment("固定资产周转天数")
|
||||
private Double daysFixedAssetsTurnover;
|
||||
@Comment("总资产周转率")
|
||||
private Double totalAssetsTurnover;
|
||||
@Comment("总资产周转天数")
|
||||
private Double daysTotalAssetsTurnover;
|
||||
@Comment("ROE")
|
||||
private Double returnOnEquity;
|
||||
@Comment("ROA")
|
||||
private Double returnOnAssets;
|
||||
@Comment("营业毛利率")
|
||||
private Double operatingGrossProfitMargin;
|
||||
@Comment("营业利益率")
|
||||
private Double operatingProfitMargin;
|
||||
@Comment("经营安全边际率")
|
||||
private Double operatingSafetyMarginRatio;
|
||||
@Comment("净利率")
|
||||
private Double netProfitMargin;
|
||||
@Comment("每股盈余")
|
||||
private Double earningsPerShare;
|
||||
@Comment("现金流量比率")
|
||||
private Double cashFlowRatio;
|
||||
@Comment("现金流量允当比率")
|
||||
private Double cashFlowAdequacyRatio;
|
||||
@Comment("现金再投资比率")
|
||||
private Double cashReinvestmentRatio;
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.Comment;
|
||||
import org.hibernate.annotations.DynamicInsert;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* 利润表
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@DynamicInsert
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = Constants.DATABASE_PREFIX + "income")
|
||||
public class Income extends SimpleEntity {
|
||||
@ManyToOne
|
||||
private Stock stock;
|
||||
@Comment("年报年度")
|
||||
private Integer year;
|
||||
@Comment("原始名称:basic_eps,描述:基本每股收益")
|
||||
private Double basicEarningsPerShare;
|
||||
@Comment("原始名称:diluted_eps,描述:稀释每股收益")
|
||||
private Double dilutedEarningsPerShare;
|
||||
@Comment("原始名称:total_revenue,描述:营业总收入")
|
||||
private Double totalOperatingRevenue;
|
||||
@Comment("原始名称:revenue,描述:营业收入")
|
||||
private Double operatingRevenue;
|
||||
@Comment("原始名称:total_cogs,描述:营业总成本")
|
||||
private Double totalOperatingCost;
|
||||
@Comment("原始名称:oper_cost,描述:减:营业成本")
|
||||
private Double operatingCost;
|
||||
@Comment("原始名称:sell_exp,描述:减:销售费用")
|
||||
private Double sellingExpense;
|
||||
@Comment("原始名称:admin_exp,描述:减:管理费用")
|
||||
private Double administrativeExpense;
|
||||
@Comment("原始名称:fin_exp,描述:减:财务费用")
|
||||
private Double financialExpense;
|
||||
@Comment("原始名称:oper_exp,描述:营业支出")
|
||||
private Double operatingExpense;
|
||||
@Comment("原始名称:operate_profit,描述:营业利润")
|
||||
private Double operatingProfit;
|
||||
@Comment("原始名称:non_oper_income,描述:加:营业外收入")
|
||||
private Double addNonOperatingIncome;
|
||||
@Comment("原始名称:non_oper_exp,描述:减:营业外支出")
|
||||
private Double lessNonOperatingExpense;
|
||||
@Comment("原始名称:total_profit,描述:利润总额")
|
||||
private Double totalProfit;
|
||||
@Comment("原始名称:income_tax,描述:所得税费用")
|
||||
private Double incomeTaxExpense;
|
||||
@Comment("原始名称:n_income,描述:净利润(含少数股东损益)")
|
||||
private Double netProfitIncludingMinorityInterest;
|
||||
@Comment("原始名称:n_income_attr_p,描述:净利润(不含少数股东损益)")
|
||||
private Double netProfitExcludingMinorityInterest;
|
||||
@Comment("原始名称:compr_inc_attr_p,描述:归属于母公司(或股东)的综合收益总额")
|
||||
private Double comprehensiveIncomeAttributableToParent;
|
||||
@Comment("原始名称:compr_inc_attr_m_s,描述:归属于少数股东的综合收益总额")
|
||||
private Double comprehensiveIncomeAttributableToMinorityShareholders;
|
||||
@Comment("原始名称:ebit,描述:息税前利润")
|
||||
private Double earningsBeforeInterestAndTax;
|
||||
@Comment("原始名称:undist_profit,描述:年初未分配利润")
|
||||
private Double beginningUndistributedProfit;
|
||||
@Comment("原始名称:distable_profit,描述:可分配利润")
|
||||
private Double distributableProfit;
|
||||
@Comment("原始名称:rd_exp,描述:研发费用")
|
||||
private Double researchAndDevelopmentExpense;
|
||||
@Comment("原始名称:fin_exp_int_exp,描述:财务费用-利息费用")
|
||||
private Double financialExpenseInterestExpense;
|
||||
@Comment("原始名称:continued_net_profit,描述:持续经营净利润")
|
||||
private Double netProfitFromContinuingOperations;
|
||||
@Comment("原始名称:end_net_profit,描述:终止经营净利润")
|
||||
private Double netProfitFromDiscontinuedOperations;
|
||||
}
|
||||
@@ -63,15 +63,20 @@ public class Stock extends SimpleEntity {
|
||||
|
||||
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
|
||||
@ToString.Exclude
|
||||
private Set<Income> incomes;
|
||||
private Set<FinanceIndicator> indicators;
|
||||
|
||||
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
|
||||
@ToString.Exclude
|
||||
private Set<BalanceSheet> balanceSheets;
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
|
||||
@ToString.Exclude
|
||||
private Set<CashFlow> cashFlows;
|
||||
Stock stock = (Stock) o;
|
||||
return code.equals(stock.code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return code.hashCode();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -2,10 +2,11 @@ package com.lanyuanxiaoyao.leopard.core.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.Set;
|
||||
import lombok.Getter;
|
||||
@@ -31,7 +32,7 @@ public class StockCollection extends SimpleEntity {
|
||||
@Column(nullable = false)
|
||||
private String description;
|
||||
|
||||
@ManyToMany
|
||||
@OneToMany(cascade = CascadeType.ALL)
|
||||
@ToString.Exclude
|
||||
private Set<Stock> stocks;
|
||||
private Set<StockScore> scores;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package com.lanyuanxiaoyao.leopard.core.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
@@ -22,18 +25,14 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
@DynamicUpdate
|
||||
@DynamicInsert
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = Constants.DATABASE_PREFIX + "task_template")
|
||||
public class TaskTemplate extends SimpleEntity {
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
@Column(nullable = false, length = 500)
|
||||
private String description;
|
||||
@Column(nullable = false)
|
||||
private String application;
|
||||
@Column(nullable = false)
|
||||
private String chain;
|
||||
@Column(nullable = false)
|
||||
private String expression;
|
||||
@Column(nullable = false)
|
||||
private String expressionEl;
|
||||
}
|
||||
@Table(name = Constants.DATABASE_PREFIX + "stock_score")
|
||||
public class StockScore extends SimpleEntity {
|
||||
@ManyToOne
|
||||
private Stock stock;
|
||||
@ManyToOne
|
||||
private StockCollection collection;
|
||||
@ElementCollection
|
||||
@JoinTable(name = Constants.DATABASE_PREFIX + "stock_score_extra")
|
||||
private Map<String, String> extra;
|
||||
private Double score;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class Task extends SimpleEntity {
|
||||
private Status status = Status.RUNNING;
|
||||
@Column(nullable = false)
|
||||
@Comment("任务进度")
|
||||
private Integer step = 0;
|
||||
private Double step = 0.0;
|
||||
@Comment("任务开始时间")
|
||||
private LocalDateTime launchedTime;
|
||||
@Comment("任务结束时间")
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record DailyDouble(LocalDate date, Double value) {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record Monthly(
|
||||
LocalDate tradeDate,
|
||||
int year,
|
||||
int month,
|
||||
Double open,
|
||||
Double high,
|
||||
Double low,
|
||||
Double close,
|
||||
Double priceChangeAmount,
|
||||
Double priceFluctuationRange,
|
||||
Double volume,
|
||||
Double turnover
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record Weekly(
|
||||
LocalDate tradeDate,
|
||||
int year,
|
||||
int week,
|
||||
Double open,
|
||||
Double high,
|
||||
Double low,
|
||||
Double close,
|
||||
Double priceChangeAmount,
|
||||
Double priceFluctuationRange,
|
||||
Double volume,
|
||||
Double turnover
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity.dto;
|
||||
|
||||
public record YearAndMonth(int year, int month) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity.dto;
|
||||
|
||||
public record YearAndWeek(int year, int week) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.entity.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record Yearly(
|
||||
LocalDate tradeDate,
|
||||
int year,
|
||||
Double open,
|
||||
Double high,
|
||||
Double low,
|
||||
Double close,
|
||||
Double priceChangeAmount,
|
||||
Double priceFluctuationRange,
|
||||
Double volume,
|
||||
Double turnover
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.helper;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250912
|
||||
*/
|
||||
public class NumberHelper {
|
||||
public static final String FINANCE_NULL_DOUBLE = "/";
|
||||
|
||||
public static String formatFinanceDouble(Double value) {
|
||||
if (ObjectUtil.isNull(value)) {
|
||||
return FINANCE_NULL_DOUBLE;
|
||||
}
|
||||
var result = FINANCE_NULL_DOUBLE;
|
||||
var absValue = Double.valueOf(Math.abs(value));
|
||||
if (absValue > 100000000) {
|
||||
result = NumberUtil.decimalFormat("#.##亿", absValue / 100000000);
|
||||
} else if (value > 10000) {
|
||||
result = NumberUtil.decimalFormat("#.##万", absValue / 10000);
|
||||
} else {
|
||||
result = NumberUtil.decimalFormat("#.##", absValue);
|
||||
}
|
||||
return value < 0 ? "-" + result : result;
|
||||
}
|
||||
|
||||
public static String formatDaysDouble(Double value) {
|
||||
if (ObjectUtil.isNull(value)) {
|
||||
return FINANCE_NULL_DOUBLE;
|
||||
}
|
||||
return NumberUtil.decimalFormat("#", value);
|
||||
}
|
||||
|
||||
public static String formatPercentageDouble(Double value) {
|
||||
if (ObjectUtil.isNull(value)) {
|
||||
return null;
|
||||
}
|
||||
return NumberUtil.decimalFormat("0.00%", value);
|
||||
}
|
||||
|
||||
public static String formatPriceDouble(Double value) {
|
||||
if (ObjectUtil.isNull(value)) {
|
||||
return null;
|
||||
}
|
||||
return NumberUtil.decimalFormat("0.00", value);
|
||||
}
|
||||
|
||||
public static String formatPriceDouble(Integer value) {
|
||||
if (ObjectUtil.isNull(value)) {
|
||||
return null;
|
||||
}
|
||||
return NumberUtil.decimalFormat("0.00", value);
|
||||
}
|
||||
|
||||
public static Double parseDouble(String value) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
return Double.parseDouble(value);
|
||||
}
|
||||
|
||||
public static Double parseDouble(String value, Function<Double, Double> ifSuccess) {
|
||||
var result = parseDouble(value);
|
||||
return ObjectUtil.isNull(result) ? null : ifSuccess.apply(result);
|
||||
}
|
||||
|
||||
public static Double safePlus(Double a, Double b) {
|
||||
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b)) {
|
||||
return null;
|
||||
}
|
||||
return a + b;
|
||||
}
|
||||
|
||||
public static Double safeMinus(Double a, Double b) {
|
||||
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b)) {
|
||||
return null;
|
||||
}
|
||||
return a - b;
|
||||
}
|
||||
|
||||
public static Double safeDiv(Double a, Double b) {
|
||||
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b) || b == 0) {
|
||||
return null;
|
||||
}
|
||||
return NumberUtil.div(a, b, 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.helper;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import org.ta4j.core.Bar;
|
||||
import org.ta4j.core.BaseBar;
|
||||
import org.ta4j.core.BaseBarSeries;
|
||||
import org.ta4j.core.indicators.SMAIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
|
||||
public class TaHelper {
|
||||
public static <T> List<Double> sma(List<T> data, int period, Function<T, Double> closeFunction) {
|
||||
var series = new BaseBarSeries();
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
var price = closeFunction.apply(data.get(i));
|
||||
Bar bar = new BaseBar(
|
||||
Duration.ofDays(1),
|
||||
ZonedDateTime.now().plusDays(i),
|
||||
price,
|
||||
price,
|
||||
price,
|
||||
price,
|
||||
0
|
||||
);
|
||||
series.addBar(bar);
|
||||
}
|
||||
var sma = new SMAIndicator(new ClosePriceIndicator(series), period);
|
||||
var result = new ArrayList<Double>(series.getBarCount());
|
||||
for (int i = 0; i < series.getBarCount(); i++) {
|
||||
result.add(sma.getValue(i).doubleValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Double maxFromDaily(List<Daily> dailies, Function<Daily, Double> function) {
|
||||
return dailies.stream()
|
||||
.map(function)
|
||||
.filter(ObjectUtil::isNotNull)
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.max()
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
public static Double minFromDaily(List<Daily> dailies, Function<Daily, Double> function) {
|
||||
return dailies.stream()
|
||||
.map(function)
|
||||
.filter(ObjectUtil::isNotNull)
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.min()
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
public static Double sumFromDaily(List<Daily> dailies, Function<Daily, Double> function) {
|
||||
return dailies.stream()
|
||||
.map(function)
|
||||
.filter(ObjectUtil::isNotNull)
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.sum();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250911
|
||||
*/
|
||||
@Repository
|
||||
public interface BalanceSheetRepository extends SimpleRepository<BalanceSheet> {
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250911
|
||||
*/
|
||||
@Repository
|
||||
public interface CashFlowRepository extends SimpleRepository<CashFlow> {
|
||||
}
|
||||
@@ -2,16 +2,39 @@ package com.lanyuanxiaoyao.leopard.core.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface DailyRepository extends SimpleRepository<Daily> {
|
||||
@Query("select distinct daily.tradeDate from Daily daily")
|
||||
List<LocalDate> findDistinctTradeDate();
|
||||
Set<LocalDate> findDistinctTradeDate();
|
||||
|
||||
@Query("select distinct daily.tradeDate from Daily daily where daily.stock.id = ?1")
|
||||
List<LocalDate> findDistinctTradeDateByStockId(Long stockId);
|
||||
@Query("select max(daily.tradeDate) from Daily daily")
|
||||
LocalDate findMaxTradeDate();
|
||||
|
||||
@Query("select min(daily.tradeDate) from Daily daily")
|
||||
LocalDate findMinTradeDate();
|
||||
|
||||
@Query("from Daily daily where daily.stock.id = ?1 order by daily.tradeDate desc limit 1")
|
||||
Optional<Daily> findLatest(Long stockId);
|
||||
|
||||
@EntityGraph(attributePaths = {"stock"})
|
||||
@Override
|
||||
Optional<Daily> findOne(Predicate predicate);
|
||||
|
||||
@EntityGraph(attributePaths = {"stock"})
|
||||
@Override
|
||||
List<Daily> findAll(Predicate predicate, OrderSpecifier<?>... orders);
|
||||
|
||||
@EntityGraph(attributePaths = {"stock"})
|
||||
@Query("from Daily daily where daily.stock.id = ?1 and daily.tradeDate <= current date order by daily.tradeDate desc limit ?2")
|
||||
List<Daily> findRecent(Long stockId, int days);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface FinanceIndicatorRepository extends SimpleRepository<FinanceIndicator> {
|
||||
@EntityGraph(attributePaths = {"stock"})
|
||||
@Override
|
||||
Optional<FinanceIndicator> findOne(Predicate predicate);
|
||||
|
||||
@EntityGraph(attributePaths = {"stock"})
|
||||
@Override
|
||||
List<FinanceIndicator> findAll(Predicate predicate, Sort sort);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Income;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250911
|
||||
*/
|
||||
@Repository
|
||||
public interface IncomeRepository extends SimpleRepository<Income> {
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package com.lanyuanxiaoyao.leopard.core.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import java.util.List;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -13,5 +16,12 @@ import org.springframework.stereotype.Repository;
|
||||
@Repository
|
||||
public interface StockRepository extends SimpleRepository<Stock> {
|
||||
@Query("select distinct stock.industry from Stock stock where stock.industry is not null")
|
||||
List<String> findDistinctIndustries();
|
||||
Set<String> findDistinctIndustries();
|
||||
|
||||
@Query("select distinct stock.code from Stock stock")
|
||||
Set<String> findDistinctCodes();
|
||||
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@Modifying
|
||||
void deleteAllByCodeIn(Collection<String> code);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
@@ -13,10 +14,15 @@ import org.springframework.stereotype.Repository;
|
||||
@Repository
|
||||
public interface TaskRepository extends SimpleRepository<Task> {
|
||||
@Modifying
|
||||
@Query("update Task task set task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.FAILURE where task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.RUNNING")
|
||||
@Query("""
|
||||
update Task task set
|
||||
task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.FAILURE,
|
||||
task.finishedTime = current timestamp
|
||||
where task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.RUNNING""")
|
||||
void updateAllRunningTaskToFailure();
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
@Modifying
|
||||
@Query("update Task task set task.step = ?1 where task.id = ?2")
|
||||
void updateStepById(Integer step, Long id);
|
||||
@Query("update Task task set task.step = ?2 where task.id = ?1")
|
||||
void updateStepById(Long id, Double step);
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.TaskTemplate;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface TaskTemplateRepository extends SimpleRepository<TaskTemplate> {
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.service;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 股票评估
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250924
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AssessmentService {
|
||||
private final IndustryService industryService;
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
public AssessmentService(IndustryService industryService, DailyRepository dailyRepository) {
|
||||
this.industryService = industryService;
|
||||
this.dailyRepository = dailyRepository;
|
||||
}
|
||||
|
||||
public Set<Result> assess(Set<Stock> stocks, int year) {
|
||||
if (ObjectUtil.isNotEmpty(stocks)) {
|
||||
var topChange = industryService.topChange(year, stocks);
|
||||
var dailyMap = dailyRepository.findAll(
|
||||
QDaily.daily.tradeDate.year().eq(year)
|
||||
.and(QDaily.daily.stock.in(stocks))
|
||||
)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(Daily::getStock));
|
||||
return stocks
|
||||
.stream()
|
||||
.filter(stock -> {
|
||||
if (!dailyMap.containsKey(stock) || ObjectUtil.isEmpty(dailyMap.get(stock))) {
|
||||
log.warn("Cannot find daily data in {} for {}", year, stock.getCode());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(stock -> {
|
||||
var dailies = dailyMap.get(stock)
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(Daily::getTradeDate))
|
||||
.toList();
|
||||
var change = getChange(dailies);
|
||||
var std = getStd(dailies);
|
||||
var industryTop = topChange.getOrDefault(new IndustryService.IndustryYearlyKey(stock.getIndustry(), year), 0.0);
|
||||
return new Result(stock, change, std, industryTop);
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
private double getChange(List<Daily> dailies) {
|
||||
return (dailies.getLast().getHfqClose() - dailies.getFirst().getHfqClose()) / dailies.getFirst().getHfqClose();
|
||||
}
|
||||
|
||||
private double getStd(List<Daily> dailies) {
|
||||
var statistics = new DescriptiveStatistics();
|
||||
dailies.forEach(daily -> statistics.addValue(daily.getHfqClose()));
|
||||
return statistics.getStandardDeviation();
|
||||
}
|
||||
|
||||
public record Result(Stock stock, double change, double std, double industryTop) {
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Result result = (Result) o;
|
||||
return stock.equals(result.stock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return stock.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.service;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 计算行业相关指标
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250924
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class IndustryService {
|
||||
private final StockRepository stockRepository;
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
public IndustryService(StockRepository stockRepository, DailyRepository dailyRepository) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.dailyRepository = dailyRepository;
|
||||
}
|
||||
|
||||
public Map<IndustryYearlyKey, Double> topChange(int year) {
|
||||
return topChange(year, null);
|
||||
}
|
||||
|
||||
public Map<IndustryYearlyKey, Double> topChange(int year, Set<Stock> includeStocks) {
|
||||
return topChange(year, year, includeStocks);
|
||||
}
|
||||
|
||||
public Map<IndustryYearlyKey, Double> topChange(int startYear, int endYear) {
|
||||
return topChange(startYear, endYear, null);
|
||||
}
|
||||
|
||||
public Map<IndustryYearlyKey, Double> topChange(int startYear, int endYear, Set<Stock> includeStocks) {
|
||||
var includeIndustries = ObjectUtil.isNull(includeStocks)
|
||||
? null
|
||||
: includeStocks.stream().map(Stock::getIndustry).collect(Collectors.toSet());
|
||||
return stockRepository.findDistinctIndustries()
|
||||
.parallelStream()
|
||||
.filter(o -> ObjectUtil.isNull(includeIndustries) || includeIndustries.contains(o))
|
||||
.flatMap(industry -> {
|
||||
var keys = new ArrayList<IndustryYearlyKey>();
|
||||
for (int year = startYear; year <= endYear; year++) {
|
||||
keys.add(new IndustryYearlyKey(industry, year));
|
||||
}
|
||||
return keys.stream();
|
||||
})
|
||||
.map(key -> {
|
||||
var maxChange = dailyRepository
|
||||
.findAll(
|
||||
QDaily.daily.stock.industry.eq(key.industry())
|
||||
.and(QDaily.daily.stock.in(includeStocks))
|
||||
.and(QDaily.daily.tradeDate.year().eq(key.year())),
|
||||
QDaily.daily.tradeDate.asc()
|
||||
)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(Daily::getStock))
|
||||
.values()
|
||||
.stream()
|
||||
.mapToDouble(dailies -> {
|
||||
var dailiesSorted = dailies
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(Daily::getTradeDate))
|
||||
.toList();
|
||||
return (dailiesSorted.getLast().getHfqClose() - dailiesSorted.getFirst().getHfqClose()) / dailiesSorted.getFirst().getHfqClose();
|
||||
})
|
||||
.max()
|
||||
.orElse(0.0);
|
||||
return Map.entry(key, maxChange);
|
||||
})
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
public record IndustryYearlyKey(String industry, int year) {
|
||||
}
|
||||
|
||||
public record IndustryYearlyData(
|
||||
String industry,
|
||||
int year,
|
||||
double maxChange,
|
||||
double minChange,
|
||||
double avgChange,
|
||||
double medianChange
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service;
|
||||
package com.lanyuanxiaoyao.leopard.core.service;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.StockCollection;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockCollectionRepository;
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.service;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QFinanceIndicator;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.Monthly;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.Weekly;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.YearAndMonth;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.YearAndWeek;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.Yearly;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.FinanceIndicatorRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import static com.lanyuanxiaoyao.leopard.core.helper.TaHelper.maxFromDaily;
|
||||
import static com.lanyuanxiaoyao.leopard.core.helper.TaHelper.minFromDaily;
|
||||
import static com.lanyuanxiaoyao.leopard.core.helper.TaHelper.sumFromDaily;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250828
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class StockService extends SimpleServiceSupport<Stock> {
|
||||
private final FinanceIndicatorRepository financeIndicatorRepository;
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
public StockService(StockRepository repository, FinanceIndicatorRepository financeIndicatorRepository, DailyRepository dailyRepository) {
|
||||
super(repository);
|
||||
this.financeIndicatorRepository = financeIndicatorRepository;
|
||||
this.dailyRepository = dailyRepository;
|
||||
}
|
||||
|
||||
@Cacheable(value = "findFinanceIndicator", cacheManager = "long-cache", sync = true)
|
||||
public Optional<FinanceIndicator> findFinanceIndicator(Long stockId, Integer year) {
|
||||
return financeIndicatorRepository.findOne(
|
||||
QFinanceIndicator.financeIndicator.year.eq(year)
|
||||
.and(QFinanceIndicator.financeIndicator.stock.id.eq(stockId))
|
||||
);
|
||||
}
|
||||
|
||||
@Cacheable(value = "findFinanceIndicatorRecent", cacheManager = "long-cache", sync = true)
|
||||
public List<FinanceIndicator> findFinanceIndicatorRecent(Long stockId, int years) {
|
||||
var current = LocalDate.now();
|
||||
return financeIndicatorRepository.findAll(
|
||||
QFinanceIndicator.financeIndicator.stock.id.eq(stockId)
|
||||
.and(QFinanceIndicator.financeIndicator.year.between(current.minusYears(years).getYear(), current.getYear())),
|
||||
QFinanceIndicator.financeIndicator.year.asc()
|
||||
);
|
||||
}
|
||||
|
||||
@Cacheable(value = "findDailyRecent", cacheManager = "long-cache", sync = true)
|
||||
public List<Daily> findDailyRecent(Long stockId, int days) {
|
||||
return dailyRepository.findRecent(stockId, days)
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(Daily::getTradeDate))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Cacheable(value = "findDailyLatest", cacheManager = "long-cache", sync = true)
|
||||
public Optional<Daily> findDailyLatest(Long stockId) {
|
||||
return dailyRepository.findLatest(stockId);
|
||||
}
|
||||
|
||||
@Cacheable(value = "findYearlyRecent", cacheManager = "long-cache", sync = true)
|
||||
public List<Yearly> findYearlyRecent(Long stockId, int years) {
|
||||
var current = LocalDate.now().withMonth(1).withDayOfMonth(1);
|
||||
var start = current.minusYears(years).getYear();
|
||||
var end = current.getYear();
|
||||
return dailyRepository
|
||||
.findAll(
|
||||
QDaily.daily.stock.id.eq(stockId)
|
||||
.and(QDaily.daily.tradeDate.year().gt(start))
|
||||
.and(QDaily.daily.tradeDate.year().loe(end)),
|
||||
QDaily.daily.tradeDate.asc()
|
||||
)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(daily -> daily.getTradeDate().getYear()))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> {
|
||||
var year = entry.getKey();
|
||||
var dailies = entry.getValue();
|
||||
var open = dailies.getFirst().getHfqOpen();
|
||||
var close = dailies.getLast().getHfqClose();
|
||||
return new Yearly(
|
||||
LocalDate.of(year, 1, 1),
|
||||
year,
|
||||
open,
|
||||
maxFromDaily(dailies, Daily::getHfqHigh),
|
||||
minFromDaily(dailies, Daily::getHfqLow),
|
||||
close,
|
||||
close - open,
|
||||
(close - open) / open * 100,
|
||||
sumFromDaily(dailies, Daily::getVolume),
|
||||
sumFromDaily(dailies, Daily::getTurnover)
|
||||
);
|
||||
})
|
||||
.sorted(Comparator.comparingInt(Yearly::year))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Cacheable(value = "findMonthlyRecent", cacheManager = "long-cache", sync = true)
|
||||
public List<Monthly> findMonthlyRecent(Long stockId, int months) {
|
||||
var end = LocalDate.now().withDayOfMonth(1);
|
||||
var start = end.minusMonths(months);
|
||||
return dailyRepository
|
||||
.findAll(
|
||||
QDaily.daily.stock.id.eq(stockId)
|
||||
.and(
|
||||
QDaily.daily.tradeDate.year().gt(start.getYear())
|
||||
.or(
|
||||
QDaily.daily.tradeDate.year().eq(start.getYear())
|
||||
.and(QDaily.daily.tradeDate.month().gt(start.getMonthValue()))
|
||||
)
|
||||
)
|
||||
.and(
|
||||
QDaily.daily.tradeDate.year().lt(end.getYear())
|
||||
.or(
|
||||
QDaily.daily.tradeDate.year().eq(end.getYear())
|
||||
.and(QDaily.daily.tradeDate.month().loe(end.getMonthValue()))
|
||||
)
|
||||
),
|
||||
QDaily.daily.tradeDate.asc()
|
||||
)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(daily -> new YearAndMonth(daily.getTradeDate().getYear(), daily.getTradeDate().getMonthValue())))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> {
|
||||
var yearAndMonth = entry.getKey();
|
||||
var dailies = entry.getValue();
|
||||
var open = dailies.getFirst().getHfqOpen();
|
||||
var close = dailies.getLast().getHfqClose();
|
||||
return new Monthly(
|
||||
LocalDate.of(yearAndMonth.year(), yearAndMonth.month(), 1),
|
||||
yearAndMonth.year(),
|
||||
yearAndMonth.month(),
|
||||
open,
|
||||
maxFromDaily(dailies, Daily::getHfqHigh),
|
||||
minFromDaily(dailies, Daily::getHfqLow),
|
||||
close,
|
||||
close - open,
|
||||
(close - open) / open * 100,
|
||||
sumFromDaily(dailies, Daily::getVolume),
|
||||
sumFromDaily(dailies, Daily::getTurnover)
|
||||
);
|
||||
})
|
||||
.sorted(Comparator.comparingInt(monthly -> monthly.year() * 100 + monthly.month()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Cacheable(value = "findWeeklyRecent", cacheManager = "long-cache", sync = true)
|
||||
public List<Weekly> findWeeklyRecent(Long stockId, int weeks) {
|
||||
var end = LocalDate.now().with(ChronoField.DAY_OF_WEEK, 1);
|
||||
var start = end.minusWeeks(weeks);
|
||||
return dailyRepository
|
||||
.findAll(
|
||||
QDaily.daily.stock.id.eq(stockId)
|
||||
.and(
|
||||
QDaily.daily.tradeDate.year().gt(start.getYear())
|
||||
.or(
|
||||
QDaily.daily.tradeDate.year().eq(start.getYear())
|
||||
.and(QDaily.daily.tradeDate.week().gt(start.get(WeekFields.ISO.weekOfYear())))
|
||||
)
|
||||
)
|
||||
.and(
|
||||
QDaily.daily.tradeDate.year().lt(end.getYear())
|
||||
.or(
|
||||
QDaily.daily.tradeDate.year().eq(end.getYear())
|
||||
.and(QDaily.daily.tradeDate.month().loe(end.get(WeekFields.ISO.weekOfYear())))
|
||||
)
|
||||
),
|
||||
QDaily.daily.tradeDate.asc()
|
||||
)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(daily -> new YearAndWeek(daily.getTradeDate().getYear(), daily.getTradeDate().get(WeekFields.ISO.weekOfYear()))))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> {
|
||||
var yearAndWeek = entry.getKey();
|
||||
var dailies = entry.getValue();
|
||||
var open = dailies.getFirst().getHfqOpen();
|
||||
var close = dailies.getLast().getHfqClose();
|
||||
return new Weekly(
|
||||
LocalDate.of(yearAndWeek.year(), 1, 1).with(WeekFields.ISO.weekOfYear(), yearAndWeek.week()),
|
||||
yearAndWeek.year(),
|
||||
yearAndWeek.week(),
|
||||
open,
|
||||
maxFromDaily(dailies, Daily::getHfqHigh),
|
||||
minFromDaily(dailies, Daily::getHfqLow),
|
||||
close,
|
||||
close - open,
|
||||
(close - open) / open * 100,
|
||||
sumFromDaily(dailies, Daily::getVolume),
|
||||
sumFromDaily(dailies, Daily::getTurnover)
|
||||
);
|
||||
})
|
||||
.sorted(Comparator.comparingInt(weekly -> weekly.year() * 100 + weekly.week()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.service;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Task;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.TaskRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.task.PyramidSelect;
|
||||
import com.lanyuanxiaoyao.leopard.core.task.TaskRunner;
|
||||
import com.lanyuanxiaoyao.leopard.core.task.UpdateDailyTask;
|
||||
import com.lanyuanxiaoyao.leopard.core.task.UpdateFinanceIndicatorTask;
|
||||
import com.lanyuanxiaoyao.leopard.core.task.UpdateStockTask;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250829
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TaskService extends SimpleServiceSupport<Task> {
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(50);
|
||||
private final ApplicationContext context;
|
||||
|
||||
@Getter
|
||||
private final Set<TaskTemplate> templates = Stream.of(
|
||||
new TaskTemplate("b29f76a5-b07d-4182-85f8-2641c2a975c1", "更新股票信息", "更新股票信息", UpdateStockTask.class),
|
||||
new TaskTemplate("b9df25ce-aa55-4f73-8265-d8a724614177", "更新日线数据", "更新日线数据", UpdateDailyTask.class),
|
||||
new TaskTemplate("8ab30478-c81f-4bbf-94dd-7e05fa537b50", "更新财务指标", "更新财务指标", UpdateFinanceIndicatorTask.class),
|
||||
new TaskTemplate("a6a7b569-a171-481b-9184-716925571639", "金字塔选股", "金字塔选股", PyramidSelect.class)
|
||||
).collect(Collectors.toSet());
|
||||
private final Map<String, TaskTemplate> templateMap = templates.stream()
|
||||
.collect(Collectors.toMap(TaskTemplate::id, template -> template));
|
||||
|
||||
public TaskService(TaskRepository repository, ApplicationContext context) {
|
||||
super(repository);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public TaskTemplate getTemplate(String templateId) {
|
||||
return templateMap.get(templateId);
|
||||
}
|
||||
|
||||
public void execute(String templateId, Map<String, Object> params) {
|
||||
execute(templateId, params, true);
|
||||
}
|
||||
|
||||
public void execute(String templateId, Map<String, Object> params, boolean async) {
|
||||
var template = templateMap.get(templateId);
|
||||
if (ObjectUtil.isNull(template)) {
|
||||
throw new RuntimeException("任务模板不存在");
|
||||
}
|
||||
var instance = context.getBean(template.runnerClass());
|
||||
if (async) {
|
||||
executor.submit(() -> instance.run(template, params));
|
||||
} else {
|
||||
instance.run(template, params);
|
||||
}
|
||||
}
|
||||
|
||||
public record TaskTemplate(
|
||||
String id,
|
||||
String name,
|
||||
String description,
|
||||
Class<? extends TaskRunner> runnerClass
|
||||
) {
|
||||
public TaskTemplate(String name, String description, Class<? extends TaskRunner> runnerClass) {
|
||||
this(IdUtil.fastUUID(), name, description, runnerClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service;
|
||||
package com.lanyuanxiaoyao.leopard.core.service;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -95,9 +97,19 @@ public class TuShareService {
|
||||
|
||||
@SneakyThrows
|
||||
public TuShareResponse factorList(LocalDate tradeDate) {
|
||||
return factorList(tradeDate, null);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public TuShareResponse factorList(LocalDate tradeDate, String stockCode) {
|
||||
var paramsMap = new HashMap<String, Object>();
|
||||
paramsMap.put("trade_date", tradeDate.format(TRADE_FORMAT));
|
||||
if (StrUtil.isNotBlank(stockCode)) {
|
||||
paramsMap.put("ts_code", stockCode);
|
||||
}
|
||||
var response = HttpUtil.post(API_URL, buildRequest(
|
||||
"adj_factor",
|
||||
Map.of("trade_date", tradeDate.format(TRADE_FORMAT)),
|
||||
paramsMap,
|
||||
List.of("ts_code", "trade_date", "adj_factor")
|
||||
));
|
||||
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
|
||||
@@ -107,121 +119,22 @@ public class TuShareService {
|
||||
return tuShareResponse;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public TuShareResponse incomeList(int year) {
|
||||
var response = HttpUtil.post(API_URL, buildRequest(
|
||||
"income_vip",
|
||||
Map.of("period", LocalDate.of(year, 12, 31).format(TRADE_FORMAT)),
|
||||
List.of(
|
||||
"ts_code",
|
||||
"basic_eps",
|
||||
"diluted_eps",
|
||||
"total_revenue",
|
||||
"revenue",
|
||||
"total_cogs",
|
||||
"oper_cost",
|
||||
"sell_exp",
|
||||
"admin_exp",
|
||||
"fin_exp",
|
||||
"oper_exp",
|
||||
"operate_profit",
|
||||
"non_oper_income",
|
||||
"non_oper_exp",
|
||||
"total_profit",
|
||||
"income_tax",
|
||||
"n_income",
|
||||
"n_income_attr_p",
|
||||
"compr_inc_attr_p",
|
||||
"compr_inc_attr_m_s",
|
||||
"ebit",
|
||||
"undist_profit",
|
||||
"distable_profit",
|
||||
"rd_exp",
|
||||
"fin_exp_int_exp",
|
||||
"continued_net_profit",
|
||||
"end_net_profit"
|
||||
)
|
||||
));
|
||||
public List<Map<String, String>> request(String api, Map<String, Object> params, List<String> fields) throws JsonProcessingException {
|
||||
var response = HttpUtil.post(API_URL, buildRequest(api, params, fields));
|
||||
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
|
||||
if (tuShareResponse.code != 0) {
|
||||
throw new RuntimeException(tuShareResponse.message);
|
||||
}
|
||||
return tuShareResponse;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public TuShareResponse balanceList(int year) {
|
||||
var response = HttpUtil.post(API_URL, buildRequest(
|
||||
"balancesheet_vip",
|
||||
Map.of("period", LocalDate.of(year, 12, 31).format(TRADE_FORMAT)),
|
||||
List.of(
|
||||
"ts_code",
|
||||
"total_share",
|
||||
"cap_rese",
|
||||
"undistr_porfit",
|
||||
"money_cap",
|
||||
"accounts_receiv",
|
||||
"inventories",
|
||||
"total_cur_assets",
|
||||
"lt_eqt_invest",
|
||||
"lt_rec",
|
||||
"fix_assets",
|
||||
"r_and_d",
|
||||
"goodwill",
|
||||
"total_nca",
|
||||
"total_assets",
|
||||
"lt_borr",
|
||||
"st_borr",
|
||||
"acct_payable",
|
||||
"adv_receipts",
|
||||
"total_cur_liab",
|
||||
"total_ncl",
|
||||
"total_liab",
|
||||
"total_hldr_eqy_exc_min_int",
|
||||
"total_hldr_eqy_inc_min_int",
|
||||
"total_liab_hldr_eqy",
|
||||
"acc_receivable",
|
||||
"payables",
|
||||
"accounts_receiv_bill",
|
||||
"accounts_pay",
|
||||
"oth_rcv_total",
|
||||
"fix_assets_total"
|
||||
)
|
||||
));
|
||||
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
|
||||
if (tuShareResponse.code != 0) {
|
||||
throw new RuntimeException(tuShareResponse.message);
|
||||
var data = tuShareResponse.data;
|
||||
var result = new ArrayList<Map<String, String>>();
|
||||
for (var item : data.items) {
|
||||
var map = new HashMap<String, String>();
|
||||
for (int i = 0; i < data.fields.size(); i++) {
|
||||
map.put(data.fields.get(i), item.get(i));
|
||||
}
|
||||
result.add(map);
|
||||
}
|
||||
return tuShareResponse;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public TuShareResponse cashFlowList(int year) {
|
||||
var response = HttpUtil.post(API_URL, buildRequest(
|
||||
"cashflow_vip",
|
||||
Map.of("period", LocalDate.of(year, 12, 31).format(TRADE_FORMAT)),
|
||||
List.of(
|
||||
"ts_code",
|
||||
"net_profit",
|
||||
"finan_exp",
|
||||
"c_fr_sale_sg",
|
||||
"c_inf_fr_operate_a",
|
||||
"c_paid_to_for_empl",
|
||||
"c_paid_for_taxes",
|
||||
"n_cashflow_act",
|
||||
"stot_inflows_inv_act",
|
||||
"c_pay_acq_const_fiolta",
|
||||
"stot_out_inv_act",
|
||||
"stot_cashout_fnc_act",
|
||||
"c_cash_equ_beg_period",
|
||||
"c_cash_equ_end_period"
|
||||
)
|
||||
));
|
||||
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
|
||||
if (tuShareResponse.code != 0) {
|
||||
throw new RuntimeException(tuShareResponse.message);
|
||||
}
|
||||
return tuShareResponse;
|
||||
return result;
|
||||
}
|
||||
|
||||
public record TuShareResponse(
|
||||
@@ -0,0 +1,243 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.service.selector;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QStock;
|
||||
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 金字塔选股
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250924
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PyramidStockSelector implements StockSelector<PyramidStockSelector.Request> {
|
||||
private final StockRepository stockRepository;
|
||||
|
||||
public PyramidStockSelector(StockRepository stockRepository) {
|
||||
this.stockRepository = stockRepository;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@Override
|
||||
public Set<Candidate> select(Request request) {
|
||||
// 选择至少有最近5年财报的股票
|
||||
// 有点奇怪,001400.SZ有近5年的财报但资料显示是2025年才上市的
|
||||
return stockRepository.findAll(QStock.stock.listedDate.before(LocalDate.of(request.year(), 1, 1)))
|
||||
.stream()
|
||||
.map(stock -> {
|
||||
var extra = new HashMap<String, String>();
|
||||
var score = 0;
|
||||
|
||||
var recentIndicators = stock.getIndicators()
|
||||
.stream()
|
||||
.filter(indicator -> indicator.getYear() < request.year())
|
||||
.sorted((a, b) -> b.getYear() - a.getYear())
|
||||
.limit(5)
|
||||
.toList();
|
||||
if (recentIndicators.size() < 5) {
|
||||
return null;
|
||||
}
|
||||
var latestIndicator = recentIndicators.getFirst();
|
||||
|
||||
var roeScore = 0;
|
||||
if (recentIndicators.stream().noneMatch(indicator -> indicator.getReturnOnEquity() == null || indicator.getReturnOnEquity() < 0)) {
|
||||
var averageRoe = recentIndicators.stream()
|
||||
.map(FinanceIndicator::getReturnOnEquity)
|
||||
.map(item -> ObjectUtil.defaultIfNull(item, 0.0))
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
if (averageRoe >= 35) {
|
||||
roeScore = 550;
|
||||
} else if (averageRoe >= 30) {
|
||||
roeScore = 500;
|
||||
} else if (averageRoe >= 25) {
|
||||
roeScore = 450;
|
||||
} else if (averageRoe >= 20) {
|
||||
roeScore = 400;
|
||||
} else if (averageRoe >= 15) {
|
||||
roeScore = 350;
|
||||
} else if (averageRoe >= 10) {
|
||||
roeScore = 300;
|
||||
}
|
||||
extra.put("平均ROE", NumberHelper.formatPriceDouble(averageRoe));
|
||||
}
|
||||
extra.put("平均ROE得分", NumberHelper.formatPriceDouble(roeScore));
|
||||
score += roeScore;
|
||||
|
||||
var roaScore = 0;
|
||||
if (recentIndicators.stream().noneMatch(indicator -> indicator.getReturnOnAssets() == null)) {
|
||||
var averageRoa = recentIndicators.stream()
|
||||
.map(FinanceIndicator::getReturnOnAssets)
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
if (averageRoa >= 15) {
|
||||
roaScore = 100;
|
||||
} else if (averageRoa >= 11) {
|
||||
roaScore = 80;
|
||||
} else if (averageRoa >= 7) {
|
||||
roaScore = 50;
|
||||
}
|
||||
extra.put("平均ROA", NumberHelper.formatPriceDouble(averageRoa));
|
||||
}
|
||||
extra.put("平均ROA得分", NumberHelper.formatPriceDouble(roaScore));
|
||||
score += roaScore;
|
||||
|
||||
var netProfitScore = 0;
|
||||
if (recentIndicators.stream().noneMatch(indicator -> indicator.getNetProfit() == null)) {
|
||||
var averageNetProfit = recentIndicators.stream()
|
||||
.map(FinanceIndicator::getNetProfit)
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
if (averageNetProfit >= 10000.0 * 10000000) {
|
||||
netProfitScore = 150;
|
||||
} else if (averageNetProfit >= 1000.0 * 10000000) {
|
||||
netProfitScore = 100;
|
||||
}
|
||||
extra.put("平均净利润", NumberHelper.formatPriceDouble(averageNetProfit));
|
||||
}
|
||||
extra.put("平均净利润得分", NumberHelper.formatPriceDouble(netProfitScore));
|
||||
score += netProfitScore;
|
||||
|
||||
var cashScore = 0;
|
||||
if (
|
||||
ArrayUtil.isAllNotNull(latestIndicator.getTotalAssetsTurnover(), latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio())
|
||||
&& (
|
||||
latestIndicator.getTotalAssetsTurnover() > 0.8 && latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio() >= 0.1
|
||||
|| latestIndicator.getTotalAssetsTurnover() <= 0.8 && latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio() >= 0.2
|
||||
)
|
||||
) {
|
||||
cashScore = 50;
|
||||
}
|
||||
extra.put("现金流得分", NumberHelper.formatPriceDouble(cashScore));
|
||||
score += cashScore;
|
||||
|
||||
if (ObjectUtil.isNotNull(latestIndicator.getDaysAccountsReceivableTurnover()) && latestIndicator.getDaysAccountsReceivableTurnover() <= 30) {
|
||||
extra.put("应收账款周转天数得分", "20");
|
||||
score += 20;
|
||||
}
|
||||
if (ObjectUtil.isNotNull(latestIndicator.getDaysInventoryTurnover()) && latestIndicator.getDaysInventoryTurnover() <= 30) {
|
||||
extra.put("存货周转天数得分", "20");
|
||||
score += 20;
|
||||
}
|
||||
if (ArrayUtil.isAllNotNull(latestIndicator.getDaysAccountsReceivableTurnover(), latestIndicator.getDaysInventoryTurnover())) {
|
||||
if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 40) {
|
||||
score += 20;
|
||||
} else if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 60) {
|
||||
score += 10;
|
||||
}
|
||||
}
|
||||
|
||||
if (recentIndicators.stream().noneMatch(indicator -> indicator.getOperatingGrossProfitMargin() == null)) {
|
||||
var stat = new DescriptiveStatistics();
|
||||
recentIndicators.stream()
|
||||
.map(FinanceIndicator::getOperatingGrossProfitMargin)
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.forEach(stat::addValue);
|
||||
if (stat.getStandardDeviation() <= 0.3) {
|
||||
extra.put("毛利率标准差得分", "50");
|
||||
score += 50;
|
||||
}
|
||||
}
|
||||
|
||||
var operatingSafeMarginScore = 0;
|
||||
if (ObjectUtil.isNotNull(latestIndicator.getOperatingSafetyMarginRatio())) {
|
||||
if (latestIndicator.getOperatingSafetyMarginRatio() >= 70) {
|
||||
operatingSafeMarginScore = 50;
|
||||
} else if (latestIndicator.getOperatingSafetyMarginRatio() >= 50) {
|
||||
operatingSafeMarginScore = 30;
|
||||
} else if (latestIndicator.getOperatingSafetyMarginRatio() >= 30) {
|
||||
operatingSafeMarginScore = 10;
|
||||
}
|
||||
extra.put("安全边际比率", NumberHelper.formatPriceDouble(latestIndicator.getOperatingSafetyMarginRatio()));
|
||||
}
|
||||
extra.put("安全边际比率得分", NumberHelper.formatPriceDouble(operatingSafeMarginScore));
|
||||
score += operatingSafeMarginScore;
|
||||
|
||||
var netProfitAscendingScore = 0;
|
||||
if (recentIndicators.stream().noneMatch(indicator -> indicator.getNetProfit() == null)) {
|
||||
if (recentIndicators.get(0).getNetProfit() > recentIndicators.get(1).getNetProfit()) {
|
||||
netProfitAscendingScore += 30;
|
||||
} else {
|
||||
netProfitAscendingScore -= 30;
|
||||
}
|
||||
if (recentIndicators.get(1).getNetProfit() > recentIndicators.get(2).getNetProfit()) {
|
||||
netProfitAscendingScore += 25;
|
||||
} else {
|
||||
netProfitAscendingScore -= 25;
|
||||
}
|
||||
|
||||
if (recentIndicators.get(2).getNetProfit() > recentIndicators.get(3).getNetProfit()) {
|
||||
netProfitAscendingScore += 20;
|
||||
} else {
|
||||
netProfitAscendingScore -= 20;
|
||||
}
|
||||
|
||||
if (recentIndicators.get(3).getNetProfit() > recentIndicators.get(4).getNetProfit()) {
|
||||
netProfitAscendingScore += 15;
|
||||
} else {
|
||||
netProfitAscendingScore -= 15;
|
||||
}
|
||||
}
|
||||
extra.put("近五年净利润得分", NumberHelper.formatPriceDouble(netProfitAscendingScore));
|
||||
score += netProfitAscendingScore;
|
||||
|
||||
var cashAscendingScore = 0;
|
||||
if (recentIndicators.stream().noneMatch(indicator -> indicator.getCashAndCashEquivalents() == null)) {
|
||||
if (recentIndicators.get(0).getCashAndCashEquivalents() > recentIndicators.get(1).getCashAndCashEquivalents()) {
|
||||
cashAscendingScore += 30;
|
||||
} else {
|
||||
cashAscendingScore -= 30;
|
||||
}
|
||||
|
||||
if (recentIndicators.get(1).getCashAndCashEquivalents() > recentIndicators.get(2).getCashAndCashEquivalents()) {
|
||||
cashAscendingScore += 25;
|
||||
} else {
|
||||
cashAscendingScore -= 25;
|
||||
}
|
||||
|
||||
if (recentIndicators.get(2).getCashAndCashEquivalents() > recentIndicators.get(3).getCashAndCashEquivalents()) {
|
||||
cashAscendingScore += 20;
|
||||
} else {
|
||||
cashAscendingScore -= 20;
|
||||
}
|
||||
|
||||
if (recentIndicators.get(3).getCashAndCashEquivalents() > recentIndicators.get(4).getCashAndCashEquivalents()) {
|
||||
cashAscendingScore += 15;
|
||||
} else {
|
||||
cashAscendingScore -= 15;
|
||||
}
|
||||
}
|
||||
extra.put("近五年现金流得分", NumberHelper.formatPriceDouble(cashAscendingScore));
|
||||
score += cashAscendingScore;
|
||||
|
||||
return new Candidate(stock, score, extra);
|
||||
})
|
||||
.filter(ObjectUtil::isNotNull)
|
||||
.sorted(Comparator.comparingDouble(Candidate::score).reversed())
|
||||
.limit(request.limit)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public record Request(int year, int limit) {
|
||||
public Request(int year) {
|
||||
this(year, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.service.selector;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 选股器
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250924
|
||||
*/
|
||||
public interface StockSelector<T> {
|
||||
Set<Candidate> select(T request);
|
||||
|
||||
record Candidate(Stock stock, double score, Map<String, String> extra) {
|
||||
public Candidate(Stock stock, double score) {
|
||||
this(stock, score, Map.of());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.strategy;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 交易策略
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20251016
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TradeEngine {
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
public TradeEngine(DailyRepository dailyRepository) {
|
||||
this.dailyRepository = dailyRepository;
|
||||
}
|
||||
|
||||
public Asset backtest(Stock stock, TradeStrategy strategy, LocalDate startDate, LocalDate endDate) {
|
||||
var dailies = dailyRepository.findAll(
|
||||
QDaily.daily.stock.eq(stock)
|
||||
.and(QDaily.daily.tradeDate.before(endDate)),
|
||||
QDaily.daily.tradeDate.asc()
|
||||
);
|
||||
var validTradeDates = dailies.stream()
|
||||
.map(Daily::getTradeDate)
|
||||
.distinct()
|
||||
.toList();
|
||||
var asset = new Asset();
|
||||
for (var now = startDate; now.isBefore(endDate) || now.isEqual(endDate); now = now.plusDays(1)) {
|
||||
if (!validTradeDates.contains(now)) {
|
||||
continue;
|
||||
}
|
||||
final var currentDate = now;
|
||||
var trade = strategy.trade(
|
||||
now,
|
||||
asset,
|
||||
dailies.stream()
|
||||
.filter(daily -> daily.getTradeDate().isBefore(currentDate))
|
||||
.toList()
|
||||
);
|
||||
var daily = dailies.stream()
|
||||
.filter(d -> ObjectUtil.equals(d.getTradeDate(), currentDate))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
asset.addTrade(now, trade, daily.getHfqClose());
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
|
||||
public interface TradeStrategy {
|
||||
int trade(LocalDate now, Asset asset, List<Daily> dailies);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static final class Asset {
|
||||
private List<Trade> trades = new ArrayList<>();
|
||||
|
||||
public void addTrade(LocalDate date, int volume, double price) {
|
||||
trades.add(new Trade(date, volume, price));
|
||||
}
|
||||
|
||||
public int getVolume() {
|
||||
return getVolume(trade -> true);
|
||||
}
|
||||
|
||||
public int getVolume(LocalDate date) {
|
||||
return getVolume(trade -> trade.date().isBefore(date) || trade.date().isEqual(date));
|
||||
}
|
||||
|
||||
private int getVolume(Predicate<Trade> predicate) {
|
||||
return trades.stream()
|
||||
.filter(predicate)
|
||||
.mapToInt(Trade::volume)
|
||||
.sum();
|
||||
}
|
||||
|
||||
public double getCash() {
|
||||
return getCash(trade -> true);
|
||||
}
|
||||
|
||||
public double getCash(LocalDate date) {
|
||||
return getCash(trade -> trade.date().isBefore(date) || trade.date().isEqual(date));
|
||||
}
|
||||
|
||||
private double getCash(Predicate<Trade> predicate) {
|
||||
return trades.stream()
|
||||
.filter(predicate)
|
||||
.mapToDouble(trade -> -1 * trade.volume() * trade.price())
|
||||
.sum();
|
||||
}
|
||||
|
||||
public double getPrice() {
|
||||
return getPrice(trade -> true);
|
||||
}
|
||||
|
||||
public double getPrice(LocalDate date) {
|
||||
return getPrice(trade -> trade.date().isBefore(date) || trade.date().isEqual(date));
|
||||
}
|
||||
|
||||
public double getPrice(Predicate<Trade> predicate) {
|
||||
int volume = getVolume(predicate);
|
||||
return volume == 0 ? 0 : getCash(predicate) / volume;
|
||||
}
|
||||
|
||||
public record Trade(
|
||||
LocalDate date,
|
||||
int volume,
|
||||
double price
|
||||
) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.task;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.StockCollection;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.StockScore;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockCollectionRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.selector.PyramidStockSelector;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 金字塔选股
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250925
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PyramidSelect extends TaskRunner {
|
||||
private final StockCollectionRepository stockCollectionRepository;
|
||||
|
||||
private final PyramidStockSelector pyramidStockSelector;
|
||||
|
||||
protected PyramidSelect(ApplicationContext context, StockCollectionRepository stockCollectionRepository, PyramidStockSelector pyramidStockSelector) {
|
||||
super(context);
|
||||
this.stockCollectionRepository = stockCollectionRepository;
|
||||
this.pyramidStockSelector = pyramidStockSelector;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
@Override
|
||||
public String process(Map<String, Object> params, StepUpdater updater) {
|
||||
var candidates = pyramidStockSelector.select(new PyramidStockSelector.Request(LocalDate.now().getYear(), 50));
|
||||
|
||||
var collection = new StockCollection();
|
||||
collection.setName("金字塔选股");
|
||||
collection.setDescription("金字塔选股");
|
||||
collection.setScores(
|
||||
candidates.stream()
|
||||
.map(candidate -> {
|
||||
var score = new StockScore();
|
||||
score.setStock(candidate.stock());
|
||||
score.setScore(candidate.score());
|
||||
score.setExtra(candidate.extra());
|
||||
score.setCollection(collection);
|
||||
return score;
|
||||
})
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
stockCollectionRepository.save(collection);
|
||||
|
||||
return """
|
||||
| Code | Name | Score |
|
||||
| ---- | ---- | ----- |
|
||||
%s
|
||||
""".formatted(candidates.stream()
|
||||
.map(candidate -> "| %s | %s | %.2f |".formatted(candidate.stock().getCode(), candidate.stock().getName(), candidate.score()))
|
||||
.collect(Collectors.joining("\n")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.task;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Task;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.TaskRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.TaskService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* 任务运行
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250924
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class TaskRunner {
|
||||
private final ApplicationContext context;
|
||||
|
||||
protected TaskRunner(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public abstract String process(Map<String, Object> params, StepUpdater updater) throws Exception;
|
||||
|
||||
public void run(TaskService.TaskTemplate template, Map<String, Object> params) {
|
||||
var taskRepository = context.getBean(TaskRepository.class);
|
||||
var task = new Task();
|
||||
task.setName(template.name());
|
||||
task.setDescription(template.description());
|
||||
task.setStatus(Task.Status.RUNNING);
|
||||
task.setLaunchedTime(LocalDateTime.now());
|
||||
taskRepository.saveAndFlush(task);
|
||||
|
||||
try {
|
||||
var result = process(params, step -> {
|
||||
synchronized (task) {
|
||||
taskRepository.updateStepById(task.getId(), step);
|
||||
}
|
||||
});
|
||||
|
||||
task.setStatus(Task.Status.SUCCESS);
|
||||
task.setStep(1.0);
|
||||
task.setFinishedTime(LocalDateTime.now());
|
||||
if (StrUtil.isNotBlank(result)) {
|
||||
task.setResult(result);
|
||||
}
|
||||
taskRepository.saveAndFlush(task);
|
||||
} catch (Throwable throwable) {
|
||||
log.error("任务执行失败", throwable);
|
||||
task.setStatus(Task.Status.FAILURE);
|
||||
task.setFinishedTime(LocalDateTime.now());
|
||||
if (ObjectUtil.isNotNull(throwable)) {
|
||||
task.setError(ExceptionUtil.stacktraceToString(throwable));
|
||||
}
|
||||
taskRepository.saveAndFlush(task);
|
||||
}
|
||||
}
|
||||
|
||||
public interface StepUpdater {
|
||||
void update(double step);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.task;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.TuShareService;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
/**
|
||||
* 更新日线数据
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250924
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UpdateDailyTask extends TaskRunner {
|
||||
private final StockRepository stockRepository;
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
private final TuShareService tuShareService;
|
||||
|
||||
protected UpdateDailyTask(ApplicationContext context, StockRepository stockRepository, DailyRepository dailyRepository, TransactionTemplate transactionTemplate, TuShareService tuShareService) {
|
||||
super(context);
|
||||
this.stockRepository = stockRepository;
|
||||
this.dailyRepository = dailyRepository;
|
||||
this.transactionTemplate = transactionTemplate;
|
||||
this.tuShareService = tuShareService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(Map<String, Object> params, StepUpdater updater) throws Exception {
|
||||
var tradeDates = new HashSet<LocalDate>();
|
||||
for (String exchange : List.of("SSE", "SZSE", "BSE")) {
|
||||
var response = tuShareService.tradeDateList(exchange);
|
||||
for (List<String> item : response.data().items()) {
|
||||
if (ObjectUtil.isNotEmpty(item) && StrUtil.isNotBlank(item.getFirst())) {
|
||||
tradeDates.add(LocalDate.parse(item.getFirst(), TuShareService.TRADE_FORMAT));
|
||||
}
|
||||
}
|
||||
}
|
||||
var existsTradeDates = dailyRepository.findDistinctTradeDate();
|
||||
var nowDate = LocalDate.now();
|
||||
var stocksMap = stockRepository.findAll().stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
||||
var targetTradeDates = tradeDates.stream()
|
||||
.filter(date -> date.isBefore(nowDate) || date.isEqual(nowDate))
|
||||
.filter(date -> !existsTradeDates.contains(date))
|
||||
.toList();
|
||||
var total = targetTradeDates.size();
|
||||
var finished = new AtomicInteger(0);
|
||||
targetTradeDates.parallelStream()
|
||||
.forEach(tradeDate -> {
|
||||
var factorResponse = tuShareService.factorList(tradeDate);
|
||||
var factorMap = new HashMap<String, Double>();
|
||||
for (List<String> item : factorResponse.data().items()) {
|
||||
factorMap.put(item.get(0), Double.valueOf(item.get(2)));
|
||||
}
|
||||
transactionTemplate.execute(status -> {
|
||||
var response = tuShareService.dailyList(tradeDate);
|
||||
var dailies = new ArrayList<Daily>();
|
||||
for (List<String> item : response.data().items()) {
|
||||
var code = item.get(0);
|
||||
if (stocksMap.containsKey(code)) {
|
||||
var stock = stocksMap.get(code);
|
||||
var factor = factorMap.get(code);
|
||||
var daily = new Daily();
|
||||
daily.setTradeDate(tradeDate);
|
||||
daily.setOpen(NumberHelper.parseDouble(item.get(2)));
|
||||
daily.setHigh(NumberUtil.parseDouble(item.get(3)));
|
||||
daily.setLow(NumberUtil.parseDouble(item.get(4)));
|
||||
daily.setClose(NumberUtil.parseDouble(item.get(5)));
|
||||
daily.setPreviousClose(NumberUtil.parseDouble(item.get(6)));
|
||||
daily.setPriceChangeAmount(NumberUtil.parseDouble(item.get(7)));
|
||||
daily.setPriceFluctuationRange(NumberUtil.parseDouble(item.get(8)));
|
||||
daily.setVolume(NumberUtil.parseDouble(item.get(9)));
|
||||
daily.setTurnover(NumberUtil.parseDouble(item.get(10)));
|
||||
daily.setFactor(factor);
|
||||
daily.setStock(stock);
|
||||
dailies.add(daily);
|
||||
}
|
||||
}
|
||||
dailyRepository.saveAll(dailies);
|
||||
return null;
|
||||
});
|
||||
updater.update(finished.incrementAndGet() * 1.0 / total);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.task;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QFinanceIndicator;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.FinanceIndicatorRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.TuShareService;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 更新财务指标数据
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250924
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UpdateFinanceIndicatorTask extends TaskRunner {
|
||||
private final StockRepository stockRepository;
|
||||
private final FinanceIndicatorRepository financeIndicatorRepository;
|
||||
|
||||
private final TuShareService tuShareService;
|
||||
|
||||
protected UpdateFinanceIndicatorTask(ApplicationContext context, StockRepository stockRepository, FinanceIndicatorRepository financeIndicatorRepository, TuShareService tuShareService) {
|
||||
super(context);
|
||||
this.stockRepository = stockRepository;
|
||||
this.financeIndicatorRepository = financeIndicatorRepository;
|
||||
this.tuShareService = tuShareService;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
@Override
|
||||
public String process(Map<String, Object> params, StepUpdater updater) throws JsonProcessingException {
|
||||
var stocks = stockRepository.findAll();
|
||||
var currentYear = LocalDate.now().getYear();
|
||||
for (int year = 1990; year < currentYear; year++) {
|
||||
var balances = tuShareService.request(
|
||||
"balancesheet_vip",
|
||||
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
|
||||
List.of(
|
||||
"ts_code",
|
||||
"total_share",
|
||||
"cap_rese",
|
||||
"surplus_rese",
|
||||
"undistr_porfit",
|
||||
"cash_reser_cb",
|
||||
"accounts_receiv_bill",
|
||||
"accounts_pay",
|
||||
"inventories",
|
||||
"goodwill",
|
||||
"total_cur_assets",
|
||||
"total_nca",
|
||||
"total_cur_liab",
|
||||
"total_ncl",
|
||||
"total_liab",
|
||||
"total_hldr_eqy_inc_min_int",
|
||||
"total_assets"
|
||||
)
|
||||
);
|
||||
var balancesMap = balances.stream().collect(Collectors.toMap(
|
||||
map -> map.get("ts_code"),
|
||||
map -> map,
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
var incomes = tuShareService.request(
|
||||
"income_vip",
|
||||
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
|
||||
List.of(
|
||||
"ts_code",
|
||||
"total_revenue",
|
||||
"total_cogs",
|
||||
"operate_profit",
|
||||
"oper_exp",
|
||||
"n_income"
|
||||
)
|
||||
);
|
||||
var incomesMap = incomes.stream().collect(Collectors.toMap(
|
||||
map -> map.get("ts_code"),
|
||||
map -> map,
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
var cashFlows = tuShareService.request(
|
||||
"cashflow_vip",
|
||||
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
|
||||
List.of(
|
||||
"ts_code",
|
||||
"n_cashflow_act",
|
||||
"n_cashflow_inv_act",
|
||||
"n_cash_flows_fnc_act"
|
||||
)
|
||||
);
|
||||
var cashFlowsMap = cashFlows.stream().collect(Collectors.toMap(
|
||||
map -> map.get("ts_code"),
|
||||
map -> map,
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
var finaIndicators = tuShareService.request(
|
||||
"fina_indicator_vip",
|
||||
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
|
||||
List.of(
|
||||
"ts_code",
|
||||
"ca_to_assets",
|
||||
"nca_to_assets",
|
||||
"currentdebt_to_debt",
|
||||
"longdeb_to_debt",
|
||||
"current_ratio",
|
||||
"quick_ratio",
|
||||
"ar_turn",
|
||||
"arturn_days",
|
||||
"inv_turn",
|
||||
"invturn_days",
|
||||
"fa_turn",
|
||||
"assets_turn",
|
||||
"roe_dt",
|
||||
"roa",
|
||||
"roa_dp",
|
||||
"total_revenue_ps"
|
||||
)
|
||||
);
|
||||
var finaIndicatorsMap = finaIndicators.stream().collect(Collectors.toMap(
|
||||
map -> map.get("ts_code"),
|
||||
map -> map,
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
|
||||
var financeIndicatorsMap = financeIndicatorRepository.findAll(QFinanceIndicator.financeIndicator.year.eq(year))
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
indicator -> indicator.getStock().getCode(),
|
||||
indicator -> indicator
|
||||
));
|
||||
|
||||
for (Stock stock : stocks) {
|
||||
var balance = balancesMap.get(stock.getCode());
|
||||
var income = incomesMap.get(stock.getCode());
|
||||
var cashFlow = cashFlowsMap.get(stock.getCode());
|
||||
var finaIndicator = finaIndicatorsMap.get(stock.getCode());
|
||||
if (stock.getListedDate().getYear() > year || ArrayUtil.<Object>isAllNull(balance, income, cashFlow, finaIndicator)) {
|
||||
continue;
|
||||
}
|
||||
var indicator = financeIndicatorsMap.getOrDefault(stock.getCode(), new FinanceIndicator());
|
||||
indicator.setStock(stock);
|
||||
indicator.setYear(year);
|
||||
if (ObjectUtil.isNotNull(balance)) {
|
||||
indicator.setTotalAssets(NumberHelper.parseDouble(balance.get("total_assets")));
|
||||
indicator.setTotalShareCapital(NumberHelper.parseDouble(balance.get("total_share")));
|
||||
indicator.setCapitalSurplus(NumberHelper.parseDouble(balance.get("cap_rese")));
|
||||
indicator.setSurplusReserve(NumberHelper.parseDouble(balance.get("surplus_rese")));
|
||||
indicator.setUndistributedProfit(NumberHelper.parseDouble(balance.get("undistr_porfit")));
|
||||
indicator.setCashAndCashEquivalents(NumberHelper.parseDouble(balance.get("cash_reser_cb")));
|
||||
indicator.setCashAndCashEquivalentsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCashAndCashEquivalents(), indicator.getTotalAssets()));
|
||||
indicator.setAccountsReceivable(NumberHelper.parseDouble(balance.get("accounts_receiv_bill")));
|
||||
indicator.setAccountsReceivableToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getAccountsReceivable(), indicator.getTotalAssets()));
|
||||
indicator.setAccountsPayable(NumberHelper.parseDouble(balance.get("accounts_pay")));
|
||||
indicator.setAccountsPayableToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getAccountsPayable(), indicator.getTotalAssets()));
|
||||
indicator.setInventory(NumberHelper.parseDouble(balance.get("inventories")));
|
||||
indicator.setInventoryToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getInventory(), indicator.getTotalAssets()));
|
||||
indicator.setGoodwill(NumberHelper.parseDouble(balance.get("goodwill")));
|
||||
indicator.setGoodwillToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getGoodwill(), indicator.getTotalAssets()));
|
||||
indicator.setCurrentAssets(NumberHelper.parseDouble(balance.get("total_cur_assets")));
|
||||
indicator.setCurrentAssetsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCurrentAssets(), indicator.getTotalAssets()));
|
||||
indicator.setFixedAssets(NumberHelper.parseDouble(balance.get("total_nca")));
|
||||
indicator.setFixedAssetsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getFixedAssets(), indicator.getTotalAssets()));
|
||||
indicator.setTotalLiabilities(NumberHelper.parseDouble(balance.get("total_liab")));
|
||||
indicator.setCurrentLiabilities(NumberHelper.parseDouble(balance.get("total_cur_liab")));
|
||||
indicator.setCurrentLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCurrentLiabilities(), indicator.getTotalAssets()));
|
||||
indicator.setCurrentLiabilitiesToTotalLiabilitiesRatio(NumberHelper.safeDiv(indicator.getCurrentLiabilities(), indicator.getTotalLiabilities()));
|
||||
indicator.setLongTermLiabilities(NumberHelper.parseDouble(balance.get("total_ncl")));
|
||||
indicator.setLongTermLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getLongTermLiabilities(), indicator.getTotalAssets()));
|
||||
indicator.setLongTermLiabilitiesToTotalLiabilitiesRatio(NumberHelper.safeDiv(indicator.getLongTermLiabilities(), indicator.getTotalLiabilities()));
|
||||
indicator.setLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getTotalLiabilities(), indicator.getTotalAssets()));
|
||||
indicator.setShareholdersEquity(NumberHelper.parseDouble(balance.get("total_hldr_eqy_inc_min_int")));
|
||||
indicator.setShareholdersEquityToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getShareholdersEquity(), indicator.getTotalAssets()));
|
||||
}
|
||||
if (ObjectUtil.isNotNull(income)) {
|
||||
indicator.setOperatingRevenue(NumberHelper.parseDouble(income.get("total_revenue")));
|
||||
indicator.setOperatingCost(NumberHelper.parseDouble(income.get("total_cogs")));
|
||||
indicator.setOperatingProfit(NumberHelper.parseDouble(income.get("operate_profit")));
|
||||
indicator.setOperatingExpenses(NumberHelper.parseDouble(income.get("oper_exp")));
|
||||
indicator.setNetProfit(NumberHelper.parseDouble(income.get("n_income")));
|
||||
}
|
||||
if (ObjectUtil.isNotNull(cashFlow)) {
|
||||
indicator.setCashFlowFromOperatingActivities(NumberHelper.parseDouble(cashFlow.get("n_cashflow_act")));
|
||||
indicator.setCashFlowFromInvestingActivities(NumberHelper.parseDouble(cashFlow.get("n_cashflow_inv_act")));
|
||||
indicator.setCashFlowFromFinancingActivities(NumberHelper.parseDouble(cashFlow.get("n_cash_flows_fnc_act")));
|
||||
}
|
||||
if (ObjectUtil.isNotNull(finaIndicator)) {
|
||||
indicator.setCurrentRatio(NumberHelper.parseDouble(finaIndicator.get("current_ratio")));
|
||||
indicator.setQuickRatio(NumberHelper.parseDouble(finaIndicator.get("quick_ratio")));
|
||||
indicator.setAccountsReceivableTurnover(NumberHelper.parseDouble(finaIndicator.get("ar_turn")));
|
||||
indicator.setDaysAccountsReceivableTurnover(NumberHelper.parseDouble(finaIndicator.get("arturn_days")));
|
||||
indicator.setInventoryTurnover(NumberHelper.parseDouble(finaIndicator.get("inv_turn")));
|
||||
indicator.setDaysInventoryTurnover(NumberHelper.parseDouble(finaIndicator.get("invturn_days")));
|
||||
indicator.setFixedAssetsTurnover(NumberHelper.parseDouble(finaIndicator.get("fa_turn")));
|
||||
indicator.setDaysFixedAssetsTurnover(NumberHelper.safeDiv(360.0, indicator.getFixedAssetsTurnover()));
|
||||
indicator.setTotalAssetsTurnover(NumberHelper.parseDouble(finaIndicator.get("assets_turn")));
|
||||
indicator.setDaysTotalAssetsTurnover(NumberHelper.safeDiv(360.0, indicator.getTotalAssetsTurnover()));
|
||||
indicator.setReturnOnEquity(NumberHelper.parseDouble(finaIndicator.get("roe_dt")));
|
||||
indicator.setReturnOnAssets(NumberHelper.parseDouble(finaIndicator.get("roa")));
|
||||
indicator.setOperatingGrossProfitMargin(NumberHelper.safeDiv(NumberHelper.safeMinus(indicator.getOperatingRevenue(), indicator.getOperatingCost()), indicator.getOperatingRevenue()));
|
||||
indicator.setOperatingProfitMargin(NumberHelper.safeDiv(indicator.getOperatingProfit(), indicator.getOperatingRevenue()));
|
||||
indicator.setOperatingSafetyMarginRatio(NumberHelper.safeDiv(indicator.getOperatingProfitMargin(), indicator.getOperatingGrossProfitMargin()));
|
||||
indicator.setNetProfitMargin(NumberHelper.parseDouble(finaIndicator.get("roa_dp")));
|
||||
indicator.setEarningsPerShare(NumberHelper.parseDouble(finaIndicator.get("total_revenue_ps")));
|
||||
}
|
||||
financeIndicatorRepository.save(indicator);
|
||||
}
|
||||
|
||||
updater.update((year - 1990) * 1.0 / (currentYear - 1990));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.lanyuanxiaoyao.leopard.core.task;
|
||||
|
||||
import cn.hutool.core.util.EnumUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.TuShareService;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 更新股票信息
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250924
|
||||
*/
|
||||
@Component
|
||||
public class UpdateStockTask extends TaskRunner {
|
||||
private final StockRepository stockRepository;
|
||||
|
||||
private final TuShareService tuShareService;
|
||||
|
||||
public UpdateStockTask(ApplicationContext context, StockRepository stockRepository, TuShareService tuShareService) {
|
||||
super(context);
|
||||
this.stockRepository = stockRepository;
|
||||
this.tuShareService = tuShareService;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
@Override
|
||||
public String process(Map<String, Object> params, StepUpdater updater) {
|
||||
var existsStockMap = stockRepository.findAll().stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
||||
var stocks = tuShareService.stockList()
|
||||
.data()
|
||||
.items()
|
||||
.stream()
|
||||
.map(item -> {
|
||||
var code = item.get(0);
|
||||
var name = item.get(1);
|
||||
var fullname = item.get(2);
|
||||
var market = EnumUtil.fromString(Stock.Market.class, item.get(3));
|
||||
var industry = item.get(4);
|
||||
var listedDate = LocalDate.parse(item.get(5), TuShareService.TRADE_FORMAT);
|
||||
var stock = existsStockMap.getOrDefault(code, new Stock());
|
||||
stock.setCode(code);
|
||||
stock.setName(name);
|
||||
stock.setFullname(fullname);
|
||||
stock.setMarket(market);
|
||||
stock.setIndustry(industry);
|
||||
stock.setListedDate(listedDate);
|
||||
return stock;
|
||||
})
|
||||
.toList();
|
||||
var currentCodes = stocks.stream().map(Stock::getCode).toList();
|
||||
var existsCodes = stockRepository.findDistinctCodes();
|
||||
var deleteCodes = existsCodes.stream().filter(code -> !currentCodes.contains(code)).toList();
|
||||
stockRepository.deleteAllByCodeIn(deleteCodes);
|
||||
stockRepository.saveAll(stocks);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.lanyuanxiaoyao.leopard.core;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.util.DDLGenerator;
|
||||
import com.lanyuanxiaoyao.service.template.Helper;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.postgresql.Driver;
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.postgresql.Driver;
|
||||
*/
|
||||
public class GenerateDDL {
|
||||
public static void main(String[] args) {
|
||||
DDLGenerator.generateDDL(
|
||||
Helper.generateDDL(
|
||||
"com.lanyuanxiaoyao.leopard.core.entity",
|
||||
"/Users/lanyuanxiaoyao/Project/IdeaProjects/leopard/leopard-core/target",
|
||||
PostgreSQLDialect.class,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
nohup /home/ubuntu/jdk-17.0.16+8/bin/java -jar /home/ubuntu/app/leopard-server-1.0.0.jar --spring.profiles.active=build --spring.web.resources.static-locations=file:/home/ubuntu/app/dist --logging.parent=/home/ubuntu/app > /dev/null 2>&1 &
|
||||
nohup /home/ubuntu/jdk-21.0.8+9/bin/java -jar /home/ubuntu/app/leopard-server-1.0.0.jar --spring.profiles.active=build --spring.web.resources.static-locations=file:/home/ubuntu/app/dist --logging.parent=/home/ubuntu/app > /dev/null 2>&1 &
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
original_command="$0"
|
||||
application_name="/home/ubuntu/app/leopard-server-1.0.0.jar"
|
||||
application_name="leopard-server-1.0.0.jar"
|
||||
|
||||
function get_pid() {
|
||||
echo $(ps ef | grep "$application_name" | grep -v grep | awk '{ print $1 }')
|
||||
ID=$(ps -ef | grep "$application_name" | grep -v grep | grep -v $original_command | awk '{ print $2 }')
|
||||
if [[ -z "$ID" ]]; then
|
||||
ID=$(ps aux | grep "$application_name" | grep -v grep | grep -v $original_command | awk '{print $2}')
|
||||
if [[ -z "$ID" ]]; then
|
||||
ID=$(/home/ubuntu/jdk-21.0.8+9/bin/jps -lmvV | grep "$application_name" | awk '{print $1}')
|
||||
fi
|
||||
fi
|
||||
echo $ID
|
||||
}
|
||||
|
||||
pid=$(get_pid)
|
||||
@@ -31,3 +38,4 @@ while (true); do
|
||||
fi
|
||||
sleep 1s
|
||||
done
|
||||
|
||||
|
||||
@@ -31,6 +31,14 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jetty</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
@@ -41,24 +49,6 @@
|
||||
<artifactId>spring-boot-starter-quartz</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-rule-sql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
@@ -69,6 +59,11 @@
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.lanyuanxiaoyao.leopard.server;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
|
||||
import com.yomahub.liteflow.core.FlowExecutor;
|
||||
import jakarta.annotation.Resource;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.TaskRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
@@ -18,19 +17,20 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
@SpringBootApplication(scanBasePackages = "com.lanyuanxiaoyao.leopard")
|
||||
@EnableJpaAuditing
|
||||
public class LeopardServerApplication implements ApplicationRunner {
|
||||
private final TaskRepository taskRepository;
|
||||
|
||||
public LeopardServerApplication(TaskRepository taskRepository) {
|
||||
this.taskRepository = taskRepository;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LeopardServerApplication.class, args);
|
||||
}
|
||||
|
||||
@Resource
|
||||
private FlowExecutor executor;
|
||||
@Resource
|
||||
private TuShareService tuShareService;
|
||||
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
// executor.execute2RespWithEL("THEN(update_daily)");
|
||||
// executor.execute2RespWithEL("THEN(update_stock)");
|
||||
// executor.execute2RespWithEL("THEN(check_daily)");
|
||||
log.warn("更新所有未完成的任务状态为失败");
|
||||
taskRepository.updateAllRunningTaskToFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.configuration;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* 缓存提供
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @date 2023-04-23
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheProvider {
|
||||
@Primary
|
||||
@Bean("short-cache")
|
||||
public CacheManager normalCache() {
|
||||
CaffeineCacheManager manager = new CaffeineCacheManager();
|
||||
manager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES));
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Bean("long-cache")
|
||||
public CacheManager longCache() {
|
||||
CaffeineCacheManager manager = new CaffeineCacheManager();
|
||||
manager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS));
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,10 @@ package com.lanyuanxiaoyao.leopard.server.controller;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Task;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskTemplateService;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.TaskService;
|
||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -87,11 +88,11 @@ public class CommonOptionsController {
|
||||
);
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
private final TaskTemplateService taskTemplateService;
|
||||
private final TaskService taskService;
|
||||
|
||||
public CommonOptionsController(StockRepository stockRepository, TaskTemplateService taskTemplateService) {
|
||||
public CommonOptionsController(StockRepository stockRepository, TaskService taskService) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.taskTemplateService = taskTemplateService;
|
||||
this.taskService = taskService;
|
||||
}
|
||||
|
||||
@GetMapping("/options/{name}")
|
||||
@@ -100,18 +101,21 @@ public class CommonOptionsController {
|
||||
case "stock_market" -> GlobalResponse.responseSuccess(
|
||||
Arrays.stream(Stock.Market.values())
|
||||
.map(market -> new Option(market.getChineseName(), market.name()))
|
||||
.sorted(Comparator.comparing(Option::label))
|
||||
.toList()
|
||||
);
|
||||
case "stock_industry" -> GlobalResponse.responseSuccess(
|
||||
stockRepository.findDistinctIndustries()
|
||||
.stream()
|
||||
.map(industry -> new Option(industry, industry))
|
||||
.sorted(Comparator.comparing(Option::label))
|
||||
.toList()
|
||||
);
|
||||
case "task_template_id" -> GlobalResponse.responseSuccess(
|
||||
taskTemplateService.list()
|
||||
taskService.getTemplates()
|
||||
.stream()
|
||||
.map(template -> new Option(template.getName(), template.getId()))
|
||||
.map(template -> new Option(template.name(), template.id()))
|
||||
.sorted(Comparator.comparing(Option::label))
|
||||
.toList()
|
||||
);
|
||||
default -> GlobalResponse.responseSuccess(List.of());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.service.TaskService;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.QuartzService;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskTemplateService;
|
||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -20,11 +20,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("task_schedule")
|
||||
public class QuartzController {
|
||||
private final QuartzService quartzService;
|
||||
private final TaskTemplateService taskTemplateService;
|
||||
private final TaskService taskService;
|
||||
|
||||
public QuartzController(QuartzService quartzService, TaskTemplateService taskTemplateService) {
|
||||
public QuartzController(QuartzService quartzService, TaskService taskService) {
|
||||
this.quartzService = quartzService;
|
||||
this.taskTemplateService = taskTemplateService;
|
||||
this.taskService = taskService;
|
||||
}
|
||||
|
||||
@PostMapping("save")
|
||||
@@ -38,11 +38,11 @@ public class QuartzController {
|
||||
var list = quartzService.list()
|
||||
.stream()
|
||||
.map(task -> {
|
||||
var template = taskTemplateService.detail(task.templateId());
|
||||
var template = taskService.getTemplate(task.templateId());
|
||||
return new ListItem(
|
||||
task.key(),
|
||||
template.getName(),
|
||||
template.getDescription(),
|
||||
template.name(),
|
||||
template.description(),
|
||||
task.cron(),
|
||||
task.status(),
|
||||
task.previousFireTime(),
|
||||
@@ -72,7 +72,7 @@ public class QuartzController {
|
||||
}
|
||||
|
||||
public record SaveItem(
|
||||
Long templateId,
|
||||
String templateId,
|
||||
String cron
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -1,37 +1,32 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.StockCollection;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.StockCollectionService;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.StockService;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.StockCollectionService;
|
||||
import com.lanyuanxiaoyao.leopard.server.entity.StockScoreVo;
|
||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("stock_collection")
|
||||
public class StockCollectionController extends SimpleControllerSupport<StockCollection, StockCollectionController.SaveItem, StockCollectionController.ListItem, StockCollectionController.DetailItem> {
|
||||
private final StockService stockService;
|
||||
|
||||
public StockCollectionController(StockCollectionService service, StockService stockService) {
|
||||
public class StockCollectionController extends SimpleControllerSupport<StockCollection, Void, StockCollectionController.ListItem, StockCollectionController.DetailItem> {
|
||||
public StockCollectionController(StockCollectionService service) {
|
||||
super(service);
|
||||
this.stockService = stockService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<SaveItem, StockCollection> saveItemMapper() {
|
||||
return item -> {
|
||||
var collection = new StockCollection();
|
||||
collection.setId(item.id());
|
||||
collection.setName(item.name());
|
||||
collection.setDescription(item.description());
|
||||
var stocks = stockService.list(item.stockIds());
|
||||
collection.setStocks(new HashSet<>(stocks));
|
||||
return collection;
|
||||
};
|
||||
public GlobalResponse<Long> save(Void unused) throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Void, StockCollection> saveItemMapper() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,7 +35,9 @@ public class StockCollectionController extends SimpleControllerSupport<StockColl
|
||||
collection.getId(),
|
||||
collection.getName(),
|
||||
collection.getDescription(),
|
||||
collection.getStocks().size()
|
||||
collection.getScores().size(),
|
||||
collection.getCreatedTime(),
|
||||
collection.getModifiedTime()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,24 +47,24 @@ public class StockCollectionController extends SimpleControllerSupport<StockColl
|
||||
collection.getId(),
|
||||
collection.getName(),
|
||||
collection.getDescription(),
|
||||
collection.getStocks().size(),
|
||||
collection.getStocks()
|
||||
collection.getScores().size(),
|
||||
collection.getScores()
|
||||
.stream()
|
||||
.map(StockScoreVo::of)
|
||||
.sorted(Comparator.comparing(StockScoreVo::score).reversed())
|
||||
.toList(),
|
||||
collection.getCreatedTime(),
|
||||
collection.getModifiedTime()
|
||||
);
|
||||
}
|
||||
|
||||
public record SaveItem(
|
||||
Long id,
|
||||
String name,
|
||||
String description,
|
||||
Set<Long> stockIds
|
||||
) {
|
||||
}
|
||||
|
||||
public record ListItem(
|
||||
Long id,
|
||||
String name,
|
||||
String description,
|
||||
Integer count
|
||||
Integer count,
|
||||
LocalDateTime createdTime,
|
||||
LocalDateTime modifiedTime
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -76,7 +73,9 @@ public class StockCollectionController extends SimpleControllerSupport<StockColl
|
||||
String name,
|
||||
String description,
|
||||
Integer count,
|
||||
Set<Stock> stocks
|
||||
List<StockScoreVo> scores,
|
||||
LocalDateTime createdTime,
|
||||
LocalDateTime modifiedTime
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.controller;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.StockService;
|
||||
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
|
||||
import com.lanyuanxiaoyao.leopard.core.helper.TaHelper;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.StockService;
|
||||
import com.lanyuanxiaoyao.leopard.server.entity.StockDetailVo;
|
||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -19,9 +24,10 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250829
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("stock")
|
||||
public class StockController extends SimpleControllerSupport<Stock, Void, StockController.DetailItem, StockController.DetailItem> {
|
||||
public class StockController extends SimpleControllerSupport<Stock, Void, StockDetailVo, StockDetailVo> {
|
||||
private final StockService stockService;
|
||||
|
||||
public StockController(StockService service, StockService stockService) {
|
||||
@@ -38,53 +44,131 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
|
||||
public GlobalResponse<FinanceItem> finance(@PathVariable("id") Long id) {
|
||||
// 财报默认是上一年的
|
||||
var year = LocalDate.now().minusYears(1).getYear();
|
||||
var balanceSheet = stockService.findBalanceSheet(id, year);
|
||||
var income = stockService.findIncome(id, year);
|
||||
var cashFlow = stockService.findCashFlow(id, year);
|
||||
var financeIndicator = stockService.findFinanceIndicator(id, year);
|
||||
return GlobalResponse.responseSuccess(new FinanceItem(
|
||||
id,
|
||||
year,
|
||||
balanceSheet
|
||||
.map(bs -> new BalanceSheetItem(
|
||||
NumberHelper.formatFinanceDouble(bs.getTotalAssets()),
|
||||
NumberHelper.formatFinanceDouble(bs.getTotalCurrentAssets()),
|
||||
NumberHelper.formatFinanceDouble(bs.getTotalNonCurrentAssets()),
|
||||
NumberHelper.formatFinanceDouble(bs.getTotalLiabilities()),
|
||||
NumberHelper.formatFinanceDouble(bs.getTotalCurrentLiabilities()),
|
||||
NumberHelper.formatFinanceDouble(bs.getTotalNonCurrentLiabilities())
|
||||
financeIndicator
|
||||
.map(fi -> new BalanceSheetItem(
|
||||
NumberHelper.formatFinanceDouble(fi.getTotalAssets()),
|
||||
NumberHelper.formatFinanceDouble(fi.getCurrentAssets()),
|
||||
NumberHelper.formatPercentageDouble(fi.getCurrentAssetsToTotalAssetsRatio()),
|
||||
NumberHelper.formatFinanceDouble(fi.getFixedAssets()),
|
||||
NumberHelper.formatPercentageDouble(fi.getFixedAssetsToTotalAssetsRatio()),
|
||||
NumberHelper.formatFinanceDouble(fi.getTotalLiabilities()),
|
||||
NumberHelper.formatFinanceDouble(fi.getCurrentLiabilities()),
|
||||
NumberHelper.formatPercentageDouble(fi.getCurrentLiabilitiesToTotalAssetsRatio()),
|
||||
NumberHelper.formatFinanceDouble(fi.getLongTermLiabilities()),
|
||||
NumberHelper.formatPercentageDouble(fi.getLongTermLiabilitiesToTotalAssetsRatio())
|
||||
))
|
||||
.orElse(new BalanceSheetItem()),
|
||||
income
|
||||
.map(ic -> new IncomeItem(
|
||||
NumberHelper.formatFinanceDouble(ic.getTotalOperatingRevenue()),
|
||||
NumberHelper.formatFinanceDouble(ic.getTotalOperatingCost()),
|
||||
NumberHelper.formatFinanceDouble(ic.getTotalProfit())
|
||||
financeIndicator
|
||||
.map(fi -> new IncomeItem(
|
||||
NumberHelper.formatFinanceDouble(fi.getOperatingRevenue()),
|
||||
NumberHelper.formatFinanceDouble(fi.getOperatingCost()),
|
||||
NumberHelper.formatFinanceDouble(fi.getOperatingProfit())
|
||||
))
|
||||
.orElse(new IncomeItem()),
|
||||
cashFlow
|
||||
.map(cf -> new CashFlowItem(
|
||||
NumberHelper.formatFinanceDouble(cf.getNetProfit())
|
||||
financeIndicator
|
||||
.map(fi -> new CashFlowItem(
|
||||
NumberHelper.formatFinanceDouble(fi.getNetProfit()),
|
||||
NumberHelper.formatFinanceDouble(fi.getCashFlowFromOperatingActivities()),
|
||||
NumberHelper.formatFinanceDouble(fi.getCashFlowFromInvestingActivities()),
|
||||
NumberHelper.formatFinanceDouble(fi.getCashFlowFromFinancingActivities())
|
||||
))
|
||||
.orElse(new CashFlowItem())
|
||||
.orElse(new CashFlowItem()),
|
||||
financeIndicator
|
||||
.map(fi -> new IndicateItem(
|
||||
NumberHelper.formatFinanceDouble(fi.getCurrentRatio()),
|
||||
NumberHelper.formatFinanceDouble(fi.getQuickRatio()),
|
||||
NumberHelper.formatFinanceDouble(fi.getReturnOnEquity()),
|
||||
NumberHelper.formatFinanceDouble(fi.getReturnOnAssets()),
|
||||
NumberHelper.formatFinanceDouble(fi.getAccountsReceivableTurnover()),
|
||||
NumberHelper.formatDaysDouble(fi.getDaysAccountsReceivableTurnover()),
|
||||
NumberHelper.formatFinanceDouble(fi.getInventoryTurnover()),
|
||||
NumberHelper.formatDaysDouble(fi.getDaysInventoryTurnover()),
|
||||
NumberHelper.formatFinanceDouble(fi.getFixedAssetsTurnover()),
|
||||
NumberHelper.formatDaysDouble(fi.getDaysFixedAssetsTurnover()),
|
||||
NumberHelper.formatFinanceDouble(fi.getTotalAssetsTurnover()),
|
||||
NumberHelper.formatDaysDouble(fi.getDaysTotalAssetsTurnover())
|
||||
))
|
||||
.orElse(new IndicateItem())
|
||||
));
|
||||
}
|
||||
|
||||
private GlobalResponse<Map<String, Object>> convertFinanceChartData(List<?> data, String field) {
|
||||
return GlobalResponse.responseDetailData(
|
||||
data.stream()
|
||||
.map(item -> BeanUtil.getFieldValue(item, field))
|
||||
.toList()
|
||||
);
|
||||
@GetMapping("finance/{id}/{field}")
|
||||
public GlobalResponse<Map<String, Object>> financeCharts(@PathVariable("id") Long id, @PathVariable("field") String field) {
|
||||
var data = stockService.findFinanceIndicatorRecent(id, 5);
|
||||
var xList = new ArrayList<Integer>();
|
||||
var yList = new ArrayList<Object>();
|
||||
for (var indicator : data) {
|
||||
xList.add(indicator.getYear());
|
||||
yList.add(BeanUtil.getFieldValue(indicator, field));
|
||||
}
|
||||
return GlobalResponse.responseMapData(Map.of(
|
||||
"xList", xList, "yList", yList
|
||||
));
|
||||
}
|
||||
|
||||
@GetMapping("finance/{id}/{type}/{field}")
|
||||
public GlobalResponse<Map<String, Object>> financeCharts(@PathVariable("id") Long id, @PathVariable("type") String type, @PathVariable("field") String field) {
|
||||
return switch (type) {
|
||||
case "balanceSheet" -> convertFinanceChartData(stockService.findBalanceSheetRecent(id, 5), field);
|
||||
case "income" -> convertFinanceChartData(stockService.findIncomeRecent(id, 5), field);
|
||||
case "cashflow" -> convertFinanceChartData(stockService.findCashFlowRecent(id, 5), field);
|
||||
default -> throw new IllegalStateException("Unexpected value: " + type);
|
||||
};
|
||||
@GetMapping("daily/current/{id}")
|
||||
public GlobalResponse<Map<String, Object>> dailyCurrent(@PathVariable("id") Long id) {
|
||||
var daily = stockService.findDailyLatest(id);
|
||||
return GlobalResponse.responseMapData(Map.of(
|
||||
"date", daily.map(Daily::getTradeDate).map(LocalDate::toString).orElse("/"),
|
||||
"open", daily.map(Daily::getOpen).map(NumberHelper::formatPriceDouble).orElse(NumberHelper.FINANCE_NULL_DOUBLE),
|
||||
"close", daily.map(Daily::getClose).map(NumberHelper::formatPriceDouble).orElse(NumberHelper.FINANCE_NULL_DOUBLE),
|
||||
"low", daily.map(Daily::getLow).map(NumberHelper::formatPriceDouble).orElse(NumberHelper.FINANCE_NULL_DOUBLE),
|
||||
"high", daily.map(Daily::getHigh).map(NumberHelper::formatPriceDouble).orElse(NumberHelper.FINANCE_NULL_DOUBLE)
|
||||
));
|
||||
}
|
||||
|
||||
@GetMapping("daily/{id}")
|
||||
public GlobalResponse<Map<String, Object>> dailyCharts(@PathVariable("id") Long id) {
|
||||
var data = stockService.findDailyRecent(id, 100 + 60);
|
||||
log.info("Size: {}", data.size());
|
||||
var xList = new ArrayList<String>();
|
||||
var yList = new ArrayList<List<Double>>();
|
||||
for (var daily : data.subList(60, data.size() - 1)) {
|
||||
xList.add(daily.getTradeDate().toString());
|
||||
yList.add(List.of(daily.getHfqOpen(), daily.getHfqClose(), daily.getHfqLow(), daily.getHfqHigh()));
|
||||
}
|
||||
return GlobalResponse.responseMapData(Map.of(
|
||||
"xList", xList,
|
||||
"yList", yList,
|
||||
"sma10", TaHelper.sma(data, 10, Daily::getHfqClose).subList(60, data.size() - 1),
|
||||
"sma30", TaHelper.sma(data, 30, Daily::getHfqClose).subList(60, data.size() - 1),
|
||||
"sma60", TaHelper.sma(data, 60, Daily::getHfqClose).subList(60, data.size() - 1)
|
||||
));
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@GetMapping("weekly/{id}")
|
||||
public GlobalResponse<Map<String, Object>> weeklyCharts(@PathVariable("id") Long id) {
|
||||
var data = stockService.findWeeklyRecent(id, 50);
|
||||
var xList = new ArrayList<String>();
|
||||
var yList = new ArrayList<List<Double>>();
|
||||
for (var weekly : data) {
|
||||
xList.add(weekly.tradeDate().toString());
|
||||
yList.add(List.of(weekly.open(), weekly.close(), weekly.low(), weekly.high()));
|
||||
}
|
||||
return GlobalResponse.responseMapData(Map.of(
|
||||
"xList", xList, "yList", yList
|
||||
));
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@GetMapping("monthly/{id}")
|
||||
public GlobalResponse<Map<String, Object>> monthlyCharts(@PathVariable("id") Long id) {
|
||||
var data = stockService.findMonthlyRecent(id, 24);
|
||||
var xList = new ArrayList<String>();
|
||||
var yList = new ArrayList<List<Double>>();
|
||||
for (var monthly : data) {
|
||||
xList.add(monthly.tradeDate().toString());
|
||||
yList.add(List.of(monthly.open(), monthly.close(), monthly.low(), monthly.high()));
|
||||
}
|
||||
return GlobalResponse.responseMapData(Map.of(
|
||||
"xList", xList, "yList", yList
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,37 +176,14 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private DetailItem covert(Stock stock) {
|
||||
return new DetailItem(
|
||||
stock.getId(),
|
||||
stock.getCode(),
|
||||
stock.getName(),
|
||||
stock.getFullname(),
|
||||
stock.getMarket(),
|
||||
stock.getIndustry(),
|
||||
stock.getListedDate()
|
||||
);
|
||||
@Override
|
||||
protected Function<Stock, StockDetailVo> listItemMapper() {
|
||||
return StockDetailVo::of;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Stock, DetailItem> listItemMapper() {
|
||||
return this::covert;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Stock, DetailItem> detailItemMapper() {
|
||||
return this::covert;
|
||||
}
|
||||
|
||||
public record DetailItem(
|
||||
Long id,
|
||||
String code,
|
||||
String name,
|
||||
String fullname,
|
||||
Stock.Market market,
|
||||
String industry,
|
||||
LocalDate listedDate
|
||||
) {
|
||||
protected Function<Stock, StockDetailVo> detailItemMapper() {
|
||||
return StockDetailVo::of;
|
||||
}
|
||||
|
||||
public record FinanceItem(
|
||||
@@ -130,20 +191,29 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
|
||||
Integer year,
|
||||
BalanceSheetItem balanceSheet,
|
||||
IncomeItem income,
|
||||
CashFlowItem cashFlow
|
||||
CashFlowItem cashFlow,
|
||||
IndicateItem indicate
|
||||
) {
|
||||
}
|
||||
|
||||
public record BalanceSheetItem(
|
||||
String totalAssets,
|
||||
String totalCurrentAssets,
|
||||
String totalNonCurrentAssets,
|
||||
String currentAssets,
|
||||
String currentAssetsRatio,
|
||||
String fixedAssets,
|
||||
String fixedAssetsRatio,
|
||||
String totalLiabilities,
|
||||
String totalCurrentLiabilities,
|
||||
String totalNonCurrentLiabilities
|
||||
String currentLiabilities,
|
||||
String currentLiabilitiesRatio,
|
||||
String longTermLiabilities,
|
||||
String longTermLiabilitiesRatio
|
||||
) {
|
||||
public BalanceSheetItem() {
|
||||
this(
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
@@ -155,9 +225,9 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
|
||||
}
|
||||
|
||||
public record IncomeItem(
|
||||
String totalOperatingRevenue,
|
||||
String totalOperatingCost,
|
||||
String totalProfit
|
||||
String operatingRevenue,
|
||||
String operatingCost,
|
||||
String operatingProfit
|
||||
) {
|
||||
public IncomeItem() {
|
||||
this(
|
||||
@@ -169,10 +239,48 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
|
||||
}
|
||||
|
||||
public record CashFlowItem(
|
||||
String netProfit
|
||||
String netProfit,
|
||||
String cashFlowFromOperatingActivities,
|
||||
String cashFlowFromInvestingActivities,
|
||||
String cashFlowFromFinancingActivities
|
||||
) {
|
||||
public CashFlowItem() {
|
||||
this(
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public record IndicateItem(
|
||||
String currentRatio,
|
||||
String quickRatio,
|
||||
String roe,
|
||||
String roa,
|
||||
String accountsReceivableTurnover,
|
||||
String daysAccountsReceivableTurnover,
|
||||
String inventoryTurnover,
|
||||
String daysInventoryTurnover,
|
||||
String fixedAssetsTurnover,
|
||||
String daysFixedAssetsTurnover,
|
||||
String totalAssetsTurnover,
|
||||
String daysTotalAssetsTurnover
|
||||
) {
|
||||
public IndicateItem() {
|
||||
this(
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE,
|
||||
NumberHelper.FINANCE_NULL_DOUBLE
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ import cn.hutool.core.date.BetweenFormatter;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Task;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.TaskService;
|
||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -45,14 +47,25 @@ public class TaskController extends SimpleControllerSupport<Task, Void, TaskCont
|
||||
return GlobalResponse.responseSuccess();
|
||||
}
|
||||
|
||||
@GetMapping("template/list")
|
||||
public GlobalResponse<Map<String, Object>> templateList() {
|
||||
var templates = taskService.getTemplates()
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(TaskService.TaskTemplate::name))
|
||||
.toList();
|
||||
return GlobalResponse.responseCrudData(templates, templates.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Void, Task> saveItemMapper() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private TaskCost calculateCost(LocalDateTime start, LocalDateTime finish) {
|
||||
if (ObjectUtil.isNull(start) || ObjectUtil.isNull(finish)) {
|
||||
if (ObjectUtil.isNull(start)) {
|
||||
return new TaskCost(null, null);
|
||||
} else if (ObjectUtil.isNull(finish)) {
|
||||
finish = LocalDateTime.now();
|
||||
}
|
||||
var duration = Duration.between(start, finish).toMillis();
|
||||
return new TaskCost(
|
||||
@@ -108,7 +121,7 @@ public class TaskController extends SimpleControllerSupport<Task, Void, TaskCont
|
||||
LocalDateTime finishedTime,
|
||||
Long cost,
|
||||
String costText,
|
||||
Integer step
|
||||
Double step
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -123,11 +136,11 @@ public class TaskController extends SimpleControllerSupport<Task, Void, TaskCont
|
||||
LocalDateTime finishedTime,
|
||||
Long cost,
|
||||
String costText,
|
||||
Integer step
|
||||
Double step
|
||||
) {
|
||||
}
|
||||
|
||||
public record ExecuteRequest(Long templateId, Map<String, Object> params) {
|
||||
public record ExecuteRequest(String templateId, Map<String, Object> params) {
|
||||
}
|
||||
|
||||
public record TaskCost(Long cost, String costText) {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.controller;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.TaskTemplate;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskTemplateService;
|
||||
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
|
||||
import java.util.function.Function;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("task_template")
|
||||
public class TaskTemplateController extends SimpleControllerSupport<TaskTemplate, TaskTemplateController.Item, TaskTemplateController.Item, TaskTemplateController.Item> {
|
||||
@Value("${spring.application.name}")
|
||||
private String application;
|
||||
|
||||
public TaskTemplateController(TaskTemplateService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Item, TaskTemplate> saveItemMapper() {
|
||||
return item -> {
|
||||
var template = new TaskTemplate();
|
||||
template.setId(item.id());
|
||||
template.setName(item.name());
|
||||
template.setDescription(item.description());
|
||||
template.setApplication(application);
|
||||
template.setChain(IdUtil.simpleUUID());
|
||||
template.setExpression(item.expression());
|
||||
template.setExpressionEl(StrUtil.format("CATCH(THEN(task_start, ({}), task_end)).DO(task_error)", item.expression()));
|
||||
return template;
|
||||
};
|
||||
}
|
||||
|
||||
private Item convert(TaskTemplate template) {
|
||||
return new Item(
|
||||
template.getId(),
|
||||
template.getName(),
|
||||
template.getDescription(),
|
||||
template.getExpression()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<TaskTemplate, Item> listItemMapper() {
|
||||
return this::convert;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<TaskTemplate, Item> detailItemMapper() {
|
||||
return this::convert;
|
||||
}
|
||||
|
||||
public record Item(
|
||||
Long id,
|
||||
String name,
|
||||
String description,
|
||||
String expression
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250917
|
||||
*/
|
||||
public record StockDetailVo(
|
||||
Long id,
|
||||
String code,
|
||||
String name,
|
||||
String fullname,
|
||||
Stock.Market market,
|
||||
String industry,
|
||||
LocalDate listedDate
|
||||
) {
|
||||
public static StockDetailVo of(Stock stock) {
|
||||
return new StockDetailVo(
|
||||
stock.getId(),
|
||||
stock.getCode(),
|
||||
stock.getName(),
|
||||
stock.getFullname(),
|
||||
stock.getMarket(),
|
||||
stock.getIndustry(),
|
||||
stock.getListedDate()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.StockScore;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250917
|
||||
*/
|
||||
public record StockScoreVo(
|
||||
Long id,
|
||||
String code,
|
||||
String name,
|
||||
String fullname,
|
||||
Stock.Market market,
|
||||
String industry,
|
||||
LocalDate listedDate,
|
||||
Double score,
|
||||
String extra
|
||||
) {
|
||||
public static StockScoreVo of(StockScore score) {
|
||||
return new StockScoreVo(
|
||||
score.getStock().getId(),
|
||||
score.getStock().getCode(),
|
||||
score.getStock().getName(),
|
||||
score.getStock().getFullname(),
|
||||
score.getStock().getMarket(),
|
||||
score.getStock().getIndustry(),
|
||||
score.getStock().getListedDate(),
|
||||
score.getScore(),
|
||||
score.getExtra()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.map(entry -> entry.getKey() + ": " + entry.getValue())
|
||||
.collect(Collectors.joining("<br>"))
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.helper;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250912
|
||||
*/
|
||||
public class NumberHelper {
|
||||
public static final String FINANCE_NULL_DOUBLE = "/";
|
||||
|
||||
public static String formatFinanceDouble(Double value) {
|
||||
if (ObjectUtil.isNull(value)) {
|
||||
return FINANCE_NULL_DOUBLE;
|
||||
}
|
||||
return NumberUtil.decimalFormat("#.##", value);
|
||||
}
|
||||
|
||||
public static Double parseDouble(String value) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
return Double.parseDouble(value);
|
||||
}
|
||||
|
||||
public static Double parseDouble(String value, Function<Double, Double> ifSuccess) {
|
||||
var result = parseDouble(value);
|
||||
return ObjectUtil.isNull(result) ? null : ifSuccess.apply(result);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DailyService extends SimpleServiceSupport<Daily> {
|
||||
public DailyService(DailyRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package com.lanyuanxiaoyao.leopard.server.service;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.task.TaskMonitorNodes;
|
||||
import com.yomahub.liteflow.core.FlowExecutor;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.TaskService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
@@ -36,10 +36,10 @@ public class QuartzService {
|
||||
var tasks = new ArrayList<QuartzTask>();
|
||||
for (var key : scheduler.getJobKeys(GroupMatcher.anyGroup())) {
|
||||
var detail = scheduler.getJobDetail(key);
|
||||
var trigger = (CronTrigger) scheduler.getTriggersOfJob(key).get(0);
|
||||
var trigger = (CronTrigger) scheduler.getTriggersOfJob(key).getFirst();
|
||||
tasks.add(new QuartzTask(
|
||||
detail.getKey().getName(),
|
||||
detail.getJobDataMap().getLong("template_id"),
|
||||
detail.getJobDataMap().getString("template_id"),
|
||||
trigger.getCronExpression(),
|
||||
scheduler.getTriggerState(trigger.getKey()),
|
||||
ObjectUtil.isNull(trigger.getPreviousFireTime()) ? null : LocalDateTime.ofInstant(trigger.getPreviousFireTime().toInstant(), ZoneId.systemDefault()),
|
||||
@@ -49,7 +49,7 @@ public class QuartzService {
|
||||
return tasks;
|
||||
}
|
||||
|
||||
public void save(Long templateId, String cron) throws SchedulerException {
|
||||
public void save(String templateId, String cron) throws SchedulerException {
|
||||
var detail = JobBuilder.newJob(TaskExecutionJob.class)
|
||||
.withIdentity("task_execution_" + IdUtil.fastUUID())
|
||||
.usingJobData("template_id", templateId)
|
||||
@@ -81,30 +81,27 @@ public class QuartzService {
|
||||
|
||||
@Slf4j
|
||||
public static class TaskExecutionJob extends QuartzJobBean {
|
||||
private final TaskTemplateService taskTemplateService;
|
||||
private final FlowExecutor flowExecutor;
|
||||
private final TaskService taskService;
|
||||
|
||||
public TaskExecutionJob(TaskTemplateService taskTemplateService, FlowExecutor flowExecutor) {
|
||||
this.taskTemplateService = taskTemplateService;
|
||||
this.flowExecutor = flowExecutor;
|
||||
public TaskExecutionJob(TaskService taskService) {
|
||||
this.taskService = taskService;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext context) {
|
||||
var dataMap = context.getMergedJobDataMap();
|
||||
var templateId = dataMap.getLong("template_id");
|
||||
if (ObjectUtil.isNotNull(templateId)) {
|
||||
var template = taskTemplateService.detail(templateId);
|
||||
var templateId = dataMap.getString("template_id");
|
||||
if (StrUtil.isNotBlank(templateId)) {
|
||||
var params = (Map<String, Object>) dataMap.getOrDefault("params", Map.of());
|
||||
var monitorContext = new TaskMonitorNodes.TaskMonitorContext(template);
|
||||
flowExecutor.execute2Resp(template.getChain(), params, monitorContext);
|
||||
taskService.execute(templateId, params, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record QuartzTask(
|
||||
String key,
|
||||
Long templateId,
|
||||
String templateId,
|
||||
String cron,
|
||||
Trigger.TriggerState status,
|
||||
LocalDateTime previousFireTime,
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet_;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow_;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Income;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Income_;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QBalanceSheet;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QCashFlow;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QIncome;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.BalanceSheetRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.CashFlowRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.IncomeRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250828
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class StockService extends SimpleServiceSupport<Stock> {
|
||||
private final StockRepository stockRepository;
|
||||
private final BalanceSheetRepository balanceSheetRepository;
|
||||
private final IncomeRepository incomeRepository;
|
||||
private final CashFlowRepository cashFlowRepository;
|
||||
|
||||
public StockService(StockRepository repository, BalanceSheetRepository balanceSheetRepository, IncomeRepository incomeRepository, CashFlowRepository cashFlowRepository) {
|
||||
super(repository);
|
||||
this.stockRepository = repository;
|
||||
this.balanceSheetRepository = balanceSheetRepository;
|
||||
this.incomeRepository = incomeRepository;
|
||||
this.cashFlowRepository = cashFlowRepository;
|
||||
}
|
||||
|
||||
public Optional<BalanceSheet> findBalanceSheet(Long stockId, Integer year) {
|
||||
return balanceSheetRepository.findOne(
|
||||
QBalanceSheet.balanceSheet.year.eq(year)
|
||||
.and(QBalanceSheet.balanceSheet.stock.id.eq(stockId))
|
||||
);
|
||||
}
|
||||
|
||||
public Optional<Income> findIncome(Long stockId, Integer year) {
|
||||
return incomeRepository.findOne(
|
||||
QIncome.income.year.eq(year)
|
||||
.and(QIncome.income.stock.id.eq(stockId))
|
||||
);
|
||||
}
|
||||
|
||||
public Optional<CashFlow> findCashFlow(Long stockId, Integer year) {
|
||||
return cashFlowRepository.findOne(
|
||||
QCashFlow.cashFlow.year.eq(year)
|
||||
.and(QCashFlow.cashFlow.stock.id.eq(stockId))
|
||||
);
|
||||
}
|
||||
|
||||
public List<BalanceSheet> findBalanceSheetRecent(Long stockId, int years) {
|
||||
var current = LocalDate.now();
|
||||
return balanceSheetRepository.findAll(
|
||||
QBalanceSheet.balanceSheet.stock.id.eq(stockId)
|
||||
.and(QBalanceSheet.balanceSheet.year.between(current.minusYears(years).getYear(), current.getYear())),
|
||||
Sort.by(Sort.Direction.ASC, BalanceSheet_.YEAR)
|
||||
);
|
||||
}
|
||||
|
||||
public List<Income> findIncomeRecent(Long stockId, int years) {
|
||||
var current = LocalDate.now();
|
||||
return incomeRepository.findAll(
|
||||
QIncome.income.stock.id.eq(stockId)
|
||||
.and(QIncome.income.year.between(current.minusYears(years).getYear(), current.getYear())),
|
||||
Sort.by(Sort.Direction.ASC, Income_.YEAR)
|
||||
);
|
||||
}
|
||||
|
||||
public List<CashFlow> findCashFlowRecent(Long stockId, int years) {
|
||||
var current = LocalDate.now();
|
||||
return cashFlowRepository.findAll(
|
||||
QCashFlow.cashFlow.stock.id.eq(stockId)
|
||||
.and(QCashFlow.cashFlow.year.between(current.minusYears(years).getYear(), current.getYear())),
|
||||
Sort.by(Sort.Direction.ASC, CashFlow_.YEAR)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Task;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.TaskRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.task.TaskMonitorNodes;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import com.yomahub.liteflow.core.FlowExecutor;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250829
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TaskService extends SimpleServiceSupport<Task> {
|
||||
private final TaskRepository taskRepository;
|
||||
private final TaskTemplateService taskTemplateService;
|
||||
private final FlowExecutor flowExecutor;
|
||||
|
||||
public TaskService(TaskRepository repository, TaskTemplateService taskTemplateService, @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") FlowExecutor flowExecutor) {
|
||||
super(repository);
|
||||
this.taskRepository = repository;
|
||||
this.taskTemplateService = taskTemplateService;
|
||||
this.flowExecutor = flowExecutor;
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onApplicationReady() {
|
||||
log.warn("更新所有未完成的任务状态为失败");
|
||||
taskRepository.updateAllRunningTaskToFailure();
|
||||
}
|
||||
|
||||
public void execute(Long templateId, Map<String, Object> params) {
|
||||
var template = taskTemplateService.detail(templateId);
|
||||
var context = new TaskMonitorNodes.TaskMonitorContext(template);
|
||||
flowExecutor.execute2Future(template.getChain(), params, context);
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
public void updateStepById(Integer step, Long id) {
|
||||
taskRepository.updateStepById(step, id);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.TaskTemplate;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.TaskTemplateRepository;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder;
|
||||
import com.yomahub.liteflow.meta.LiteflowMetaOperator;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class TaskTemplateService extends SimpleServiceSupport<TaskTemplate> {
|
||||
public TaskTemplateService(TaskTemplateRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
private void validateExpression(String expression) {
|
||||
var response = LiteFlowChainELBuilder.validateWithEx(expression);
|
||||
if (!response.isSuccess()) {
|
||||
throw new RuntimeException(response.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long save(TaskTemplate entity) {
|
||||
validateExpression(entity.getExpression());
|
||||
Long id = super.save(entity);
|
||||
LiteflowMetaOperator.reloadAllChain();
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Iterable<TaskTemplate> taskTemplates) {
|
||||
taskTemplates.forEach(template -> validateExpression(template.getExpression()));
|
||||
super.save(taskTemplates);
|
||||
LiteflowMetaOperator.reloadAllChain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Iterable<Long> ids) {
|
||||
super.remove(ids);
|
||||
LiteflowMetaOperator.reloadAllChain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Long id) {
|
||||
super.remove(id);
|
||||
LiteflowMetaOperator.reloadAllChain();
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service.task;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
|
||||
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@LiteflowComponent("check_daily")
|
||||
public class CheckDailyNode extends TaskNodeComponent {
|
||||
private final StockRepository stockRepository;
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
private final TuShareService tuShareService;
|
||||
|
||||
public CheckDailyNode(TaskService taskService, StockRepository stockRepository, DailyRepository dailyRepository, TuShareService tuShareService) {
|
||||
super(taskService);
|
||||
this.stockRepository = stockRepository;
|
||||
this.dailyRepository = dailyRepository;
|
||||
this.tuShareService = tuShareService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process() {
|
||||
var reports = new ArrayList<MissedTradeReport>();
|
||||
var stocks = stockRepository.findAll();
|
||||
var exchanges = stocks.stream().map(Stock::getMarket).distinct().toList();
|
||||
for (Stock.Market exchange : exchanges) {
|
||||
var nowDate = LocalDate.now();
|
||||
var allTradeDates = tuShareService.tradeDateList(exchange.name())
|
||||
.data()
|
||||
.items()
|
||||
.stream()
|
||||
.map(item -> LocalDate.parse(item.get(0), TuShareService.TRADE_FORMAT))
|
||||
.filter(date -> date.isBefore(nowDate) || date.isEqual(nowDate))
|
||||
.toList();
|
||||
var total = stocks.size();
|
||||
var progress = 0;
|
||||
for (Stock stock : stocks) {
|
||||
log.info("正在处理:{} {}", stock.getCode(), stock.getName());
|
||||
if (exchange.equals(stock.getMarket())) {
|
||||
var existsTradeDates = dailyRepository.findDistinctTradeDateByStockId(stock.getId());
|
||||
var missedTradeDates = allTradeDates.stream()
|
||||
.filter(date -> date.isAfter(stock.getListedDate()) || date.isEqual(stock.getListedDate()))
|
||||
.filter(date -> !existsTradeDates.contains(date))
|
||||
.filter(date -> {
|
||||
ThreadUtil.safeSleep(100);
|
||||
var response = tuShareService.dailyList(date, stock.getCode());
|
||||
return !response.data().items().isEmpty();
|
||||
})
|
||||
.toList();
|
||||
if (ObjectUtil.isNotEmpty(missedTradeDates)) {
|
||||
reports.add(new MissedTradeReport(
|
||||
stock.getCode(),
|
||||
stock.getName(),
|
||||
missedTradeDates
|
||||
));
|
||||
}
|
||||
}
|
||||
setStep(++progress * 100 / total);
|
||||
}
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(reports)) {
|
||||
var context = getContextBean(TaskMonitorNodes.TaskMonitorContext.class);
|
||||
context.setTaskResult(
|
||||
reports.stream()
|
||||
.map(report -> StrUtil.format("{}({})缺少如下交易日数据:{}", report.name(), report.code(), report.missedTradeDates().stream().map(LocalDate::toString).collect(Collectors.joining(", "))))
|
||||
.collect(Collectors.joining("\n"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public record MissedTradeReport(
|
||||
String code,
|
||||
String name,
|
||||
List<LocalDate> missedTradeDates
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service.task;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Task;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.TaskTemplate;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
|
||||
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||
import com.yomahub.liteflow.annotation.LiteflowFact;
|
||||
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@LiteflowComponent
|
||||
public class TaskMonitorNodes {
|
||||
private final TaskService taskService;
|
||||
|
||||
public TaskMonitorNodes(TaskService taskService) {
|
||||
this.taskService = taskService;
|
||||
}
|
||||
|
||||
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "task_start", nodeName = "任务开始", nodeType = NodeTypeEnum.COMMON)
|
||||
public void taskStart(NodeComponent node) {
|
||||
try {
|
||||
var context = node.getContextBean(TaskMonitorContext.class);
|
||||
var task = new Task();
|
||||
task.setName(context.getTemplate().getName());
|
||||
task.setDescription(context.getTemplate().getDescription());
|
||||
task.setStatus(Task.Status.RUNNING);
|
||||
task.setLaunchedTime(LocalDateTime.now());
|
||||
var taskId = taskService.save(task);
|
||||
context.setTaskId(taskId);
|
||||
} catch (Exception exception) {
|
||||
log.warn("Not in task", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "task_end", nodeName = "任务结束", nodeType = NodeTypeEnum.COMMON)
|
||||
public void taskEnd(NodeComponent node, @LiteflowFact("taskId") Long taskId) {
|
||||
if (ObjectUtil.isNotNull(taskId)) {
|
||||
var task = taskService.detail(taskId);
|
||||
task.setStatus(Task.Status.SUCCESS);
|
||||
task.setStep(100);
|
||||
task.setFinishedTime(LocalDateTime.now());
|
||||
var result = node.<String>getContextValue("taskResult");
|
||||
if (StrUtil.isNotBlank(result)) {
|
||||
task.setResult(result);
|
||||
}
|
||||
taskService.save(task);
|
||||
}
|
||||
}
|
||||
|
||||
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "task_error", nodeName = "任务错误", nodeType = NodeTypeEnum.COMMON)
|
||||
public void taskError(NodeComponent node, @LiteflowFact("taskId") Long taskId) {
|
||||
if (ObjectUtil.isNotNull(taskId)) {
|
||||
var task = taskService.detail(taskId);
|
||||
task.setStatus(Task.Status.FAILURE);
|
||||
task.setFinishedTime(LocalDateTime.now());
|
||||
var exception = node.getSlot().getException();
|
||||
if (ObjectUtil.isNotNull(exception)) {
|
||||
task.setError(exception.getMessage());
|
||||
}
|
||||
taskService.save(task);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static final class TaskMonitorContext {
|
||||
private TaskTemplate template;
|
||||
private Long taskId;
|
||||
private String taskResult;
|
||||
|
||||
public TaskMonitorContext(TaskTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service.task;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import com.yomahub.liteflow.exception.NoSuchContextBeanException;
|
||||
|
||||
public abstract class TaskNodeComponent extends NodeComponent {
|
||||
private final TaskService taskService;
|
||||
|
||||
protected TaskNodeComponent(TaskService taskService) {
|
||||
this.taskService = taskService;
|
||||
}
|
||||
|
||||
protected void setStep(int step) {
|
||||
if (step < 0 || step > 100) {
|
||||
throw new IllegalArgumentException("step must be between 0 and 100");
|
||||
}
|
||||
try {
|
||||
var context = getContextBean(TaskMonitorNodes.TaskMonitorContext.class);
|
||||
taskService.updateStepById(step, context.getTaskId());
|
||||
} catch (NoSuchContextBeanException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service.task;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
|
||||
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
@Slf4j
|
||||
@LiteflowComponent("update_daily")
|
||||
public class UpdateDailyNode extends TaskNodeComponent {
|
||||
private final StockRepository stockRepository;
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
private final TuShareService tuShareService;
|
||||
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
public UpdateDailyNode(TaskService taskService, StockRepository stockRepository, DailyRepository dailyRepository, TuShareService tuShareService, TransactionTemplate transactionTemplate) {
|
||||
super(taskService);
|
||||
this.stockRepository = stockRepository;
|
||||
this.dailyRepository = dailyRepository;
|
||||
this.tuShareService = tuShareService;
|
||||
this.transactionTemplate = transactionTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process() {
|
||||
var tradeDates = new HashSet<LocalDate>();
|
||||
for (String exchange : List.of("SSE", "SZSE", "BSE")) {
|
||||
var response = tuShareService.tradeDateList(exchange);
|
||||
for (List<String> item : response.data().items()) {
|
||||
if (ObjectUtil.isNotEmpty(item) && StrUtil.isNotBlank(item.get(0))) {
|
||||
tradeDates.add(LocalDate.parse(item.get(0), TuShareService.TRADE_FORMAT));
|
||||
}
|
||||
}
|
||||
}
|
||||
var existsTradeDates = dailyRepository.findDistinctTradeDate();
|
||||
var nowDate = LocalDate.now();
|
||||
var stocks = stockRepository.findAll();
|
||||
var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
||||
var allTradeDates = tradeDates.stream()
|
||||
.filter(date -> date.isBefore(nowDate) || date.isEqual(nowDate))
|
||||
.filter(date -> !existsTradeDates.contains(date))
|
||||
.sorted()
|
||||
.toList();
|
||||
var total = new AtomicInteger(allTradeDates.size());
|
||||
allTradeDates.parallelStream()
|
||||
.forEach(tradeDate -> {
|
||||
var factorResponse = tuShareService.factorList(tradeDate);
|
||||
var factorMap = new HashMap<String, Double>();
|
||||
for (List<String> item : factorResponse.data().items()) {
|
||||
factorMap.put(item.get(0), Double.valueOf(item.get(2)));
|
||||
}
|
||||
|
||||
var response = tuShareService.dailyList(tradeDate);
|
||||
transactionTemplate.execute(status -> {
|
||||
try {
|
||||
for (List<String> item : response.data().items()) {
|
||||
var code = item.get(0);
|
||||
if (stocksMap.containsKey(code)) {
|
||||
var stock = stocksMap.get(code);
|
||||
var factor = factorMap.get(code);
|
||||
var daily = new Daily();
|
||||
daily.setTradeDate(tradeDate);
|
||||
daily.setOpen(NumberHelper.parseDouble(item.get(2)));
|
||||
daily.setHigh(NumberUtil.parseDouble(item.get(3)));
|
||||
daily.setLow(NumberUtil.parseDouble(item.get(4)));
|
||||
daily.setClose(NumberUtil.parseDouble(item.get(5)));
|
||||
daily.setPreviousClose(NumberUtil.parseDouble(item.get(6)));
|
||||
daily.setPriceChangeAmount(NumberUtil.parseDouble(item.get(7)));
|
||||
daily.setPriceFluctuationRange(NumberUtil.parseDouble(item.get(8)));
|
||||
daily.setVolume(NumberUtil.parseDouble(item.get(9)));
|
||||
daily.setTurnover(NumberUtil.parseDouble(item.get(10)));
|
||||
daily.setFactor(factor);
|
||||
daily.setStock(stock);
|
||||
dailyRepository.save(daily);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Exception exception) {
|
||||
log.error("Error", exception);
|
||||
status.setRollbackOnly();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service.task;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Income;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QBalanceSheet;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QCashFlow;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QIncome;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.BalanceSheetRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.CashFlowRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.IncomeRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
|
||||
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 更新财务数据
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250911
|
||||
*/
|
||||
@Slf4j
|
||||
@LiteflowComponent("update_finance")
|
||||
public class UpdateFinanceNode extends TaskNodeComponent {
|
||||
private final StockRepository stockRepository;
|
||||
private final IncomeRepository incomeRepository;
|
||||
private final BalanceSheetRepository balanceSheetRepository;
|
||||
private final CashFlowRepository cashFlowRepository;
|
||||
|
||||
private final TuShareService tuShareService;
|
||||
|
||||
public UpdateFinanceNode(TaskService taskService, StockRepository stockRepository, IncomeRepository incomeRepository, BalanceSheetRepository balanceSheetRepository, CashFlowRepository cashFlowRepository, TuShareService tuShareService) {
|
||||
super(taskService);
|
||||
this.stockRepository = stockRepository;
|
||||
this.incomeRepository = incomeRepository;
|
||||
this.balanceSheetRepository = balanceSheetRepository;
|
||||
this.cashFlowRepository = cashFlowRepository;
|
||||
this.tuShareService = tuShareService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process() {
|
||||
var stocks = stockRepository.findAll();
|
||||
var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
||||
var currentYear = LocalDate.now().getYear();
|
||||
for (int year = 1990; year < currentYear; year++) {
|
||||
var response = tuShareService.incomeList(year);
|
||||
for (List<String> item : response.data().items()) {
|
||||
var code = item.get(0);
|
||||
if (!stocksMap.containsKey(code)) {
|
||||
continue;
|
||||
}
|
||||
var stock = stocksMap.get(code);
|
||||
var income = incomeRepository.findOne(
|
||||
QIncome.income.year.eq(year)
|
||||
.and(QIncome.income.stock.code.eq(stock.getCode()))
|
||||
).orElse(new Income());
|
||||
income.setStock(stock);
|
||||
income.setYear(year);
|
||||
income.setBasicEarningsPerShare(NumberHelper.parseDouble(item.get(1)));
|
||||
income.setDilutedEarningsPerShare(NumberHelper.parseDouble(item.get(2)));
|
||||
income.setTotalOperatingRevenue(NumberHelper.parseDouble(item.get(3)));
|
||||
income.setOperatingRevenue(NumberHelper.parseDouble(item.get(4)));
|
||||
income.setTotalOperatingCost(NumberHelper.parseDouble(item.get(5)));
|
||||
income.setOperatingCost(NumberHelper.parseDouble(item.get(6)));
|
||||
income.setSellingExpense(NumberHelper.parseDouble(item.get(7)));
|
||||
income.setAdministrativeExpense(NumberHelper.parseDouble(item.get(8)));
|
||||
income.setFinancialExpense(NumberHelper.parseDouble(item.get(9)));
|
||||
income.setOperatingExpense(NumberHelper.parseDouble(item.get(10)));
|
||||
income.setOperatingProfit(NumberHelper.parseDouble(item.get(11)));
|
||||
income.setAddNonOperatingIncome(NumberHelper.parseDouble(item.get(12)));
|
||||
income.setLessNonOperatingExpense(NumberHelper.parseDouble(item.get(13)));
|
||||
income.setTotalProfit(NumberHelper.parseDouble(item.get(14)));
|
||||
income.setIncomeTaxExpense(NumberHelper.parseDouble(item.get(15)));
|
||||
income.setNetProfitIncludingMinorityInterest(NumberHelper.parseDouble(item.get(16)));
|
||||
income.setNetProfitExcludingMinorityInterest(NumberHelper.parseDouble(item.get(17)));
|
||||
income.setComprehensiveIncomeAttributableToParent(NumberHelper.parseDouble(item.get(18)));
|
||||
income.setComprehensiveIncomeAttributableToMinorityShareholders(NumberHelper.parseDouble(item.get(19)));
|
||||
income.setEarningsBeforeInterestAndTax(NumberHelper.parseDouble(item.get(20)));
|
||||
income.setBeginningUndistributedProfit(NumberHelper.parseDouble(item.get(21)));
|
||||
income.setDistributableProfit(NumberHelper.parseDouble(item.get(22)));
|
||||
income.setResearchAndDevelopmentExpense(NumberHelper.parseDouble(item.get(23)));
|
||||
income.setFinancialExpenseInterestExpense(NumberHelper.parseDouble(item.get(24)));
|
||||
income.setNetProfitFromContinuingOperations(NumberHelper.parseDouble(item.get(25)));
|
||||
income.setNetProfitFromDiscontinuedOperations(NumberHelper.parseDouble(item.get(26)));
|
||||
incomeRepository.save(income);
|
||||
}
|
||||
|
||||
response = tuShareService.balanceList(year);
|
||||
for (List<String> item : response.data().items()) {
|
||||
var code = item.get(0);
|
||||
if (!stocksMap.containsKey(code)) {
|
||||
continue;
|
||||
}
|
||||
var stock = stocksMap.get(code);
|
||||
var balanceSheet = balanceSheetRepository.findOne(
|
||||
QBalanceSheet.balanceSheet.year.eq(year)
|
||||
.and(QBalanceSheet.balanceSheet.stock.code.eq(stock.getCode()))
|
||||
).orElse(new BalanceSheet());
|
||||
balanceSheet.setStock(stock);
|
||||
balanceSheet.setYear(year);
|
||||
balanceSheet.setEndingTotalShares(NumberHelper.parseDouble(item.get(1)));
|
||||
balanceSheet.setCapitalSurplus(NumberHelper.parseDouble(item.get(2)));
|
||||
balanceSheet.setUndistributedProfit(NumberHelper.parseDouble(item.get(3)));
|
||||
balanceSheet.setMonetaryFunds(NumberHelper.parseDouble(item.get(4)));
|
||||
balanceSheet.setAccountsReceivable(NumberHelper.parseDouble(item.get(5)));
|
||||
balanceSheet.setInventories(NumberHelper.parseDouble(item.get(6)));
|
||||
balanceSheet.setTotalCurrentAssets(NumberHelper.parseDouble(item.get(7)));
|
||||
balanceSheet.setLongTermEquityInvestments(NumberHelper.parseDouble(item.get(8)));
|
||||
balanceSheet.setLongTermReceivables(NumberHelper.parseDouble(item.get(9)));
|
||||
balanceSheet.setFixedAssets(NumberHelper.parseDouble(item.get(10)));
|
||||
balanceSheet.setResearchAndDevelopmentExpenditures(NumberHelper.parseDouble(item.get(11)));
|
||||
balanceSheet.setGoodwill(NumberHelper.parseDouble(item.get(12)));
|
||||
balanceSheet.setTotalNonCurrentAssets(NumberHelper.parseDouble(item.get(13)));
|
||||
balanceSheet.setTotalAssets(NumberHelper.parseDouble(item.get(14)));
|
||||
balanceSheet.setLongTermBorrowings(NumberHelper.parseDouble(item.get(15)));
|
||||
balanceSheet.setShortTermBorrowings(NumberHelper.parseDouble(item.get(16)));
|
||||
balanceSheet.setAccountsPayable(NumberHelper.parseDouble(item.get(17)));
|
||||
balanceSheet.setAdvancesReceived(NumberHelper.parseDouble(item.get(18)));
|
||||
balanceSheet.setTotalCurrentLiabilities(NumberHelper.parseDouble(item.get(19)));
|
||||
balanceSheet.setTotalNonCurrentLiabilities(NumberHelper.parseDouble(item.get(20)));
|
||||
balanceSheet.setTotalLiabilities(NumberHelper.parseDouble(item.get(21)));
|
||||
balanceSheet.setTotalShareholdersEquityExcludingMinorityInterest(NumberHelper.parseDouble(item.get(22)));
|
||||
balanceSheet.setTotalShareholdersEquityIncludingMinorityInterest(NumberHelper.parseDouble(item.get(23)));
|
||||
balanceSheet.setTotalLiabilitiesAndShareholdersEquity(NumberHelper.parseDouble(item.get(24)));
|
||||
balanceSheet.setAccountsReceivable(NumberHelper.parseDouble(item.get(25)));
|
||||
balanceSheet.setPayables(NumberHelper.parseDouble(item.get(26)));
|
||||
balanceSheet.setNotesAndAccountsReceivable(NumberHelper.parseDouble(item.get(27)));
|
||||
balanceSheet.setNotesAndAccountsPayable(NumberHelper.parseDouble(item.get(28)));
|
||||
balanceSheet.setOtherReceivablesTotal(NumberHelper.parseDouble(item.get(29)));
|
||||
balanceSheet.setFixedAssetsTotal(NumberHelper.parseDouble(item.get(30)));
|
||||
balanceSheetRepository.save(balanceSheet);
|
||||
}
|
||||
|
||||
response = tuShareService.cashFlowList(year);
|
||||
for (List<String> item : response.data().items()) {
|
||||
var code = item.get(0);
|
||||
if (!stocksMap.containsKey(code)) {
|
||||
continue;
|
||||
}
|
||||
var stock = stocksMap.get(code);
|
||||
var cashFlow = cashFlowRepository.findOne(
|
||||
QCashFlow.cashFlow.year.eq(year)
|
||||
.and(QCashFlow.cashFlow.stock.code.eq(stock.getCode()))
|
||||
).orElse(new CashFlow());
|
||||
NumberHelper.parseDouble(item.get(1));
|
||||
cashFlow.setStock(stock);
|
||||
cashFlow.setYear(year);
|
||||
cashFlow.setNetProfit(NumberHelper.parseDouble(item.get(1)));
|
||||
cashFlow.setFinancialExpense(NumberHelper.parseDouble(item.get(2)));
|
||||
cashFlow.setCashReceivedFromSalesAndServices(NumberHelper.parseDouble(item.get(3)));
|
||||
cashFlow.setSubtotalOfCashInflowsFromOperatingActivities(NumberHelper.parseDouble(item.get(4)));
|
||||
cashFlow.setCashPaidToAndForEmployees(NumberHelper.parseDouble(item.get(5)));
|
||||
cashFlow.setCashPaidForVariousTaxes(NumberHelper.parseDouble(item.get(6)));
|
||||
cashFlow.setNetCashFlowFromOperatingActivities(NumberHelper.parseDouble(item.get(7)));
|
||||
cashFlow.setSubtotalOfCashInflowsFromInvestingActivities(NumberHelper.parseDouble(item.get(8)));
|
||||
cashFlow.setCashPaidForLongTermAssets(NumberHelper.parseDouble(item.get(9)));
|
||||
cashFlow.setSubtotalOfCashOutflowsFromInvestingActivities(NumberHelper.parseDouble(item.get(10)));
|
||||
cashFlow.setSubtotalOfCashOutflowsFromFinancingActivities(NumberHelper.parseDouble(item.get(11)));
|
||||
cashFlow.setBeginningBalanceOfCashAndCashEquivalents(NumberHelper.parseDouble(item.get(12)));
|
||||
cashFlowRepository.save(cashFlow);
|
||||
}
|
||||
|
||||
setStep((year - 1990) * 100 / (currentYear - 1990));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service.task;
|
||||
|
||||
import cn.hutool.core.util.EnumUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
|
||||
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@LiteflowComponent("update_stock")
|
||||
public class UpdateStockNode extends NodeComponent {
|
||||
private final StockRepository stockRepository;
|
||||
|
||||
private final TuShareService tuShareService;
|
||||
|
||||
public UpdateStockNode(StockRepository stockRepository, TuShareService tuShareService) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.tuShareService = tuShareService;
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@Override
|
||||
public void process() {
|
||||
var stocks = stockRepository.findAll();
|
||||
var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
||||
var targetCodes = new HashSet<String>();
|
||||
tuShareService.stockList()
|
||||
.data()
|
||||
.items()
|
||||
.forEach(item -> {
|
||||
var code = item.get(0);
|
||||
var name = item.get(1);
|
||||
var fullname = item.get(2);
|
||||
var market = EnumUtil.fromString(Stock.Market.class, item.get(3));
|
||||
var industry = item.get(4);
|
||||
var listedDate = LocalDate.parse(item.get(5), TuShareService.TRADE_FORMAT);
|
||||
if (stocksMap.containsKey(code)) {
|
||||
var stock = stocksMap.get(code);
|
||||
stock.setName(name);
|
||||
stock.setFullname(fullname);
|
||||
stock.setMarket(market);
|
||||
stock.setIndustry(industry);
|
||||
stock.setListedDate(listedDate);
|
||||
} else {
|
||||
var stock = new Stock();
|
||||
stock.setCode(code);
|
||||
stock.setName(name);
|
||||
stock.setFullname(fullname);
|
||||
stock.setMarket(market);
|
||||
stock.setIndustry(industry);
|
||||
stock.setListedDate(listedDate);
|
||||
stocks.add(stock);
|
||||
}
|
||||
targetCodes.add(code);
|
||||
});
|
||||
var deleteStocks = stocks.stream()
|
||||
.filter(stock -> !targetCodes.contains(stock.getCode()))
|
||||
.map(Stock::getId)
|
||||
.toList();
|
||||
stockRepository.deleteByIds(deleteStocks);
|
||||
|
||||
stockRepository.saveAll(stocks);
|
||||
}
|
||||
}
|
||||
3
leopard-server/src/main/resources/application-dev.yml
Normal file
3
leopard-server/src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://127.0.0.1:6785/leopard_dev
|
||||
14
leopard-server/src/main/resources/application-h2.yml
Normal file
14
leopard-server/src/main/resources/application-h2.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:file:./leopard;DB_CLOSE_ON_EXIT=TRUE
|
||||
username: leopard
|
||||
password: leopard
|
||||
driver-class-name: org.h2.Driver
|
||||
quartz:
|
||||
jdbc:
|
||||
platform: h2
|
||||
properties:
|
||||
org:
|
||||
quartz:
|
||||
jobStore:
|
||||
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
|
||||
@@ -22,9 +22,8 @@ spring:
|
||||
startup-delay: 30s
|
||||
job-store-type: jdbc
|
||||
jdbc:
|
||||
# platform: mysql
|
||||
platform: postgres
|
||||
# initialize-schema: always
|
||||
initialize-schema: always
|
||||
properties:
|
||||
org:
|
||||
quartz:
|
||||
@@ -32,13 +31,3 @@ spring:
|
||||
driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
|
||||
fenix:
|
||||
print-banner: false
|
||||
liteflow:
|
||||
print-banner: false
|
||||
check-node-exists: false
|
||||
rule-source-ext-data-map:
|
||||
applicationName: ${spring.application.name}
|
||||
sqlLogEnabled: true
|
||||
chainTableName: leopard_task_template
|
||||
chainApplicationNameField: application
|
||||
chainNameField: chain
|
||||
elDataField: expression_el
|
||||
|
||||
@@ -28,14 +28,10 @@
|
||||
</appender>
|
||||
|
||||
<logger name="com.zaxxer.hikari" level="ERROR"/>
|
||||
<!--<logger name="org.hibernate.SQL" level="DEBUG"/>-->
|
||||
|
||||
<springProfile name="build">
|
||||
<appender-ref ref="Console"/>
|
||||
<appender-ref ref="RollingFile"/>
|
||||
</springProfile>
|
||||
<logger name="org.hibernate.SQL" level="DEBUG"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="Console"/>
|
||||
<appender-ref ref="RollingFile"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.lanyuanxiaoyao.leopard.server;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.server.helper.MdHelper;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250917
|
||||
*/
|
||||
public class MdTest {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(
|
||||
MdHelper.of()
|
||||
.bigTitle("Markdown Helper")
|
||||
.title("Markdown Helper")
|
||||
.subTitle("Markdown Helper")
|
||||
.code("java")
|
||||
.text("System.out.println()")
|
||||
.endCode()
|
||||
.divider()
|
||||
.table()
|
||||
.data(new Object[]{"name", "value"}, new Object[][]{new Object[]{"1", "2"}, new Object[]{"3", "4"}})
|
||||
.endTable()
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,8 @@ Content-Type: application/json
|
||||
"api_name": "adj_factor",
|
||||
"token": "{{api_key}}",
|
||||
"params": {
|
||||
"trade_date": "20241231"
|
||||
"ts_code": "600018.SH",
|
||||
"trade_date": "20060331"
|
||||
},
|
||||
"fields": [
|
||||
"ts_code",
|
||||
@@ -126,16 +127,18 @@ Content-Type: application/json
|
||||
"api_name": "stk_factor_pro",
|
||||
"token": "{{api_key}}",
|
||||
"params": {
|
||||
"ts_code": "000001.SZ",
|
||||
"trade_date": "20250225"
|
||||
"ts_code": "600018.SH",
|
||||
"trade_date": "20060331"
|
||||
},
|
||||
"fields": [
|
||||
"ts_code",
|
||||
"trade_date",
|
||||
"open",
|
||||
"open_qfq",
|
||||
"open_hfq",
|
||||
"close",
|
||||
"close_qfq",
|
||||
"close_hfq",
|
||||
"ema_hfq_5"
|
||||
"close_hfq"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -144,18 +147,15 @@ POST {{api_url}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"api_name": "income",
|
||||
"api_name": "income_vip",
|
||||
"token": "{{api_key}}",
|
||||
"params": {
|
||||
"ts_code": "000001.SZ",
|
||||
"period": "20241231"
|
||||
"ts_code": "000799.SZ",
|
||||
"period": "20191231"
|
||||
},
|
||||
"fields": [
|
||||
"ts_code",
|
||||
"ann_date",
|
||||
"total_revenue",
|
||||
"total_cogs",
|
||||
"n_income"
|
||||
"revenue",
|
||||
"total_revenue"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ POST {{api_url}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"api_name": "cashflow",
|
||||
"api_name": "cashflow_vip",
|
||||
"token": "{{api_key}}",
|
||||
"params": {
|
||||
"ts_code": "000002.SZ",
|
||||
@@ -196,41 +196,12 @@ Content-Type: application/json
|
||||
"api_name": "balancesheet_vip",
|
||||
"token": "{{api_key}}",
|
||||
"params": {
|
||||
"ts_code": "000001.SZ",
|
||||
"period": "20241231"
|
||||
"ts_code": "000799.SZ",
|
||||
"period": "20191231"
|
||||
},
|
||||
"fields": [
|
||||
"ts_code",
|
||||
"total_share",
|
||||
"cap_rese",
|
||||
"undistr_porfit",
|
||||
"money_cap",
|
||||
"accounts_receiv",
|
||||
"inventories",
|
||||
"total_cur_assets",
|
||||
"lt_eqt_invest",
|
||||
"lt_rec",
|
||||
"fix_assets",
|
||||
"r_and_d",
|
||||
"goodwill",
|
||||
"total_nca",
|
||||
"total_assets",
|
||||
"lt_borr",
|
||||
"st_borr",
|
||||
"acct_payable",
|
||||
"adv_receipts",
|
||||
"total_cur_liab",
|
||||
"total_ncl",
|
||||
"total_liab",
|
||||
"total_hldr_eqy_exc_min_int",
|
||||
"total_hldr_eqy_inc_min_int",
|
||||
"total_liab_hldr_eqy",
|
||||
"acc_receivable",
|
||||
"payables",
|
||||
"accounts_receiv_bill",
|
||||
"accounts_pay",
|
||||
"oth_rcv_total",
|
||||
"fix_assets_total"
|
||||
"total_assets"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -242,13 +213,16 @@ Content-Type: application/json
|
||||
"api_name": "fina_indicator",
|
||||
"token": "{{api_key}}",
|
||||
"params": {
|
||||
"ts_code": "000002.SZ",
|
||||
"ts_code": "001400.SZ",
|
||||
"period": "20231231"
|
||||
},
|
||||
"fields": [
|
||||
"ann_date",
|
||||
"ocf_to_shortdebt",
|
||||
"currentdebt_to_debt"
|
||||
"ts_code",
|
||||
"current_ratio",
|
||||
"quick_ratio",
|
||||
"invturn_days",
|
||||
"arturn_days",
|
||||
"ar_turn"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -268,3 +242,25 @@ Content-Type: application/json
|
||||
"resume_date"
|
||||
]
|
||||
}
|
||||
|
||||
### Get Index
|
||||
POST {{api_url}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"api_name": "index_basic",
|
||||
"token": "{{api_key}}",
|
||||
"params": {
|
||||
"market": "CSI"
|
||||
},
|
||||
"fields": [
|
||||
"ts_code",
|
||||
"name",
|
||||
"fullname",
|
||||
"market",
|
||||
"publisher",
|
||||
"index_type",
|
||||
"category",
|
||||
"desc"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,14 +21,27 @@
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.ralfkonrad.quantlib_for_maven</groupId>
|
||||
<artifactId>quantlib</artifactId>
|
||||
<version>1.39.0</version>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ta4j</groupId>
|
||||
<artifactId>ta4j-core</artifactId>
|
||||
<version>0.17</version>
|
||||
<groupId>org.commonmark</groupId>
|
||||
<artifactId>commonmark</artifactId>
|
||||
<version>0.26.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.commonmark</groupId>
|
||||
<artifactId>commonmark-ext-gfm-tables</artifactId>
|
||||
<version>0.26.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -41,6 +54,11 @@
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,69 +1,292 @@
|
||||
package com.lanyuanxiaoyao.leopard.strategy;
|
||||
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.template.TemplateConfig;
|
||||
import cn.hutool.extra.template.TemplateEngine;
|
||||
import cn.hutool.extra.template.TemplateUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily_;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.QStock;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.Monthly;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.Weekly;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.YearAndMonth;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.dto.YearAndWeek;
|
||||
import com.lanyuanxiaoyao.leopard.core.helper.TaHelper;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockCollectionRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.strategy.TradeEngine;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneId;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.ta4j.core.BaseBar;
|
||||
import org.ta4j.core.BaseBarSeriesBuilder;
|
||||
import org.ta4j.core.indicators.EMAIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
import org.ta4j.core.num.DoubleNum;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static com.lanyuanxiaoyao.leopard.core.helper.TaHelper.maxFromDaily;
|
||||
import static com.lanyuanxiaoyao.leopard.core.helper.TaHelper.minFromDaily;
|
||||
import static com.lanyuanxiaoyao.leopard.core.helper.TaHelper.sumFromDaily;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootApplication(scanBasePackages = "com.lanyuanxiaoyao.leopard")
|
||||
@EnableJpaAuditing
|
||||
public class StrategyApplication {
|
||||
private static final TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
|
||||
|
||||
@Resource
|
||||
private TradeEngine tradeEngine;
|
||||
@Resource
|
||||
private StockRepository stockRepository;
|
||||
@Resource
|
||||
private DailyRepository dailyRepository;
|
||||
@Resource
|
||||
private StockCollectionRepository stockCollectionRepository;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(StrategyApplication.class, args);
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@Transactional(readOnly = true)
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void test() {
|
||||
var dailies = dailyRepository.findAll(QDaily.daily.stock.code.eq("000001.SZ"), Sort.by(Daily_.TRADE_DATE));
|
||||
var series = new BaseBarSeriesBuilder()
|
||||
.withNumTypeOf(DoubleNum.class)
|
||||
.build();
|
||||
log.info("{}", dailies.size());
|
||||
for (Daily daily : dailies) {
|
||||
series.addBar(new BaseBar(
|
||||
Duration.ofDays(1),
|
||||
daily.getTradeDate().plusDays(1).atStartOfDay(ZoneId.systemDefault()),
|
||||
DoubleNum.valueOf(daily.getOpen() * daily.getFactor()),
|
||||
DoubleNum.valueOf(daily.getHigh() * daily.getFactor()),
|
||||
DoubleNum.valueOf(daily.getLow() * daily.getFactor()),
|
||||
DoubleNum.valueOf(daily.getClose() * daily.getFactor()),
|
||||
DoubleNum.valueOf(daily.getVolume()),
|
||||
DoubleNum.valueOf(daily.getPriceChangeAmount()),
|
||||
daily.getTurnover().longValue()
|
||||
));
|
||||
}
|
||||
var ema = new EMAIndicator(new ClosePriceIndicator(series), 5);
|
||||
var emaValues = ema.stream().toList();
|
||||
log.info("{}", emaValues.size());
|
||||
public void backtest() throws IOException {
|
||||
var startDate = LocalDate.of(2024, 12, 1);
|
||||
var endDate = LocalDate.of(2024, 12, 31);
|
||||
var charts = new ArrayList<Dict>();
|
||||
List.of("000048.SZ", "000333.SZ", "000568.SZ", "000596.SZ", "000651.SZ", "000848.SZ", "000858.SZ", "000933.SZ", "002027.SZ", "002032.SZ", "002142.SZ", "002192.SZ", "002415.SZ", "002432.SZ", "002475.SZ", "002517.SZ", "002555.SZ", "002648.SZ", "002756.SZ", "002847.SZ", "600036.SH", "600096.SH"/*, "600132.SH", "600188.SH", "600309.SH", "600426.SH", "600436.SH", "600519.SH", "600546.SH", "600563.SH", "600702.SH", "600779.SH", "600803.SH", "600809.SH", "600961.SH", "601001.SH", "601100.SH", "601138.SH", "601225.SH", "601899.SH", "601919.SH", "603195.SH", "603198.SH", "603288.SH", "603369.SH", "603444.SH", "603565.SH", "603568.SH", "603605.SH", "603688.SH"*/)
|
||||
.parallelStream()
|
||||
.forEach(code -> {
|
||||
var stock = stockRepository.findOne(QStock.stock.code.eq(code)).orElseThrow();
|
||||
var asset = tradeEngine.backtest(
|
||||
stockRepository.findById(stock.getId()).orElseThrow(),
|
||||
(now, currentAsset, dailies) -> {
|
||||
var stockDailies = dailies
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(Daily::getTradeDate))
|
||||
.toList();
|
||||
var yesterday = stockDailies.getLast();
|
||||
if (yesterday.getHfqClose() > yesterday.getHfqOpen()) {
|
||||
return 100;
|
||||
} else if (yesterday.getHfqClose() < yesterday.getHfqOpen()) {
|
||||
var hold = currentAsset.getVolume();
|
||||
if (hold > 0) {
|
||||
return -1 * hold;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
startDate,
|
||||
endDate
|
||||
);
|
||||
var sources = dailyRepository.findAll(
|
||||
QDaily.daily.stock.code.eq(code)
|
||||
.and(QDaily.daily.tradeDate.after(startDate.minusDays(150)))
|
||||
.and(QDaily.daily.tradeDate.before(endDate)),
|
||||
QDaily.daily.tradeDate.asc()
|
||||
);
|
||||
|
||||
for (int index = 0; index < dailies.size(); index++) {
|
||||
var daily = dailies.get(index);
|
||||
var emaValue = emaValues.get(index);
|
||||
log.info("{} {} {} {} {}", daily.getTradeDate().toString(), daily.getClose(), daily.getFactor(), daily.getClose() * daily.getFactor(), emaValue.doubleValue());
|
||||
}
|
||||
var dailies = sources.stream()
|
||||
.filter(daily -> daily.getTradeDate().isAfter(startDate) && daily.getTradeDate().isBefore(endDate))
|
||||
.sorted(Comparator.comparing(Daily::getTradeDate))
|
||||
.toList();
|
||||
var oclhList = new HashMap<>();
|
||||
var dailyCloseMapping = new HashMap<String, Double>();
|
||||
for (var daily : dailies) {
|
||||
oclhList.put(
|
||||
daily.getTradeDate().toString(),
|
||||
List.of(daily.getHfqOpen(), daily.getHfqClose(), daily.getHfqLow(), daily.getHfqHigh())
|
||||
);
|
||||
dailyCloseMapping.put(daily.getTradeDate().toString(), daily.getHfqClose());
|
||||
}
|
||||
charts.add(
|
||||
Dict.create()
|
||||
.set("title", code)
|
||||
.set("type", "candle")
|
||||
.set(
|
||||
"data",
|
||||
Dict.create()
|
||||
.set(
|
||||
"日线",
|
||||
Dict.create()
|
||||
.set("type", "candle")
|
||||
.set("oclh", oclhList)
|
||||
.set(
|
||||
"points",
|
||||
asset.getTrades()
|
||||
.stream()
|
||||
.filter(trade -> trade.volume() != 0)
|
||||
.map(trade -> Dict.create()
|
||||
.set("value", trade.volume())
|
||||
.set("itemStyle", Dict.create()
|
||||
.set("color", trade.volume() > 0 ? "#e5b8b5" : "#b5e2e5")
|
||||
)
|
||||
.set("coord", List.of(trade.date().toString(), dailyCloseMapping.getOrDefault(trade.date().toString(), 0.0)))
|
||||
)
|
||||
.toList()
|
||||
)
|
||||
)
|
||||
.set(
|
||||
"资金",
|
||||
Dict.create()
|
||||
.set("type", "line")
|
||||
)
|
||||
)
|
||||
);
|
||||
/*log.info("Final Cash: {}", asset.getCash());
|
||||
for (var history : asset.getHistories()) {
|
||||
log.info("Date: {} Cash: {} Trade: {}", history.date(), history.cash(), history.trades().values());
|
||||
}*/
|
||||
});
|
||||
var template = engine.getTemplate("backtest_report.html");
|
||||
Files.writeString(Path.of("backtest_report.html"), template.render(
|
||||
Dict.create().set("charts", charts)
|
||||
));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void test() throws IOException {
|
||||
var dailyRange = 150;
|
||||
var weekRange = 24;
|
||||
var monthRange = 12;
|
||||
var charts = Dict.create();
|
||||
List.of("000048.SZ", "000333.SZ", "000568.SZ", "000596.SZ", "000651.SZ", "000848.SZ"/*, "000858.SZ", "000933.SZ", "002027.SZ", "002032.SZ", "002142.SZ", "002192.SZ", "002415.SZ", "002432.SZ", "002475.SZ", "002517.SZ", "002555.SZ", "002648.SZ", "002756.SZ", "002847.SZ", "600036.SH", "600096.SH", "600132.SH", "600188.SH", "600309.SH", "600426.SH", "600436.SH", "600519.SH", "600546.SH", "600563.SH", "600702.SH", "600779.SH", "600803.SH", "600809.SH", "600961.SH", "601001.SH", "601100.SH", "601138.SH", "601225.SH", "601899.SH", "601919.SH", "603195.SH", "603198.SH", "603288.SH", "603369.SH", "603444.SH", "603565.SH", "603568.SH", "603605.SH", "603688.SH"*/)
|
||||
.parallelStream()
|
||||
.forEach(code -> {
|
||||
var sources = dailyRepository.findAll(
|
||||
QDaily.daily.stock.code.eq(code)
|
||||
.and(QDaily.daily.tradeDate.after(LocalDate.now().minusMonths(12))),
|
||||
QDaily.daily.tradeDate.asc()
|
||||
);
|
||||
|
||||
var dailies = sources.stream()
|
||||
.filter(daily -> daily.getTradeDate().isAfter(LocalDate.now().minusDays(dailyRange)))
|
||||
.sorted(Comparator.comparing(Daily::getTradeDate))
|
||||
.toList();
|
||||
var dailyXList = new ArrayList<String>();
|
||||
var dailyYList = new ArrayList<List<Double>>();
|
||||
for (var daily : dailies) {
|
||||
dailyXList.add(daily.getTradeDate().toString());
|
||||
dailyYList.add(List.of(daily.getHfqOpen(), daily.getHfqClose(), daily.getHfqLow(), daily.getHfqHigh()));
|
||||
}
|
||||
|
||||
// 30日均线和均线斜率
|
||||
var sma30 = TaHelper.sma(sources, 30, Daily::getHfqClose).subList(sources.size() - dailyRange, sources.size());
|
||||
/*var slopes = new ArrayList<Double>();
|
||||
slopes.add(0.0);
|
||||
for (int i = 1; i < sma30.size(); i++) {
|
||||
slopes.add(((sma30.get(i) - sma30.get(i - 1)) * 1000.0) / sma30.get(i - 1));
|
||||
}*/
|
||||
var sma60 = TaHelper.sma(sources, 60, Daily::getHfqClose).subList(sources.size() - dailyRange, sources.size());
|
||||
|
||||
charts.set(
|
||||
StrUtil.format("日线 {}", code),
|
||||
Dict.create()
|
||||
.set("xList", dailyXList)
|
||||
.set("yList", dailyYList)
|
||||
.set("sma30", sma30)
|
||||
.set("sma60", sma60)
|
||||
// .set("sma30Slopes", slopes)
|
||||
);
|
||||
|
||||
var weeklies = sources.stream()
|
||||
.filter(daily -> daily.getTradeDate().isAfter(LocalDate.now().minusWeeks(weekRange)))
|
||||
.collect(Collectors.groupingBy(daily -> new YearAndWeek(daily.getTradeDate().getYear(), daily.getTradeDate().get(WeekFields.ISO.weekOfYear()))))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> {
|
||||
var yearAndWeek = entry.getKey();
|
||||
var subDailies = entry.getValue();
|
||||
var open = subDailies.getFirst().getHfqOpen();
|
||||
var close = subDailies.getLast().getHfqClose();
|
||||
return new Weekly(
|
||||
LocalDate.of(yearAndWeek.year(), 1, 1).with(WeekFields.ISO.weekOfYear(), yearAndWeek.week()),
|
||||
yearAndWeek.year(),
|
||||
yearAndWeek.week(),
|
||||
open,
|
||||
maxFromDaily(subDailies, Daily::getHfqHigh),
|
||||
minFromDaily(subDailies, Daily::getHfqLow),
|
||||
close,
|
||||
close - open,
|
||||
(close - open) / open * 100,
|
||||
sumFromDaily(subDailies, Daily::getVolume),
|
||||
sumFromDaily(subDailies, Daily::getTurnover)
|
||||
);
|
||||
})
|
||||
.sorted(Comparator.comparingInt(weekly -> weekly.year() * 100 + weekly.week()))
|
||||
.toList();
|
||||
|
||||
var weekXList = new ArrayList<String>();
|
||||
var weekYList = new ArrayList<List<Double>>();
|
||||
for (var weekly : weeklies) {
|
||||
weekXList.add(weekly.tradeDate().toString());
|
||||
weekYList.add(List.of(weekly.open(), weekly.close(), weekly.low(), weekly.high()));
|
||||
}
|
||||
|
||||
charts.set(
|
||||
StrUtil.format("周线 {}", code),
|
||||
Dict.create()
|
||||
.set("xList", weekXList)
|
||||
.set("yList", weekYList)
|
||||
);
|
||||
|
||||
var monthlies = sources.stream()
|
||||
.filter(daily -> daily.getTradeDate().isAfter(LocalDate.now().minusMonths(monthRange)))
|
||||
.collect(Collectors.groupingBy(daily -> new YearAndMonth(daily.getTradeDate().getYear(), daily.getTradeDate().getMonthValue())))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> {
|
||||
var yearAndMonth = entry.getKey();
|
||||
var subDailies = entry.getValue();
|
||||
var open = subDailies.getFirst().getHfqOpen();
|
||||
var close = subDailies.getLast().getHfqClose();
|
||||
return new Monthly(
|
||||
LocalDate.of(yearAndMonth.year(), yearAndMonth.month(), 1),
|
||||
yearAndMonth.year(),
|
||||
yearAndMonth.month(),
|
||||
open,
|
||||
maxFromDaily(subDailies, Daily::getHfqHigh),
|
||||
minFromDaily(subDailies, Daily::getHfqLow),
|
||||
close,
|
||||
close - open,
|
||||
(close - open) / open * 100,
|
||||
sumFromDaily(subDailies, Daily::getVolume),
|
||||
sumFromDaily(subDailies, Daily::getTurnover)
|
||||
);
|
||||
})
|
||||
.sorted(Comparator.comparingInt(monthly -> monthly.year() * 100 + monthly.month()))
|
||||
.toList();
|
||||
|
||||
var monthXList = new ArrayList<String>();
|
||||
var monthYList = new ArrayList<List<Double>>();
|
||||
for (var month : monthlies) {
|
||||
monthXList.add(month.tradeDate().toString());
|
||||
monthYList.add(List.of(month.open(), month.close(), month.low(), month.high()));
|
||||
}
|
||||
|
||||
charts.set(
|
||||
StrUtil.format("月线 {}", code),
|
||||
Dict.create()
|
||||
.set("xList", monthXList)
|
||||
.set("yList", monthYList)
|
||||
);
|
||||
});
|
||||
|
||||
var template = engine.getTemplate("report.html");
|
||||
Files.writeString(Path.of("report.html"), template.render(
|
||||
Dict.create().set("charts", charts)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,16 @@ spring:
|
||||
application:
|
||||
name: leopard-strategy
|
||||
datasource:
|
||||
url: jdbc:postgresql://192.168.31.127:6785/leopard
|
||||
url: jdbc:postgresql://81.71.3.24:6785/leopard
|
||||
username: leopard
|
||||
password: '9NEzFzovnddf@PyEP?e*AYAWnCyd7UhYwQK$pJf>7?ccFiN^x4$eKEZ5~E<7<+~X'
|
||||
driver-class-name: org.postgresql.Driver
|
||||
jpa:
|
||||
generate-ddl: false
|
||||
main:
|
||||
banner-mode: off
|
||||
fenix:
|
||||
print-banner: false
|
||||
liteflow:
|
||||
print-banner: false
|
||||
check-node-exists: false
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.zaxxer.hikari" level="ERROR"/>
|
||||
<logger name="com.lanyuanxiaoyao.leopard" level="INFO"/>
|
||||
<logger name="org.hibernate.SQL" level="DEBUG"/>
|
||||
|
||||
<root level="INFO">
|
||||
<root level="ERROR">
|
||||
<appender-ref ref="Console"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,391 @@
|
||||
<html lang='zh'>
|
||||
<head>
|
||||
<meta charset='utf-8'/>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
|
||||
<meta content='width=device-width, initial-scale=1.0' name='viewport'/>
|
||||
<title>Strategy</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/antd.min.css" rel="stylesheet"/>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/helper.min.css" rel="stylesheet"/>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/iconfont.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
html, body, #root {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'></div>
|
||||
</body>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/sdk.min.js"></script>
|
||||
<script th:inline="javascript" type='text/javascript'>
|
||||
// 全局配置(颜色、尺寸、间距等),集中管理,便于统一调整
|
||||
const CONFIG = {
|
||||
colors: {up: '#000000FF', down: '#00000045'},
|
||||
grid: {left: '2%', right: '2%', top: 40, bottom: 110},
|
||||
zoom: {bottom: 16, height: 50},
|
||||
linewidth: {stem: 1.5, openTick: 1.2, closeTick: 1.6, closeLine: 1.5},
|
||||
tick: {min: 4, max: 10, scale: 0.4},
|
||||
}
|
||||
|
||||
// 通用 tooltip 格式化(按索引回读原始 O/H/L/C)
|
||||
function makeTooltipFormatter(dataMap, dates) {
|
||||
return function (params) {
|
||||
let p = Array.isArray(params) ? params[0] : params
|
||||
let idx = p.dataIndex
|
||||
let d = dates[idx]
|
||||
let ohlc = dataMap[d] || []
|
||||
let o = ohlc[0], c = ohlc[1], l = ohlc[2], h = ohlc[3]
|
||||
let chg = (c - o)
|
||||
let chgPct = o ? (chg / o * 100) : 0
|
||||
let sign = chg >= 0 ? '+' : ''
|
||||
return [
|
||||
d,
|
||||
'O: ' + o,
|
||||
'C: ' + c,
|
||||
'H: ' + h,
|
||||
'L: ' + l,
|
||||
'Chg: ' + sign + chg.toFixed(2) + ' (' + sign + chgPct.toFixed(2) + '%)',
|
||||
].join('<br/>')
|
||||
}
|
||||
}
|
||||
|
||||
// 通用基础配置构建(legend/tooltip/grid/xAxis/yAxis/dataZoom)
|
||||
function buildBaseOption(dates, series, formatter) {
|
||||
return {
|
||||
animation: false,
|
||||
legend: {show: false},
|
||||
tooltip: {trigger: 'axis', axisPointer: {type: 'cross'}, formatter},
|
||||
grid: CONFIG.grid,
|
||||
xAxis: {type: 'category', data: dates, boundaryGap: true, axisLine: {onZero: false}},
|
||||
yAxis: {scale: true},
|
||||
dataZoom: [
|
||||
{type: 'inside', xAxisIndex: 0, start: 0, end: 100},
|
||||
{
|
||||
show: true,
|
||||
type: 'slider',
|
||||
xAxisIndex: 0,
|
||||
bottom: CONFIG.zoom.bottom,
|
||||
height: CONFIG.zoom.height,
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series,
|
||||
}
|
||||
}
|
||||
|
||||
// Range Band + Close Line(高低区间带 + 收盘线):趋势与波动范围直观
|
||||
function buildRangeCloseOption(dataMap) {
|
||||
const dates = Object.keys(dataMap).sort()
|
||||
const lowArr = dates.map(d => dataMap[d][2])
|
||||
const highArr = dates.map(d => dataMap[d][3])
|
||||
const closeArr = dates.map(d => dataMap[d][1])
|
||||
const rangeArr = highArr.map((h, i) => h - lowArr[i])
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: 'Low',
|
||||
type: 'line',
|
||||
data: lowArr,
|
||||
stack: 'range',
|
||||
symbol: 'none',
|
||||
lineStyle: {width: 0},
|
||||
emphasis: {disabled: true},
|
||||
},
|
||||
{
|
||||
name: 'Range',
|
||||
type: 'line',
|
||||
data: rangeArr,
|
||||
stack: 'range',
|
||||
symbol: 'none',
|
||||
lineStyle: {width: 0},
|
||||
areaStyle: {color: CONFIG.colors.down, opacity: 0.6},
|
||||
z: 2,
|
||||
},
|
||||
{
|
||||
name: 'Close',
|
||||
type: 'line',
|
||||
data: closeArr,
|
||||
symbol: 'none',
|
||||
lineStyle: {color: CONFIG.colors.up, width: CONFIG.linewidth.closeLine},
|
||||
z: 3,
|
||||
},
|
||||
]
|
||||
|
||||
return buildBaseOption(dates, series, makeTooltipFormatter(dataMap, dates))
|
||||
}
|
||||
|
||||
// Minimal OHLC(竖线 + 左/右短横):信息等价于K线但形态简洁
|
||||
function buildOHLCMinimalOption(dataMap) {
|
||||
const dates = Object.keys(dataMap).sort()
|
||||
const ohlcData = dates.map((d, i) => [i, ...dataMap[d]])
|
||||
|
||||
// 自定义渲染:竖线=高低区间;左短横=开盘;右短横=收盘;颜色=涨跌
|
||||
function renderOHLCMinimal(params, api) {
|
||||
let idx = api.value(0)
|
||||
let open = api.value(1)
|
||||
let close = api.value(2)
|
||||
let low = api.value(3)
|
||||
let high = api.value(4)
|
||||
let up = close >= open
|
||||
|
||||
let x = api.coord([idx, 0])[0]
|
||||
let highPoint = api.coord([idx, high])
|
||||
let lowPoint = api.coord([idx, low])
|
||||
let openPoint = api.coord([idx, open])
|
||||
let closePoint = api.coord([idx, close])
|
||||
|
||||
let band = api.size([1, 0])[0]
|
||||
let tick = Math.max(CONFIG.tick.min, Math.min(CONFIG.tick.max, band * CONFIG.tick.scale))
|
||||
let color = up ? CONFIG.colors.up : CONFIG.colors.down
|
||||
|
||||
return {
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
type: 'line',
|
||||
shape: {x1: x, y1: highPoint[1], x2: x, y2: lowPoint[1]},
|
||||
style: {stroke: color, lineWidth: CONFIG.linewidth.stem, opacity: 0.9},
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
shape: {x1: x - tick, y1: openPoint[1], x2: x, y2: openPoint[1]},
|
||||
style: {stroke: color, lineWidth: CONFIG.linewidth.openTick, opacity: 0.95},
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
shape: {x1: x, y1: closePoint[1], x2: x + tick, y2: closePoint[1]},
|
||||
style: {stroke: color, lineWidth: CONFIG.linewidth.closeTick, opacity: 0.95},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: 'ohlc',
|
||||
type: 'custom',
|
||||
renderItem: renderOHLCMinimal,
|
||||
encode: {x: 0, y: [1, 2, 3, 4]},
|
||||
data: ohlcData,
|
||||
z: 10,
|
||||
},
|
||||
]
|
||||
|
||||
return buildBaseOption(dates, series, makeTooltipFormatter(dataMap, dates))
|
||||
}
|
||||
|
||||
function candleChart(title, data) {
|
||||
return {
|
||||
type: 'service',
|
||||
data: data,
|
||||
body: {
|
||||
className: 'mt-2',
|
||||
type: 'chart',
|
||||
height: 800,
|
||||
config: {
|
||||
title: {
|
||||
text: title,
|
||||
},
|
||||
...buildRangeCloseOption(data['oclh']),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function lineChart(title, data) {
|
||||
return {
|
||||
type: 'service',
|
||||
data: data,
|
||||
body: {
|
||||
className: 'mt-2',
|
||||
type: 'chart',
|
||||
height: 800,
|
||||
config: {
|
||||
title: {
|
||||
text: title,
|
||||
},
|
||||
backgroundColor: '#fff',
|
||||
animation: true,
|
||||
animationDuration: 1000,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
},
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
borderColor: '#333',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
},
|
||||
padding: 12,
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
top: '15%',
|
||||
bottom: '15%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
data: '${xList || []}',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
position: 'left',
|
||||
scale: true,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontWeight: 'bold',
|
||||
formatter: function (value) {
|
||||
return value.toFixed(2)
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#f0f0f0',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
position: 'right',
|
||||
scale: true,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontWeight: 'bold',
|
||||
formatter: function (value) {
|
||||
return value.toFixed(2)
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#f0f0f0',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
scale: true,
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
show: true,
|
||||
type: 'slider',
|
||||
top: '90%',
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
yAxisIndex: 0,
|
||||
data: '${yList || []}',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: 'rgba(0,111,255,0.5)',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const data = /*[[${charts}]]*/ [];
|
||||
|
||||
(function () {
|
||||
const amis = amisRequire('amis/embed')
|
||||
const amisJson = {
|
||||
type: 'page',
|
||||
title: 'Strategy',
|
||||
body: {
|
||||
type: 'tabs',
|
||||
tabsMode: 'vertical',
|
||||
tabs: data.map(item => {
|
||||
let body = {}
|
||||
if (item?.type === 'candle') {
|
||||
body = Object.keys(item?.data ?? {})
|
||||
.map(key => candleChart(key, item.data[key]))
|
||||
}
|
||||
return {
|
||||
title: item?.title,
|
||||
body: Object.keys(item?.data ?? {})
|
||||
.map(key => {
|
||||
let value = item.data[key]
|
||||
let type = value['type']
|
||||
if (type) {
|
||||
if (type === 'candle')
|
||||
return candleChart(key, item.data[key])
|
||||
else if (type === 'line')
|
||||
return lineChart(key, item.data[key])
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter(item => item),
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
amis.embed('#root', amisJson, {}, {theme: 'antd'})
|
||||
})()
|
||||
</script>
|
||||
</html>
|
||||
228
leopard-strategy/src/main/resources/templates/report.html
Normal file
228
leopard-strategy/src/main/resources/templates/report.html
Normal file
@@ -0,0 +1,228 @@
|
||||
<html lang='zh'>
|
||||
<head>
|
||||
<meta charset='utf-8'/>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
|
||||
<meta content='width=device-width, initial-scale=1.0' name='viewport'/>
|
||||
<title>Strategy</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/antd.min.css" rel="stylesheet"/>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/helper.min.css" rel="stylesheet"/>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/iconfont.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
html, body, #root {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'></div>
|
||||
</body>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/sdk.min.js"></script>
|
||||
<script th:inline="javascript" type='text/javascript'>
|
||||
// Market data as KV: { 'YYYY-MM-DD': [open, close, low, high], ... }
|
||||
const data = {
|
||||
'2025-01-01': [100, 105, 98, 108],
|
||||
'2025-01-02': [105, 102, 100, 107],
|
||||
'2025-01-03': [102, 110, 101, 112],
|
||||
'2025-01-04': [110, 108, 106, 113],
|
||||
'2025-01-05': [108, 115, 107, 116],
|
||||
'2025-01-06': [115, 117, 114, 120],
|
||||
'2025-01-07': [117, 112, 111, 119],
|
||||
'2025-01-08': [112, 118, 110, 121],
|
||||
'2025-01-09': [118, 121, 117, 123],
|
||||
'2025-01-10': [121, 119, 118, 122],
|
||||
}
|
||||
|
||||
// 全局配置(颜色、尺寸、间距等),集中管理,便于统一调整
|
||||
const CONFIG = {
|
||||
colors: {up: '#000000FF', down: '#00000045'},
|
||||
grid: {left: '2%', right: '2%', top: 40, bottom: 110},
|
||||
zoom: {bottom: 16, height: 50},
|
||||
linewidth: {stem: 1.5, openTick: 1.2, closeTick: 1.6, closeLine: 1.5},
|
||||
tick: {min: 4, max: 10, scale: 0.4},
|
||||
}
|
||||
|
||||
// 通用 tooltip 格式化(按索引回读原始 O/H/L/C)
|
||||
function makeTooltipFormatter(dataMap, dates) {
|
||||
return function (params) {
|
||||
let p = Array.isArray(params) ? params[0] : params
|
||||
let idx = p.dataIndex
|
||||
let d = dates[idx]
|
||||
let ohlc = dataMap[d] || []
|
||||
let o = ohlc[0], c = ohlc[1], l = ohlc[2], h = ohlc[3]
|
||||
let chg = (c - o)
|
||||
let chgPct = o ? (chg / o * 100) : 0
|
||||
let sign = chg >= 0 ? '+' : ''
|
||||
return [
|
||||
d,
|
||||
'O: ' + o,
|
||||
'C: ' + c,
|
||||
'H: ' + h,
|
||||
'L: ' + l,
|
||||
'Chg: ' + sign + chg.toFixed(2) + ' (' + sign + chgPct.toFixed(2) + '%)',
|
||||
].join('<br/>')
|
||||
}
|
||||
}
|
||||
|
||||
// 通用基础配置构建(legend/tooltip/grid/xAxis/yAxis/dataZoom)
|
||||
function buildBaseOption(dates, series, formatter) {
|
||||
return {
|
||||
animation: false,
|
||||
legend: {show: false},
|
||||
tooltip: {trigger: 'axis', axisPointer: {type: 'cross'}, formatter},
|
||||
grid: CONFIG.grid,
|
||||
xAxis: {type: 'category', data: dates, boundaryGap: true, axisLine: {onZero: false}},
|
||||
yAxis: {scale: true},
|
||||
dataZoom: [
|
||||
{type: 'inside', xAxisIndex: 0, start: 0, end: 100},
|
||||
{
|
||||
show: true,
|
||||
type: 'slider',
|
||||
xAxisIndex: 0,
|
||||
bottom: CONFIG.zoom.bottom,
|
||||
height: CONFIG.zoom.height,
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series,
|
||||
}
|
||||
}
|
||||
|
||||
// Range Band + Close Line(高低区间带 + 收盘线):趋势与波动范围直观
|
||||
function buildRangeCloseOption(dataMap) {
|
||||
const dates = Object.keys(dataMap).sort()
|
||||
const lowArr = dates.map(d => dataMap[d][2])
|
||||
const highArr = dates.map(d => dataMap[d][3])
|
||||
const closeArr = dates.map(d => dataMap[d][1])
|
||||
const rangeArr = highArr.map((h, i) => h - lowArr[i])
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: 'Low',
|
||||
type: 'line',
|
||||
data: lowArr,
|
||||
stack: 'range',
|
||||
symbol: 'none',
|
||||
lineStyle: {width: 0},
|
||||
emphasis: {disabled: true},
|
||||
},
|
||||
{
|
||||
name: 'Range',
|
||||
type: 'line',
|
||||
data: rangeArr,
|
||||
stack: 'range',
|
||||
symbol: 'none',
|
||||
lineStyle: {width: 0},
|
||||
areaStyle: {color: CONFIG.colors.down, opacity: 0.6},
|
||||
z: 2,
|
||||
},
|
||||
{
|
||||
name: 'Close',
|
||||
type: 'line',
|
||||
data: closeArr,
|
||||
symbol: 'none',
|
||||
lineStyle: {color: CONFIG.colors.up, width: CONFIG.linewidth.closeLine},
|
||||
z: 3,
|
||||
},
|
||||
]
|
||||
|
||||
return buildBaseOption(dates, series, makeTooltipFormatter(dataMap, dates))
|
||||
}
|
||||
|
||||
// Minimal OHLC(竖线 + 左/右短横):信息等价于K线但形态简洁
|
||||
function buildOHLCMinimalOption(dataMap) {
|
||||
const dates = Object.keys(dataMap).sort()
|
||||
const ohlcData = dates.map((d, i) => [i, ...dataMap[d]])
|
||||
|
||||
// 自定义渲染:竖线=高低区间;左短横=开盘;右短横=收盘;颜色=涨跌
|
||||
function renderOHLCMinimal(params, api) {
|
||||
let idx = api.value(0)
|
||||
let open = api.value(1)
|
||||
let close = api.value(2)
|
||||
let low = api.value(3)
|
||||
let high = api.value(4)
|
||||
let up = close >= open
|
||||
|
||||
let x = api.coord([idx, 0])[0]
|
||||
let highPoint = api.coord([idx, high])
|
||||
let lowPoint = api.coord([idx, low])
|
||||
let openPoint = api.coord([idx, open])
|
||||
let closePoint = api.coord([idx, close])
|
||||
|
||||
let band = api.size([1, 0])[0]
|
||||
let tick = Math.max(CONFIG.tick.min, Math.min(CONFIG.tick.max, band * CONFIG.tick.scale))
|
||||
let color = up ? CONFIG.colors.up : CONFIG.colors.down
|
||||
|
||||
return {
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
type: 'line',
|
||||
shape: {x1: x, y1: highPoint[1], x2: x, y2: lowPoint[1]},
|
||||
style: {stroke: color, lineWidth: CONFIG.linewidth.stem, opacity: 0.9},
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
shape: {x1: x - tick, y1: openPoint[1], x2: x, y2: openPoint[1]},
|
||||
style: {stroke: color, lineWidth: CONFIG.linewidth.openTick, opacity: 0.95},
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
shape: {x1: x, y1: closePoint[1], x2: x + tick, y2: closePoint[1]},
|
||||
style: {stroke: color, lineWidth: CONFIG.linewidth.closeTick, opacity: 0.95},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: 'ohlc',
|
||||
type: 'custom',
|
||||
renderItem: renderOHLCMinimal,
|
||||
encode: {x: 0, y: [1, 2, 3, 4]},
|
||||
data: ohlcData,
|
||||
z: 10,
|
||||
},
|
||||
]
|
||||
|
||||
return buildBaseOption(dates, series, makeTooltipFormatter(dataMap, dates))
|
||||
}
|
||||
|
||||
(function () {
|
||||
const amis = amisRequire('amis/embed')
|
||||
const amisJson = {
|
||||
type: 'page',
|
||||
title: 'Strategy',
|
||||
body: {
|
||||
type: 'tabs',
|
||||
tabsMode: 'vertical',
|
||||
tabs: [
|
||||
{
|
||||
title: 'Charts',
|
||||
// 在一个 Tab 中展示两张图,便于同屏对比
|
||||
body: [
|
||||
{
|
||||
type: 'chart',
|
||||
height: 500,
|
||||
config: buildRangeCloseOption(data),
|
||||
},
|
||||
{
|
||||
type: 'chart',
|
||||
height: 500,
|
||||
config: buildOHLCMinimalOption(data),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
// `dates` 已直接在图表配置中使用,这里无需通过 amis data 传递
|
||||
amis.embed('#root', amisJson, {}, {theme: 'antd'})
|
||||
})()
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.lanyuanxiaoyao.leopard.strategy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import org.commonmark.ext.gfm.tables.TablesExtension;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
/**
|
||||
* Markdown Render
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250918
|
||||
*/
|
||||
public class TestMarkdown {
|
||||
public static void main(String[] args) throws IOException {
|
||||
var extensions = List.of(TablesExtension.create());
|
||||
var parser = Parser.builder()
|
||||
.extensions(extensions)
|
||||
.build();
|
||||
var render = HtmlRenderer.builder()
|
||||
.extensions(extensions)
|
||||
.build();
|
||||
var result = render.render(parser.parse(
|
||||
// language=Markdown
|
||||
"""
|
||||
### Hello
|
||||
|
||||
```echarts
|
||||
System.out.println("go");
|
||||
```
|
||||
|
||||
> I am iron man
|
||||
|
||||
| | |
|
||||
|------|----|
|
||||
| Tony | 12 |
|
||||
"""
|
||||
));
|
||||
Files.writeString(Path.of("result.html"), result);
|
||||
}
|
||||
}
|
||||
@@ -4,26 +4,26 @@
|
||||
"": {
|
||||
"name": "lepoard-web",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@ant-design/icons": "^6.0.2",
|
||||
"@ant-design/pro-components": "^2.8.10",
|
||||
"@ant-design/x": "^1.6.0",
|
||||
"@ant-design/x": "^1.6.1",
|
||||
"@echofly/fetch-event-source": "^3.0.2",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@lightenna/react-mermaid-diagram": "^1.0.21",
|
||||
"ahooks": "^3.9.4",
|
||||
"ahooks": "^3.9.5",
|
||||
"amis": "^6.13.0",
|
||||
"amis-core": "^6.13.0",
|
||||
"antd": "^5.27.1",
|
||||
"antd": "^5.27.3",
|
||||
"axios": "1.11.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"licia": "^1.48.0",
|
||||
"mermaid": "^11.10.1",
|
||||
"es-toolkit": "^1.39.10",
|
||||
"mermaid": "^11.11.0",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.8.2",
|
||||
"react-router": "^7.9.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"styled-components": "^6.1.19",
|
||||
"yocto-queue": "^1.2.1",
|
||||
@@ -33,11 +33,11 @@
|
||||
"@types/react": "^18.3.24",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@vitejs/plugin-react-swc": "^3.11.0",
|
||||
"globals": "^16.3.0",
|
||||
"globals": "^16.4.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"sass": "^1.91.0",
|
||||
"sass": "^1.92.1",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.1.3",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-javascript-obfuscator": "^3.1.0",
|
||||
"vitest": "^3.2.4",
|
||||
},
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
"@ant-design/fast-color": ["@ant-design/fast-color@2.0.6", "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", { "dependencies": { "@babel/runtime": "^7.24.7" } }, "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA=="],
|
||||
|
||||
"@ant-design/icons": ["@ant-design/icons@6.0.1", "https://registry.npmmirror.com/@ant-design/icons/-/icons-6.0.1.tgz", { "dependencies": { "@ant-design/colors": "^8.0.0", "@ant-design/icons-svg": "^4.4.0", "@rc-component/util": "^1.2.1", "classnames": "^2.2.6" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-BsAoYa8NTwh5GfpziqStAyWHNyp8vkc9PkuIR/Cu8O8WkhRzrpx260zd5ygsXMhQEGtfGGFjdAG0DfjhGBOBHw=="],
|
||||
"@ant-design/icons": ["@ant-design/icons@6.0.2", "https://registry.npmmirror.com/@ant-design/icons/-/icons-6.0.2.tgz", { "dependencies": { "@ant-design/colors": "^8.0.0", "@ant-design/icons-svg": "^4.4.0", "@rc-component/util": "^1.2.1", "classnames": "^2.2.6" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-1U1+6afDP+w+6jDkxrmn/kwoFJvB/aD4mQ/+Rhkp+BBRAfgK46gxKb6VxnoS/hYDiRdhIjzilkCmi6pD7zjxCw=="],
|
||||
|
||||
"@ant-design/icons-svg": ["@ant-design/icons-svg@4.4.2", "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", {}, "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA=="],
|
||||
|
||||
@@ -80,22 +80,14 @@
|
||||
|
||||
"@ant-design/react-slick": ["@ant-design/react-slick@1.1.2", "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz", { "dependencies": { "@babel/runtime": "^7.10.4", "classnames": "^2.2.5", "json2mq": "^0.2.0", "resize-observer-polyfill": "^1.5.1", "throttle-debounce": "^5.0.0" }, "peerDependencies": { "react": ">=16.9.0" } }, "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA=="],
|
||||
|
||||
"@ant-design/x": ["@ant-design/x@1.6.0", "https://registry.npmmirror.com/@ant-design/x/-/x-1.6.0.tgz", { "dependencies": { "@ant-design/colors": "^7.1.0", "@ant-design/cssinjs": "^1.21.1", "@ant-design/cssinjs-utils": "^1.1.0", "@ant-design/fast-color": "^2.0.6", "@ant-design/icons": "^5.4.0", "@babel/runtime": "^7.25.6", "classnames": "^2.5.1", "rc-motion": "^2.9.2", "rc-util": "^5.43.0", "tbox-nodejs-sdk": "^0.0.13" }, "peerDependencies": { "antd": "^5.20.3", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-rtDmlJJ6oSvEFlfwHTmc3jkPTKrx7+0uiu/tnKrVE5qm6Da0o5juYk58Uwz5osWb9anMqtl7q42b4hWvd8XZkg=="],
|
||||
"@ant-design/x": ["@ant-design/x@1.6.1", "https://registry.npmmirror.com/@ant-design/x/-/x-1.6.1.tgz", { "dependencies": { "@ant-design/colors": "^7.1.0", "@ant-design/cssinjs": "^1.21.1", "@ant-design/cssinjs-utils": "^1.1.0", "@ant-design/fast-color": "^2.0.6", "@ant-design/icons": "^5.4.0", "@babel/runtime": "^7.25.6", "classnames": "^2.5.1", "rc-motion": "^2.9.2", "rc-util": "^5.43.0" }, "peerDependencies": { "antd": "^5.20.3", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-6KO7iNGcnAMvIJzZPWJOdNHhx3kC4KUEMs43C0tSJORs5SosL+0FdCY6reozLZkUMSu3LqloPIJVba+OAlyA+w=="],
|
||||
|
||||
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
|
||||
|
||||
"@antfu/utils": ["@antfu/utils@9.2.0", "https://registry.npmmirror.com/@antfu/utils/-/utils-9.2.0.tgz", {}, "sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.4", "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.4.tgz", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.4", "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.4", "https://registry.npmmirror.com/@babel/types/-/types-7.28.4.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="],
|
||||
|
||||
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.1", "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", {}, "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw=="],
|
||||
|
||||
"@chenshuai2144/sketch-color": ["@chenshuai2144/sketch-color@1.0.9", "https://registry.npmmirror.com/@chenshuai2144/sketch-color/-/sketch-color-1.0.9.tgz", { "dependencies": { "reactcss": "^1.2.3", "tinycolor2": "^1.4.2" }, "peerDependencies": { "react": ">=16.12.0" } }, "sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w=="],
|
||||
@@ -216,14 +208,6 @@
|
||||
|
||||
"@mermaid-js/parser": ["@mermaid-js/parser@0.6.2", "https://registry.npmmirror.com/@mermaid-js/parser/-/parser-0.6.2.tgz", { "dependencies": { "langium": "3.3.1" } }, "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ=="],
|
||||
|
||||
"@microsoft/api-extractor": ["@microsoft/api-extractor@7.52.11", "https://registry.npmmirror.com/@microsoft/api-extractor/-/api-extractor-7.52.11.tgz", { "dependencies": { "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.14.0", "@rushstack/rig-package": "0.5.3", "@rushstack/terminal": "0.15.4", "@rushstack/ts-command-line": "5.0.2", "lodash": "~4.17.15", "minimatch": "10.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-IKQ7bHg6f/Io3dQds6r9QPYk4q0OlR9A4nFDtNhUt3UUIhyitbxAqRN1CLjUVtk6IBk3xzyCMOdwwtIXQ7AlGg=="],
|
||||
|
||||
"@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.30.7", "https://registry.npmmirror.com/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", { "dependencies": { "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.14.0" } }, "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ=="],
|
||||
|
||||
"@microsoft/tsdoc": ["@microsoft/tsdoc@0.15.1", "https://registry.npmmirror.com/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", {}, "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw=="],
|
||||
|
||||
"@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.17.1", "https://registry.npmmirror.com/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", { "dependencies": { "@microsoft/tsdoc": "0.15.1", "ajv": "~8.12.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw=="],
|
||||
|
||||
"@parcel/watcher": ["@parcel/watcher@2.5.1", "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
|
||||
|
||||
"@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="],
|
||||
@@ -278,8 +262,6 @@
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw=="],
|
||||
@@ -322,14 +304,6 @@
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA=="],
|
||||
|
||||
"@rushstack/node-core-library": ["@rushstack/node-core-library@5.14.0", "https://registry.npmmirror.com/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", { "dependencies": { "ajv": "~8.13.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg=="],
|
||||
|
||||
"@rushstack/rig-package": ["@rushstack/rig-package@0.5.3", "https://registry.npmmirror.com/@rushstack/rig-package/-/rig-package-0.5.3.tgz", { "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow=="],
|
||||
|
||||
"@rushstack/terminal": ["@rushstack/terminal@0.15.4", "https://registry.npmmirror.com/@rushstack/terminal/-/terminal-0.15.4.tgz", { "dependencies": { "@rushstack/node-core-library": "5.14.0", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg=="],
|
||||
|
||||
"@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.0.2", "https://registry.npmmirror.com/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", { "dependencies": { "@rushstack/terminal": "0.15.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ=="],
|
||||
|
||||
"@swc/core": ["@swc/core@1.13.5", "https://registry.npmmirror.com/@swc/core/-/core-1.13.5.tgz", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.13.5", "@swc/core-darwin-x64": "1.13.5", "@swc/core-linux-arm-gnueabihf": "1.13.5", "@swc/core-linux-arm64-gnu": "1.13.5", "@swc/core-linux-arm64-musl": "1.13.5", "@swc/core-linux-x64-gnu": "1.13.5", "@swc/core-linux-x64-musl": "1.13.5", "@swc/core-win32-arm64-msvc": "1.13.5", "@swc/core-win32-ia32-msvc": "1.13.5", "@swc/core-win32-x64-msvc": "1.13.5" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ=="],
|
||||
|
||||
"@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.13.5", "https://registry.npmmirror.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ=="],
|
||||
@@ -356,8 +330,6 @@
|
||||
|
||||
"@swc/types": ["@swc/types@0.1.25", "https://registry.npmmirror.com/@swc/types/-/types-0.1.25.tgz", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g=="],
|
||||
|
||||
"@types/argparse": ["@types/argparse@1.0.38", "https://registry.npmmirror.com/@types/argparse/-/argparse-1.0.38.tgz", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.2", "https://registry.npmmirror.com/@types/chai/-/chai-5.2.2.tgz", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||
|
||||
"@types/d3": ["@types/d3@7.4.3", "https://registry.npmmirror.com/@types/d3/-/d3-7.4.3.tgz", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="],
|
||||
@@ -482,22 +454,6 @@
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
|
||||
"@volar/language-core": ["@volar/language-core@2.4.23", "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.23.tgz", { "dependencies": { "@volar/source-map": "2.4.23" } }, "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ=="],
|
||||
|
||||
"@volar/source-map": ["@volar/source-map@2.4.23", "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.23.tgz", {}, "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q=="],
|
||||
|
||||
"@volar/typescript": ["@volar/typescript@2.4.23", "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.23.tgz", { "dependencies": { "@volar/language-core": "2.4.23", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag=="],
|
||||
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.21", "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.21.tgz", { "dependencies": { "@babel/parser": "^7.28.3", "@vue/shared": "3.5.21", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw=="],
|
||||
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.21", "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz", { "dependencies": { "@vue/compiler-core": "3.5.21", "@vue/shared": "3.5.21" } }, "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ=="],
|
||||
|
||||
"@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="],
|
||||
|
||||
"@vue/language-core": ["@vue/language-core@2.2.0", "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.0.tgz", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^0.4.9", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw=="],
|
||||
|
||||
"@vue/shared": ["@vue/shared@3.5.21", "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.21.tgz", {}, "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw=="],
|
||||
|
||||
"abbrev": ["abbrev@1.1.1", "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="],
|
||||
|
||||
"acorn": ["acorn@8.8.2", "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="],
|
||||
@@ -510,14 +466,6 @@
|
||||
|
||||
"ahooks": ["ahooks@3.9.5", "https://registry.npmmirror.com/ahooks/-/ahooks-3.9.5.tgz", { "dependencies": { "@babel/runtime": "^7.21.0", "@types/js-cookie": "^3.0.6", "dayjs": "^1.9.1", "intersection-observer": "^0.12.0", "js-cookie": "^3.0.5", "lodash": "^4.17.21", "react-fast-compare": "^3.2.2", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.0", "tslib": "^2.4.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-TrjXie49Q8HuHKTa84Fm9A+famMDAG1+7a9S9Gq6RQ0h90Jgqmiq3CkObuRjWT/C4d6nRZCw35Y2k2fmybb5eA=="],
|
||||
|
||||
"ajv": ["ajv@8.12.0", "https://registry.npmmirror.com/ajv/-/ajv-8.12.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA=="],
|
||||
|
||||
"ajv-draft-04": ["ajv-draft-04@1.0.0", "https://registry.npmmirror.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
|
||||
|
||||
"ajv-formats": ["ajv-formats@3.0.1", "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||
|
||||
"alien-signals": ["alien-signals@0.4.14", "https://registry.npmmirror.com/alien-signals/-/alien-signals-0.4.14.tgz", {}, "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q=="],
|
||||
|
||||
"amis": ["amis@6.13.0", "https://registry.npmmirror.com/amis/-/amis-6.13.0.tgz", { "dependencies": { "amis-core": "^6.13.0", "amis-ui": "^6.13.0", "attr-accept": "2.2.2", "blueimp-canvastoblob": "2.1.0", "classnames": "2.3.2", "downshift": "6.1.12", "echarts": "5.5.1", "echarts-stat": "^1.2.0", "echarts-wordcloud": "^2.1.0", "exceljs": "^4.4.0", "file-saver": "^2.0.2", "file64": "^1.0.4", "hls.js": "1.1.3", "hoist-non-react-statics": "^3.3.2", "hotkeys-js": "^3.8.7", "immutability-helper": "^3.1.1", "jsbarcode": "^3.11.5", "keycode": "^2.2.1", "lodash": "^4.17.15", "match-sorter": "^6.3.1", "mobx": "^4.5.0", "mobx-react": "^6.3.1", "mobx-state-tree": "^3.17.3", "moment": "^2.19.4", "moment-timezone": "^0.5.34", "mpegts.js": "^1.6.10", "office-viewer": "*", "prop-types": "^15.6.1", "qrcode-react-next": "1.0.0", "react-cropper": "^2.1.8", "react-dropzone": "^11.4.2", "react-intersection-observer": "9.5.2", "react-json-view": "1.21.3", "react-transition-group": "4.4.2", "sortablejs": "1.15.0", "tslib": "^2.3.1", "video-react": "0.15.0", "xlsx": "^0.18.5" }, "peerDependencies": { "react": ">=16.8.6", "react-dom": ">=16.8.6" } }, "sha512-KRE5e6dfnaVyoBagrgSl8lcW7OsZcTAzVx0EVCInpayMsuiorO44LABMRhTvi9IBk9DhjzgczBKj6frnQ4pJsQ=="],
|
||||
|
||||
"amis-core": ["amis-core@6.13.0", "https://registry.npmmirror.com/amis-core/-/amis-core-6.13.0.tgz", { "dependencies": { "@rc-component/mini-decimal": "^1.0.1", "amis-formula": "^6.13.0", "classnames": "2.3.2", "cross-env": "^7.0.3", "file-saver": "^2.0.2", "hoist-non-react-statics": "^3.3.2", "lodash": "^4.17.15", "match-sorter": "^6.3.1", "mobx": "^4.5.0", "mobx-react": "^6.3.1", "mobx-state-tree": "^3.17.3", "moment": "^2.19.4", "papaparse": "^5.3.0", "path-to-regexp": "6.2.0", "qs": "6.9.7", "react-intersection-observer": "9.5.2", "react-json-view": "1.21.3", "react-overlays": "5.1.1", "tslib": "^2.3.1", "uncontrollable": "7.2.1" }, "peerDependencies": { "react": ">=16.8.6", "react-dom": ">=16.8.6", "react-is": ">=16.8.6" } }, "sha512-RLyG3azQXlihxLqeRwf+hiKMDYyg3C3P7Kh/oLgMg0c4OKl0dgWfyhE8tUok+et5fc7phoMb6FuKeh+J0MOseg=="],
|
||||
@@ -670,8 +618,6 @@
|
||||
|
||||
"commander": ["commander@10.0.0", "https://registry.npmmirror.com/commander/-/commander-10.0.0.tgz", {}, "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA=="],
|
||||
|
||||
"compare-versions": ["compare-versions@6.1.1", "https://registry.npmmirror.com/compare-versions/-/compare-versions-6.1.1.tgz", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="],
|
||||
|
||||
"compress-commons": ["compress-commons@4.1.2", "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.2.tgz", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="],
|
||||
|
||||
"compute-scroll-into-view": ["compute-scroll-into-view@1.0.20", "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", {}, "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg=="],
|
||||
@@ -784,8 +730,6 @@
|
||||
|
||||
"dayjs": ["dayjs@1.11.18", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.18.tgz", {}, "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA=="],
|
||||
|
||||
"de-indent": ["de-indent@1.0.2", "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"decode-named-character-reference": ["decode-named-character-reference@1.2.0", "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
|
||||
@@ -848,6 +792,8 @@
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"es-toolkit": ["es-toolkit@1.39.10", "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.39.10.tgz", {}, "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w=="],
|
||||
|
||||
"es6-object-assign": ["es6-object-assign@1.1.0", "https://registry.npmmirror.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz", {}, "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw=="],
|
||||
|
||||
"es6-promise": ["es6-promise@4.2.8", "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz", {}, "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="],
|
||||
@@ -872,10 +818,6 @@
|
||||
|
||||
"esutils": ["esutils@2.0.3", "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.1", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
|
||||
|
||||
"eventsource-parser": ["eventsource-parser@3.0.6", "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
|
||||
|
||||
"exceljs": ["exceljs@4.4.0", "https://registry.npmmirror.com/exceljs/-/exceljs-4.4.0.tgz", { "dependencies": { "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", "jszip": "^3.10.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", "unzipper": "^0.10.11", "uuid": "^8.3.0" } }, "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg=="],
|
||||
|
||||
"expect-type": ["expect-type@1.2.2", "https://registry.npmmirror.com/expect-type/-/expect-type-1.2.2.tgz", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
|
||||
@@ -922,8 +864,6 @@
|
||||
|
||||
"fs-constants": ["fs-constants@1.0.0", "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||
|
||||
"fs-extra": ["fs-extra@11.3.1", "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.1.tgz", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="],
|
||||
|
||||
"fs-minipass": ["fs-minipass@2.1.0", "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
@@ -942,7 +882,7 @@
|
||||
|
||||
"glob": ["glob@11.0.3", "https://registry.npmmirror.com/glob/-/glob-11.0.3.tgz", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA=="],
|
||||
|
||||
"globals": ["globals@16.3.0", "https://registry.npmmirror.com/globals/-/globals-16.3.0.tgz", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="],
|
||||
"globals": ["globals@16.4.0", "https://registry.npmmirror.com/globals/-/globals-16.4.0.tgz", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
@@ -966,8 +906,6 @@
|
||||
|
||||
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
||||
|
||||
"he": ["he@1.2.0", "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
|
||||
|
||||
"hls.js": ["hls.js@1.1.3", "https://registry.npmmirror.com/hls.js/-/hls.js-1.1.3.tgz", {}, "sha512-H1Gbbi5f786/pU5iS4IngRt4pgJvLDV+eD5iiqUFZVd62r0Uz0uxL+Lmx+idQIxwsg8krkLPK7p+EtMQhKk9hg=="],
|
||||
|
||||
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
|
||||
@@ -988,8 +926,6 @@
|
||||
|
||||
"immutable": ["immutable@5.1.3", "https://registry.npmmirror.com/immutable/-/immutable-5.1.3.tgz", {}, "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg=="],
|
||||
|
||||
"import-lazy": ["import-lazy@4.0.0", "https://registry.npmmirror.com/import-lazy/-/import-lazy-4.0.0.tgz", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="],
|
||||
|
||||
"inflight": ["inflight@1.0.6", "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
@@ -1014,8 +950,6 @@
|
||||
|
||||
"is-callable": ["is-callable@1.2.7", "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-decimal": ["is-decimal@2.0.1", "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
@@ -1048,8 +982,6 @@
|
||||
|
||||
"javascript-obfuscator": ["javascript-obfuscator@4.1.1", "https://registry.npmmirror.com/javascript-obfuscator/-/javascript-obfuscator-4.1.1.tgz", { "dependencies": { "@javascript-obfuscator/escodegen": "2.3.0", "@javascript-obfuscator/estraverse": "5.4.0", "acorn": "8.8.2", "assert": "2.0.0", "chalk": "4.1.2", "chance": "1.1.9", "class-validator": "0.14.1", "commander": "10.0.0", "eslint-scope": "7.1.1", "eslint-visitor-keys": "3.3.0", "fast-deep-equal": "3.1.3", "inversify": "6.0.1", "js-string-escape": "1.0.1", "md5": "2.3.0", "mkdirp": "2.1.3", "multimatch": "5.0.0", "opencollective-postinstall": "2.0.3", "process": "0.11.10", "reflect-metadata": "0.1.13", "source-map-support": "0.5.21", "string-template": "1.0.0", "stringz": "2.1.0", "tslib": "2.5.0" }, "bin": { "javascript-obfuscator": "bin/javascript-obfuscator" } }, "sha512-gt+KZpIIrrxXHEQGD8xZrL8mTRwRY0U76/xz/YX0gZdPrSqQhT/c7dYLASlLlecT3r+FxE7je/+C0oLnTDCx4A=="],
|
||||
|
||||
"jju": ["jju@1.4.0", "https://registry.npmmirror.com/jju/-/jju-1.4.0.tgz", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="],
|
||||
|
||||
"js-cookie": ["js-cookie@3.0.5", "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz", {}, "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="],
|
||||
|
||||
"js-string-escape": ["js-string-escape@1.0.1", "https://registry.npmmirror.com/js-string-escape/-/js-string-escape-1.0.1.tgz", {}, "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg=="],
|
||||
@@ -1058,12 +990,8 @@
|
||||
|
||||
"jsbarcode": ["jsbarcode@3.12.1", "https://registry.npmmirror.com/jsbarcode/-/jsbarcode-3.12.1.tgz", {}, "sha512-QZQSqIknC2Rr/YOUyOkCBqsoiBAOTYK+7yNN3JsqfoUtJtkazxNw1dmPpxuv7VVvqW13kA3/mKiLq+s/e3o9hQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json2mq": ["json2mq@0.2.0", "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", { "dependencies": { "string-convert": "^0.2.0" } }, "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA=="],
|
||||
|
||||
"jsonfile": ["jsonfile@6.2.0", "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
|
||||
"jszip": ["jszip@3.10.1", "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
|
||||
|
||||
"katex": ["katex@0.16.22", "https://registry.npmmirror.com/katex/-/katex-0.16.22.tgz", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg=="],
|
||||
@@ -1084,8 +1012,6 @@
|
||||
|
||||
"libphonenumber-js": ["libphonenumber-js@1.12.15", "https://registry.npmmirror.com/libphonenumber-js/-/libphonenumber-js-1.12.15.tgz", {}, "sha512-TMDCtIhWUDHh91wRC+wFuGlIzKdPzaTUHHVrIZ3vPUEoNaXFLrsIQ1ZpAeZeXApIF6rvDksMTvjrIQlLKaYxqQ=="],
|
||||
|
||||
"licia": ["licia@1.48.0", "https://registry.npmmirror.com/licia/-/licia-1.48.0.tgz", {}, "sha512-bBWiT5CSdEtwuAHiYTJ74yItCjIFdHi4xiFk6BRDfKa+sdCpkUHp69YKb5udNOJlHDzFjNjcMgNZ/+wQIHrB8A=="],
|
||||
|
||||
"lie": ["lie@3.3.0", "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
|
||||
|
||||
"linkify-it": ["linkify-it@3.0.3", "https://registry.npmmirror.com/linkify-it/-/linkify-it-3.0.3.tgz", { "dependencies": { "uc.micro": "^1.0.1" } }, "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ=="],
|
||||
@@ -1290,8 +1216,6 @@
|
||||
|
||||
"ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"muggle-string": ["muggle-string@0.4.1", "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
|
||||
|
||||
"multimatch": ["multimatch@5.0.0", "https://registry.npmmirror.com/multimatch/-/multimatch-5.0.0.tgz", { "dependencies": { "@types/minimatch": "^3.0.3", "array-differ": "^3.0.0", "array-union": "^2.1.0", "arrify": "^2.0.1", "minimatch": "^3.0.4" } }, "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA=="],
|
||||
|
||||
"nan": ["nan@2.23.0", "https://registry.npmmirror.com/nan/-/nan-2.23.0.tgz", {}, "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ=="],
|
||||
@@ -1334,16 +1258,12 @@
|
||||
|
||||
"parse-entities": ["parse-entities@4.0.2", "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
||||
|
||||
"path-browserify": ["path-browserify@1.0.1", "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||
|
||||
"path-data-parser": ["path-data-parser@0.1.0", "https://registry.npmmirror.com/path-data-parser/-/path-data-parser-0.1.0.tgz", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
|
||||
|
||||
"path-is-absolute": ["path-is-absolute@1.0.1", "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.0", "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.0.tgz", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@6.2.0", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz", {}, "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg=="],
|
||||
@@ -1386,8 +1306,6 @@
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"pure-color": ["pure-color@1.3.0", "https://registry.npmmirror.com/pure-color/-/pure-color-1.3.0.tgz", {}, "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA=="],
|
||||
|
||||
"qrcode-react-next": ["qrcode-react-next@1.0.0", "https://registry.npmmirror.com/qrcode-react-next/-/qrcode-react-next-1.0.0.tgz", { "dependencies": { "react": ">16.0.0" } }, "sha512-d8U0xudKLDJDGdUFWKyNivBcf0M0VSwbcXChIA8cfFHkB51SlxlCtrwbJYukzV1qVBexDtB0GfXm0oQvh0Pwdw=="],
|
||||
@@ -1498,7 +1416,7 @@
|
||||
|
||||
"react-pdf": ["react-pdf@9.0.0", "https://registry.npmmirror.com/react-pdf/-/react-pdf-9.0.0.tgz", { "dependencies": { "clsx": "^2.0.0", "dequal": "^2.0.3", "make-cancellable-promise": "^1.3.1", "make-event-props": "^1.6.0", "merge-refs": "^1.3.0", "pdfjs-dist": "4.3.136", "tiny-invariant": "^1.0.0", "warning": "^4.0.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-J+pza8R2p9oNEOJOHIQJI4o5rFK7ji7bBl2IvsHvz1OOyphvuzVDo5tOJwWAFAbxYauCH3Kt8jOvcMJUOpxYZQ=="],
|
||||
|
||||
"react-router": ["react-router@7.8.2", "https://registry.npmmirror.com/react-router/-/react-router-7.8.2.tgz", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ=="],
|
||||
"react-router": ["react-router@7.9.1", "https://registry.npmmirror.com/react-router/-/react-router-7.9.1.tgz", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g=="],
|
||||
|
||||
"react-textarea-autosize": ["react-textarea-autosize@8.3.3", "https://registry.npmmirror.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz", { "dependencies": { "@babel/runtime": "^7.10.2", "use-composed-ref": "^1.0.0", "use-latest": "^1.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0" } }, "sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ=="],
|
||||
|
||||
@@ -1528,12 +1446,8 @@
|
||||
|
||||
"remove-accents": ["remove-accents@0.5.0", "https://registry.npmmirror.com/remove-accents/-/remove-accents-0.5.0.tgz", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
|
||||
"rimraf": ["rimraf@6.0.1", "https://registry.npmmirror.com/rimraf/-/rimraf-6.0.1.tgz", { "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A=="],
|
||||
|
||||
"robust-predicates": ["robust-predicates@3.0.2", "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
|
||||
@@ -1600,16 +1514,12 @@
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.0.3", "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||
|
||||
"ssf": ["ssf@0.11.2", "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz", { "dependencies": { "frac": "~1.1.2" } }, "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"std-env": ["std-env@3.9.0", "https://registry.npmmirror.com/std-env/-/std-env-3.9.0.tgz", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.2.tgz", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
"string-convert": ["string-convert@0.2.1", "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", {}, "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="],
|
||||
|
||||
"string-template": ["string-template@1.0.0", "https://registry.npmmirror.com/string-template/-/string-template-1.0.0.tgz", {}, "sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg=="],
|
||||
@@ -1628,8 +1538,6 @@
|
||||
|
||||
"strip-ansi-cjs": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"strip-literal": ["strip-literal@3.0.0", "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.0.0.tgz", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA=="],
|
||||
|
||||
"style-to-js": ["style-to-js@1.1.17", "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.17.tgz", { "dependencies": { "style-to-object": "1.0.9" } }, "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA=="],
|
||||
@@ -1642,16 +1550,12 @@
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"swr": ["swr@2.3.6", "https://registry.npmmirror.com/swr/-/swr-2.3.6.tgz", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw=="],
|
||||
|
||||
"tar": ["tar@6.2.1", "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
|
||||
|
||||
"tar-stream": ["tar-stream@2.2.0", "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||
|
||||
"tbox-nodejs-sdk": ["tbox-nodejs-sdk@0.0.13", "https://registry.npmmirror.com/tbox-nodejs-sdk/-/tbox-nodejs-sdk-0.0.13.tgz", { "dependencies": { "axios": "^1.10.0", "eventemitter3": "^5.0.1", "eventsource-parser": "^3.0.2", "vite-plugin-dts": "^4.5.4" } }, "sha512-WqOKY5HYqEeb2YgEp26UChC4JTpJqFdz9pWyD1uOMvQmUYXf7k2Vlozpr3MEEiCL+Mvqos0p0TGyT3CvljkABA=="],
|
||||
|
||||
"throttle-debounce": ["throttle-debounce@5.0.2", "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", {}, "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A=="],
|
||||
|
||||
"tiny-invariant": ["tiny-invariant@1.3.3", "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||
@@ -1714,12 +1618,8 @@
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
|
||||
"unzipper": ["unzipper@0.10.14", "https://registry.npmmirror.com/unzipper/-/unzipper-0.10.14.tgz", { "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", "bluebird": "~3.4.1", "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", "graceful-fs": "^4.2.2", "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" } }, "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"use-composed-ref": ["use-composed-ref@1.4.0", "https://registry.npmmirror.com/use-composed-ref/-/use-composed-ref-1.4.0.tgz", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w=="],
|
||||
|
||||
"use-isomorphic-layout-effect": ["use-isomorphic-layout-effect@1.2.1", "https://registry.npmmirror.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA=="],
|
||||
@@ -1746,8 +1646,6 @@
|
||||
|
||||
"vite-node": ["vite-node@3.2.4", "https://registry.npmmirror.com/vite-node/-/vite-node-3.2.4.tgz", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||
|
||||
"vite-plugin-dts": ["vite-plugin-dts@4.5.4", "https://registry.npmmirror.com/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", { "dependencies": { "@microsoft/api-extractor": "^7.50.1", "@rollup/pluginutils": "^5.1.4", "@volar/typescript": "^2.4.11", "@vue/language-core": "2.2.0", "compare-versions": "^6.1.1", "debug": "^4.4.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "magic-string": "^0.30.17" }, "peerDependencies": { "typescript": "*", "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg=="],
|
||||
|
||||
"vite-plugin-javascript-obfuscator": ["vite-plugin-javascript-obfuscator@3.1.0", "https://registry.npmmirror.com/vite-plugin-javascript-obfuscator/-/vite-plugin-javascript-obfuscator-3.1.0.tgz", { "dependencies": { "anymatch": "~3.1.3", "javascript-obfuscator": "^4.1.0" } }, "sha512-sf4JFlG1iUPl7bLXHGOy+bKWOQUFyXzJFWa+n2S2xMMvyfM+V9R40HhpZoIF1eAjifArM1SF7fbSFIaTuUIbPA=="],
|
||||
|
||||
"vitest": ["vitest@3.2.4", "https://registry.npmmirror.com/vitest/-/vitest-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
|
||||
@@ -1844,24 +1742,6 @@
|
||||
|
||||
"@mapbox/node-pre-gyp/rimraf": ["rimraf@3.0.2", "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||
|
||||
"@microsoft/api-extractor/typescript": ["typescript@5.8.2", "https://registry.npmmirror.com/typescript/-/typescript-5.8.2.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@rushstack/node-core-library/ajv": ["ajv@8.13.0", "https://registry.npmmirror.com/ajv/-/ajv-8.13.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="],
|
||||
|
||||
"@rushstack/terminal/supports-color": ["supports-color@8.1.1", "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"@rushstack/ts-command-line/argparse": ["argparse@1.0.10", "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"@vue/compiler-core/entities": ["entities@4.5.0", "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@vue/language-core/minimatch": ["minimatch@9.0.5", "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"ajv-formats/ajv": ["ajv@8.13.0", "https://registry.npmmirror.com/ajv/-/ajv-8.13.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="],
|
||||
|
||||
"amis/classnames": ["classnames@2.3.2", "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz", {}, "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="],
|
||||
|
||||
"amis-core/classnames": ["classnames@2.3.2", "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz", {}, "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="],
|
||||
@@ -1996,8 +1876,6 @@
|
||||
|
||||
"@mapbox/node-pre-gyp/rimraf/glob": ["glob@7.2.3", "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"@vue/language-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"amis-ui/rc-input-number/classnames": ["classnames@2.5.1", "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="],
|
||||
|
||||
"amis-ui/rc-progress/classnames": ["classnames@2.5.1", "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="],
|
||||
|
||||
@@ -11,26 +11,26 @@
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@ant-design/icons": "^6.0.2",
|
||||
"@ant-design/pro-components": "^2.8.10",
|
||||
"@ant-design/x": "^1.6.0",
|
||||
"@ant-design/x": "^1.6.1",
|
||||
"@echofly/fetch-event-source": "^3.0.2",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@lightenna/react-mermaid-diagram": "^1.0.21",
|
||||
"ahooks": "^3.9.4",
|
||||
"ahooks": "^3.9.5",
|
||||
"amis": "^6.13.0",
|
||||
"amis-core": "^6.13.0",
|
||||
"antd": "^5.27.1",
|
||||
"antd": "^5.27.3",
|
||||
"axios": "1.11.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"licia": "^1.48.0",
|
||||
"mermaid": "^11.10.1",
|
||||
"es-toolkit": "^1.39.10",
|
||||
"mermaid": "^11.11.0",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.8.2",
|
||||
"react-router": "^7.9.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"styled-components": "^6.1.19",
|
||||
"yocto-queue": "^1.2.1",
|
||||
@@ -40,11 +40,11 @@
|
||||
"@types/react": "^18.3.24",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@vitejs/plugin-react-swc": "^3.11.0",
|
||||
"globals": "^16.3.0",
|
||||
"globals": "^16.4.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"sass": "^1.91.0",
|
||||
"sass": "^1.92.1",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.1.3",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-javascript-obfuscator": "^3.1.0",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'chart.js/auto'
|
||||
import {MermaidDiagram} from '@lightenna/react-mermaid-diagram'
|
||||
import EChartsReact from 'echarts-for-react'
|
||||
import {trim} from 'licia'
|
||||
import {Chart} from 'react-chartjs-2'
|
||||
import Markdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import {trim} from 'es-toolkit'
|
||||
|
||||
type MarkdownOptions = {
|
||||
content: string
|
||||
@@ -14,7 +14,7 @@ function MarkdownRender(options: MarkdownOptions) {
|
||||
return (
|
||||
<Markdown
|
||||
remarkPlugins={[
|
||||
remarkGfm
|
||||
remarkGfm,
|
||||
]}
|
||||
children={options.content}
|
||||
components={{
|
||||
@@ -45,7 +45,7 @@ function MarkdownRender(options: MarkdownOptions) {
|
||||
</code>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {Renderer, type RendererProps} from "amis";
|
||||
import {once} from "licia";
|
||||
import React from "react";
|
||||
import Markdown from "../Markdown.tsx";
|
||||
import {Renderer, type RendererProps} from "amis"
|
||||
import React from "react"
|
||||
import Markdown from "../Markdown.tsx"
|
||||
import './MarkdownEnhance.scss'
|
||||
import {once} from 'es-toolkit'
|
||||
|
||||
const MarkdownEnhance: React.FC<RendererProps> = props => {
|
||||
return (
|
||||
|
||||
@@ -6,14 +6,13 @@ import Overview from './pages/Overview.tsx'
|
||||
import Root from './pages/Root.tsx'
|
||||
import Test from './pages/Test.tsx'
|
||||
import StockList from './pages/stock/StockList.tsx'
|
||||
import StockDetail from './pages/stock/StockDetail.tsx'
|
||||
import TaskList from './pages/task/TaskList.tsx'
|
||||
import TaskTemplateList from './pages/task/TaskTemplateList.tsx'
|
||||
import TaskTemplateSave from './pages/task/TaskTemplateSave.tsx'
|
||||
import TaskScheduleList from './pages/task/TaskScheduleList.tsx'
|
||||
import TaskScheduleSave from './pages/task/TaskScheduleSave.tsx'
|
||||
import StockCollectionList from './pages/stock/StockCollectionList.tsx'
|
||||
import TaskDetail from './pages/task/TaskDetail.tsx'
|
||||
import StockCollectionDetail from './pages/stock/StockCollectionDetail.tsx'
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
@@ -35,10 +34,6 @@ const routes: RouteObject[] = [
|
||||
path: 'list',
|
||||
Component: StockList,
|
||||
},
|
||||
{
|
||||
path: 'detail/:id',
|
||||
Component: StockDetail,
|
||||
},
|
||||
{
|
||||
path: "collection",
|
||||
children: [
|
||||
@@ -46,6 +41,10 @@ const routes: RouteObject[] = [
|
||||
path: 'list',
|
||||
Component: StockCollectionList,
|
||||
},
|
||||
{
|
||||
path: 'detail/:id',
|
||||
Component: StockCollectionDetail,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -68,10 +67,6 @@ const routes: RouteObject[] = [
|
||||
path: 'list',
|
||||
Component: TaskTemplateList,
|
||||
},
|
||||
{
|
||||
path: 'save/:id',
|
||||
Component: TaskTemplateSave,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
} from '@ant-design/icons'
|
||||
import {type AppItemProps, ProLayout} from '@ant-design/pro-components'
|
||||
import {ConfigProvider} from 'antd'
|
||||
import {dateFormat} from 'licia'
|
||||
import React, {useMemo} from 'react'
|
||||
import {NavLink, Outlet, useLocation} from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
@@ -53,7 +52,7 @@ const menus = {
|
||||
name: '股票集',
|
||||
icon: <StarOutlined/>,
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/task',
|
||||
@@ -88,7 +87,7 @@ const menus = {
|
||||
|
||||
const Root: React.FC = () => {
|
||||
const location = useLocation()
|
||||
const currentYear = useMemo(() => dateFormat(new Date(), 'yyyy'), [])
|
||||
const currentYear = useMemo(() => new Date().getFullYear(), [])
|
||||
return (
|
||||
<ProLayoutDiv>
|
||||
<ProLayout
|
||||
|
||||
50
leopard-web/src/pages/stock/StockCollectionDetail.tsx
Normal file
50
leopard-web/src/pages/stock/StockCollectionDetail.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from "react"
|
||||
import {amisRender, commonInfo, crudCommonOptions, paginationTemplate, stockListColumns} from '../../util/amis.tsx'
|
||||
import {useParams} from 'react-router'
|
||||
|
||||
function StockCollectionDetail() {
|
||||
const {id} = useParams()
|
||||
return (
|
||||
<div className="stock-collection-detail">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '股票集详情',
|
||||
initApi: `get:${commonInfo.baseUrl}/stock_collection/detail/${id}`,
|
||||
body: [
|
||||
{
|
||||
type: 'crud',
|
||||
source: '${scores}',
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(100, undefined, ['filter-toggler']),
|
||||
columns: stockListColumns(
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
className: 'white-space-pre',
|
||||
name: 'score',
|
||||
label: '得分',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
type: 'tpl',
|
||||
tpl: '${score}',
|
||||
popOver: {
|
||||
trigger: 'click',
|
||||
showIcon: false,
|
||||
body: {
|
||||
type: 'tpl',
|
||||
tpl: '${extra|raw}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(StockCollectionDetail)
|
||||
@@ -1,8 +1,103 @@
|
||||
import React from "react"
|
||||
import {amisRender, commonInfo, crudCommonOptions, paginationTemplate, time} from '../../util/amis.tsx'
|
||||
import {useNavigate} from 'react-router'
|
||||
|
||||
function StockCollectionList() {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<div className="stock-collection-list"></div>
|
||||
<div className="stock-collection-list">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '股票列表',
|
||||
body: [
|
||||
{
|
||||
type: 'crud',
|
||||
api: {
|
||||
method: 'post',
|
||||
url: `${commonInfo.baseUrl}/stock_collection/list`,
|
||||
data: {
|
||||
sort: [
|
||||
{
|
||||
column: 'createdTime',
|
||||
direction: 'DESC',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(15, undefined, ['filter-toggler']),
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
label: '名称',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: '描述',
|
||||
},
|
||||
{
|
||||
name: 'count',
|
||||
label: '股票数量',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: 'createdTime',
|
||||
label: '创建时间',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
...time('createdTime'),
|
||||
},
|
||||
{
|
||||
name: 'modifiedTime',
|
||||
label: '更新时间',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
...time('modifiedTime'),
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 100,
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(`/stock/collection/detail/${context.props.data['id']}`)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
className: 'text-danger btn-deleted',
|
||||
type: 'action',
|
||||
label: '删除',
|
||||
level: 'link',
|
||||
actionType: 'ajax',
|
||||
api: `get:${commonInfo.baseUrl}/stock_collection/remove/\${id}`,
|
||||
confirmText: '确认删除股票集<span class="text-lg font-bold mx-2">${name}</span>?',
|
||||
confirmTitle: '删除',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
import React from 'react'
|
||||
import {useParams} from 'react-router'
|
||||
import {amisRender, commonInfo, readOnlyDialogOptions, remoteMappings} from '../../util/amis.tsx'
|
||||
import type {Schema} from 'amis'
|
||||
|
||||
// 格式化财务数字显示的公共函数
|
||||
const formatFinanceNumber = (value: number): string => {
|
||||
if (value >= 100000000) {
|
||||
return (value / 100000000).toFixed(2) + '亿'
|
||||
} else if (value >= 10000) {
|
||||
return (value / 10000).toFixed(2) + '万'
|
||||
} else {
|
||||
return value.toLocaleString()
|
||||
}
|
||||
}
|
||||
|
||||
const financePropertyLabel = (id: string | undefined, label: string, type: string, field: string): Schema => {
|
||||
if (!id) {
|
||||
return {
|
||||
type: 'tpl',
|
||||
tpl: label,
|
||||
}
|
||||
}
|
||||
let current = new Date().getFullYear()
|
||||
return {
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
body: [
|
||||
label,
|
||||
{
|
||||
className: 'ml-1 text-secondary',
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-eye',
|
||||
level: 'link',
|
||||
size: 'xs',
|
||||
tooltip: '查看五年趋势',
|
||||
tooltipPlacement: 'top',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: `${label}五年趋势`,
|
||||
size: 'md',
|
||||
bodyClassName: 'p-0',
|
||||
...readOnlyDialogOptions(),
|
||||
body: {
|
||||
type: 'chart',
|
||||
api: `get:${commonInfo.baseUrl}/stock/finance/${id}/${type}/${field}`,
|
||||
height: 500,
|
||||
config: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
borderColor: '#ccc',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#333',
|
||||
},
|
||||
padding: [10, 15],
|
||||
formatter: (params: any) => {
|
||||
const item = params[0]
|
||||
return `${item.name}<br/>${item.marker}${formatFinanceNumber(item.value)}`
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
top: '10%',
|
||||
bottom: '15%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [
|
||||
current - 5,
|
||||
current - 4,
|
||||
current - 3,
|
||||
current - 2,
|
||||
current - 1,
|
||||
],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
show: true,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#f0f0f0',
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999',
|
||||
fontSize: 12,
|
||||
formatter: (value: number) => {
|
||||
return formatFinanceNumber(value)
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: '${detail || []}',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: true,
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#4096ff',
|
||||
shadowColor: 'rgba(64, 150, 255, 0.3)',
|
||||
shadowBlur: 5,
|
||||
shadowOffsetY: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#4096ff',
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff',
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: 'rgba(64, 150, 255, 0.2)',
|
||||
}, {
|
||||
offset: 1, color: 'rgba(64, 150, 255, 0.01)',
|
||||
}],
|
||||
},
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#333',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 12,
|
||||
formatter: (params: any) => {
|
||||
return formatFinanceNumber(params.value)
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
function StockDetail() {
|
||||
const {id} = useParams()
|
||||
return (
|
||||
<div className="stock-detail">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '股票详情(${code} ${name})',
|
||||
initApi: `get:${commonInfo.baseUrl}/stock/detail/${id}`,
|
||||
body: [
|
||||
{
|
||||
type: 'property',
|
||||
items: [
|
||||
{label: '编码', content: '${code}'},
|
||||
{label: '名称', content: '${name}'},
|
||||
{label: '全名', content: '${fullname}'},
|
||||
{
|
||||
label: '市场',
|
||||
content: {
|
||||
...remoteMappings('stock_market', 'market'),
|
||||
value: '${market}',
|
||||
},
|
||||
},
|
||||
{label: '行业', content: '${industry}'},
|
||||
{label: '上市日期', content: '${listedDate}'},
|
||||
],
|
||||
},
|
||||
{type: 'divider'},
|
||||
{
|
||||
type: 'service',
|
||||
api: `get:${commonInfo.baseUrl}/stock/finance/${id}`,
|
||||
body: [
|
||||
'资产负债表',
|
||||
{
|
||||
className: 'my-2',
|
||||
type: 'property',
|
||||
items: [
|
||||
{
|
||||
label: financePropertyLabel(id, '总资产', 'balanceSheet', 'totalAssets'),
|
||||
content: '${balanceSheet.totalAssets}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(id, '流动资产', 'balanceSheet', 'totalCurrentAssets'),
|
||||
content: '${balanceSheet.totalCurrentAssets}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(id, '非流动资产', 'balanceSheet', 'totalNonCurrentAssets'),
|
||||
content: '${balanceSheet.totalNonCurrentAssets}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(id, '总负债', 'balanceSheet', 'totalLiabilities'),
|
||||
content: '${balanceSheet.totalLiabilities}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(id, '流动负债', 'balanceSheet', 'totalCurrentLiabilities'),
|
||||
content: '${balanceSheet.totalCurrentLiabilities}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(id, '非流动负债', 'balanceSheet', 'totalNonCurrentLiabilities'),
|
||||
content: '${balanceSheet.totalNonCurrentLiabilities}',
|
||||
},
|
||||
],
|
||||
},
|
||||
'利润表',
|
||||
{
|
||||
className: 'my-2',
|
||||
type: 'property',
|
||||
items: [
|
||||
{
|
||||
label: financePropertyLabel(id, '营业总收入', 'income', 'totalOperatingRevenue'),
|
||||
content: '${income.totalOperatingRevenue}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(id, '营业总成本', 'income', 'totalOperatingCost'),
|
||||
content: '${income.totalOperatingCost}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(id, '营业总利润', 'income', 'totalProfit'),
|
||||
content: '${income.totalProfit}',
|
||||
},
|
||||
],
|
||||
},
|
||||
'现金流量表',
|
||||
{
|
||||
className: 'my-2',
|
||||
type: 'property',
|
||||
items: [
|
||||
{
|
||||
label: financePropertyLabel(id, '净利润', 'cashflow', 'netProfit'),
|
||||
content: '${cashFlow.netProfit}',
|
||||
},
|
||||
],
|
||||
},
|
||||
{type: 'divider'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(StockDetail)
|
||||
@@ -3,15 +3,12 @@ import {
|
||||
amisRender,
|
||||
commonInfo,
|
||||
crudCommonOptions,
|
||||
date,
|
||||
paginationTemplate,
|
||||
remoteMappings,
|
||||
remoteOptions,
|
||||
stockListColumns,
|
||||
} from '../../util/amis.tsx'
|
||||
import {useNavigate} from 'react-router'
|
||||
|
||||
function StockList() {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<div className="stock-list">
|
||||
{amisRender(
|
||||
@@ -97,65 +94,7 @@ function StockList() {
|
||||
},
|
||||
],
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
name: 'code',
|
||||
label: '编号',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: '简称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'fullname',
|
||||
label: '全名',
|
||||
},
|
||||
{
|
||||
name: 'market',
|
||||
label: '市场',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
...remoteMappings('stock_market', 'market'),
|
||||
},
|
||||
{
|
||||
name: 'industry',
|
||||
label: '行业',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
label: '上市日期',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
...date('listedDate'),
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 100,
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(`/stock/detail/${context.props.data['id']}`)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
columns: stockListColumns(),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ function TaskDetail() {
|
||||
label: '进度',
|
||||
content: {
|
||||
type: 'tpl',
|
||||
tpl: "${step}%",
|
||||
tpl: "${ROUND(step * 100, 2)}%",
|
||||
},
|
||||
span: 2,
|
||||
},
|
||||
@@ -56,9 +56,10 @@ function TaskDetail() {
|
||||
},
|
||||
{
|
||||
visibleOn: 'result',
|
||||
type: 'editor',
|
||||
type: 'markdown-enhance',
|
||||
name: 'result',
|
||||
label: '结果',
|
||||
content: '${result}',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import React from 'react'
|
||||
import {amisRender, commonInfo, crudCommonOptions, paginationTemplate, remoteMappings, time} from '../../util/amis.tsx'
|
||||
import {
|
||||
amisRender,
|
||||
commonInfo,
|
||||
crudCommonOptions,
|
||||
horizontalFormOptions,
|
||||
paginationTemplate,
|
||||
remoteMappings,
|
||||
remoteOptions,
|
||||
time,
|
||||
} from '../../util/amis.tsx'
|
||||
import {useNavigate} from 'react-router'
|
||||
|
||||
function TaskList() {
|
||||
@@ -25,7 +34,41 @@ function TaskList() {
|
||||
},
|
||||
interval: 30000,
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(15),
|
||||
...paginationTemplate(
|
||||
15,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-plus',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '创建任务',
|
||||
body: {
|
||||
type: 'form',
|
||||
api: {
|
||||
method: 'post',
|
||||
url: `${commonInfo.baseUrl}/task/execute`,
|
||||
data: {
|
||||
templateId: '${templateId|default:undefined}',
|
||||
},
|
||||
},
|
||||
...horizontalFormOptions(),
|
||||
body: [
|
||||
{
|
||||
name: 'templateId',
|
||||
label: '名称',
|
||||
required: true,
|
||||
selectFirst: true,
|
||||
...remoteOptions('select', 'task_template_id'),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
@@ -44,10 +87,10 @@ function TaskList() {
|
||||
...remoteMappings('task_status', 'status'),
|
||||
},
|
||||
{
|
||||
name: 'step',
|
||||
label: '进度',
|
||||
type: 'progress',
|
||||
width: 200,
|
||||
value: '${ROUND(step * 100, 0)}',
|
||||
},
|
||||
{
|
||||
label: '耗时',
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from 'react'
|
||||
import {amisRender, commonInfo, crudCommonOptions, paginationTemplate} from '../../util/amis.tsx'
|
||||
import {useNavigate} from 'react-router'
|
||||
|
||||
function TaskTemplateList() {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<div className="task-template-list">
|
||||
{amisRender(
|
||||
@@ -13,43 +11,10 @@ function TaskTemplateList() {
|
||||
body: [
|
||||
{
|
||||
type: 'crud',
|
||||
api: {
|
||||
method: 'post',
|
||||
url: `${commonInfo.baseUrl}/task_template/list`,
|
||||
data: {
|
||||
page: {
|
||||
index: '${page}',
|
||||
size: '${perPage}',
|
||||
},
|
||||
},
|
||||
},
|
||||
api: `get:${commonInfo.baseUrl}/task/template/list`,
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(
|
||||
15,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-plus',
|
||||
tooltip: '添加模板',
|
||||
tooltipPlacement: 'top',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate('/task/template/save/-1')
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
...paginationTemplate(15),
|
||||
loadOnce: true,
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
@@ -63,7 +28,7 @@ function TaskTemplateList() {
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 150,
|
||||
width: 100,
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
@@ -78,35 +43,7 @@ function TaskTemplateList() {
|
||||
},
|
||||
},
|
||||
confirmText: '确认执行模板<span class="text-lg font-bold mx-2">${name}</span>?',
|
||||
confirmTitle: '删除',
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(`/task/template/save/${context.props.data['id']}`)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
className: 'text-danger btn-deleted',
|
||||
type: 'action',
|
||||
label: '删除',
|
||||
level: 'link',
|
||||
actionType: 'ajax',
|
||||
api: `get:${commonInfo.baseUrl}/task_template/remove/\${id}`,
|
||||
confirmText: '确认删除模板<span class="text-lg font-bold mx-2">${name}</span>?',
|
||||
confirmTitle: '删除',
|
||||
confirmTitle: '执行',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import React from 'react'
|
||||
import {amisRender, commonInfo} from '../../util/amis.tsx'
|
||||
import {useNavigate, useParams} from 'react-router'
|
||||
|
||||
function TaskTemplateSave() {
|
||||
const navigate = useNavigate()
|
||||
const {id} = useParams()
|
||||
return (
|
||||
<div className="task-template-save">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '任务模板添加',
|
||||
body: [
|
||||
{
|
||||
debug: commonInfo.debug,
|
||||
type: 'form',
|
||||
api: `post:${commonInfo.baseUrl}/task_template/save`,
|
||||
initApi: `get:${commonInfo.baseUrl}/task_template/detail/${id}`,
|
||||
initFetchOn: `${id} !== -1`,
|
||||
wrapWithPanel: false,
|
||||
mode: 'horizontal',
|
||||
labelAlign: 'left',
|
||||
onEvent: {
|
||||
submitSucc: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(-1)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'hidden',
|
||||
name: 'id',
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'name',
|
||||
label: '名称',
|
||||
required: true,
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'description',
|
||||
label: '描述',
|
||||
required: true,
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'expression',
|
||||
label: 'EL表达式',
|
||||
required: true,
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'button-toolbar',
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '提交',
|
||||
actionType: 'submit',
|
||||
level: 'primary',
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '重置',
|
||||
actionType: 'reset',
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '返回',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(-1)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(TaskTemplateSave)
|
||||
@@ -1,11 +1,13 @@
|
||||
import {AlertComponent, attachmentAdpator, makeTranslator, render, type Schema, ToastComponent} from 'amis'
|
||||
|
||||
import {AlertComponent, type Api, attachmentAdpator, makeTranslator, render, type Schema, ToastComponent} from 'amis'
|
||||
import 'amis/lib/themes/antd.css'
|
||||
import 'amis/lib/helper.css'
|
||||
import 'amis/sdk/iconfont.css'
|
||||
import '@fortawesome/fontawesome-free/css/all.min.css'
|
||||
import axios from 'axios'
|
||||
import {isEqual} from 'licia'
|
||||
import {isEqual, isNil} from 'es-toolkit'
|
||||
// @ts-ignore
|
||||
import type {ColumnSchema} from 'amis/lib/renderers/Table2'
|
||||
import {toNumber} from 'es-toolkit/compat'
|
||||
|
||||
export const commonInfo = {
|
||||
debug: isEqual(import.meta.env.MODE, 'development'),
|
||||
@@ -335,3 +337,672 @@ export function remoteMappings(name: string, field: string) {
|
||||
source: `get:${commonInfo.baseUrl}/constants/mappings/${name}/${field}`,
|
||||
}
|
||||
}
|
||||
|
||||
const formatFinanceNumber = (value: number): string => {
|
||||
if (isNil(value)) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const isNegative = value < 0
|
||||
const absoluteValue = Math.abs(value)
|
||||
|
||||
let formatted: string
|
||||
if (absoluteValue >= 100000000) {
|
||||
formatted = (absoluteValue / 100000000).toFixed(2) + '亿'
|
||||
} else if (absoluteValue >= 10000) {
|
||||
formatted = (absoluteValue / 10000).toFixed(2) + '万'
|
||||
} else {
|
||||
formatted = absoluteValue.toLocaleString()
|
||||
}
|
||||
|
||||
return isNegative ? `-${formatted}` : formatted
|
||||
}
|
||||
|
||||
const formatDaysNumber = (value: number): string => {
|
||||
if (isNil(value)) {
|
||||
return '-'
|
||||
}
|
||||
return `${value.toFixed(0)}天`
|
||||
}
|
||||
|
||||
const formatPercentageNumber = (value: number): string => {
|
||||
if (isNil(value)) {
|
||||
return '-'
|
||||
}
|
||||
return `${(value * 100).toFixed(2)}%`
|
||||
}
|
||||
|
||||
type FinanceType = 'PERCENTAGE' | 'FINANCE' | 'DAYS'
|
||||
|
||||
const financePropertyLabel = (idField: string, label: string, type: FinanceType, field: string): Schema => {
|
||||
let formatter: (value: number) => string
|
||||
switch (type) {
|
||||
case 'PERCENTAGE':
|
||||
formatter = formatPercentageNumber
|
||||
break
|
||||
case 'FINANCE':
|
||||
formatter = formatFinanceNumber
|
||||
break
|
||||
case 'DAYS':
|
||||
formatter = formatDaysNumber
|
||||
break
|
||||
default:
|
||||
formatter = (v: number) => v.toFixed(2)
|
||||
}
|
||||
return {
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
body: [
|
||||
{
|
||||
visibleOn: `\${!${idField}}`,
|
||||
type: 'tpl',
|
||||
tpl: label,
|
||||
},
|
||||
{
|
||||
visibleOn: `\${${idField}}`,
|
||||
className: 'text-current font-bold',
|
||||
type: 'action',
|
||||
label: label,
|
||||
level: 'link',
|
||||
tooltip: '这是什么?',
|
||||
tooltipPlacement: 'top',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '',
|
||||
size: 'lg',
|
||||
...readOnlyDialogOptions(),
|
||||
actions: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '新页面打开',
|
||||
icon: 'fa fa-solid fa-arrow-up-right-from-square',
|
||||
actionType: 'url',
|
||||
url: `https://zh.wikipedia.org/wiki/${label}`,
|
||||
blank: true,
|
||||
},
|
||||
],
|
||||
body: {
|
||||
type: 'iframe',
|
||||
src: `https://zh.wikipedia.org/wiki/${label}`,
|
||||
height: 800,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
className: 'text-secondary',
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-eye',
|
||||
level: 'link',
|
||||
size: 'xs',
|
||||
tooltip: '查看五年趋势',
|
||||
tooltipPlacement: 'top',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: `${label}五年趋势`,
|
||||
size: 'lg',
|
||||
bodyClassName: 'p-0',
|
||||
...readOnlyDialogOptions(),
|
||||
body: {
|
||||
type: 'chart',
|
||||
api: `get:${commonInfo.baseUrl}/stock/finance/\${${idField}}/${field}`,
|
||||
height: 500,
|
||||
config: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
borderColor: '#ccc',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#333',
|
||||
},
|
||||
padding: [10, 15],
|
||||
formatter: (params: any) => {
|
||||
const item = params[0]
|
||||
return `${item.name}<br/>${item.marker}${formatter(item.value)}`
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
top: '10%',
|
||||
bottom: '15%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: '${xList || []}',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
show: true,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#f0f0f0',
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999',
|
||||
fontSize: 12,
|
||||
formatter: (value: number) => {
|
||||
return formatter(value)
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: '${yList || []}',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: true,
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#4096ff',
|
||||
shadowColor: 'rgba(64, 150, 255, 0.3)',
|
||||
shadowBlur: 5,
|
||||
shadowOffsetY: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#4096ff',
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff',
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: 'rgba(64, 150, 255, 0.2)',
|
||||
}, {
|
||||
offset: 1, color: 'rgba(64, 150, 255, 0.01)',
|
||||
}],
|
||||
},
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#333',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 12,
|
||||
formatter: (params: any) => {
|
||||
return formatter(params.value)
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const candleChart = (title: string, subtitle: string, api: Api): Schema => {
|
||||
return {
|
||||
className: 'mt-2',
|
||||
type: 'chart',
|
||||
height: 500,
|
||||
api: api,
|
||||
config: {
|
||||
title: {
|
||||
text: title,
|
||||
subtext: subtitle,
|
||||
},
|
||||
backgroundColor: '#fff',
|
||||
animation: true,
|
||||
animationDuration: 1000,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
},
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
borderColor: '#333',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
},
|
||||
padding: 12,
|
||||
formatter: function (params: any) {
|
||||
const param = params[0]
|
||||
const open = toNumber(param.data[1]).toFixed(2)
|
||||
const close = toNumber(param.data[2]).toFixed(2)
|
||||
const lowest = toNumber(param.data[3]).toFixed(2)
|
||||
const highest = toNumber(param.data[4]).toFixed(2)
|
||||
|
||||
return `<div class="text-center font-bold mb-2">${param.name}</div>
|
||||
<div class="text-center">
|
||||
<span>开盘:</span>
|
||||
<span class="font-bold ml-4">${open}</span>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<span>收盘:</span>
|
||||
<span class="font-bold ml-4">${close}</span>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<span>最低:</span>
|
||||
<span class="font-bold ml-4">${lowest}</span>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<span>最高:</span>
|
||||
<span class="font-bold ml-4">${highest}</span>
|
||||
</div>`
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
top: '15%',
|
||||
bottom: '15%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
data: '${xList || []}',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
scale: true,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontWeight: 'bold',
|
||||
formatter: function (value: number) {
|
||||
return value.toFixed(2)
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#f0f0f0',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
scale: true,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
show: true,
|
||||
type: 'slider',
|
||||
top: '90%',
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'candlestick',
|
||||
data: '${yList || []}',
|
||||
yAxisIndex: 0,
|
||||
itemStyle: {
|
||||
color: '#eb5454',
|
||||
color0: '#4aaa93',
|
||||
borderColor: '#eb5454',
|
||||
borderColor0: '#4aaa93',
|
||||
borderWidth: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
yAxisIndex: 0,
|
||||
data: '${sma10 || []}',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: 'rgba(25,147,51,0.5)',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
yAxisIndex: 0,
|
||||
data: '${sma30 || []}',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: 'rgba(10,94,131,0.5)',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
yAxisIndex: 0,
|
||||
data: '${sma60 || []}',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: 'rgba(231,15,130,0.5)',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function stockListColumns(idField: string = 'id', extraColumns: Array<ColumnSchema> = []) {
|
||||
return [
|
||||
{
|
||||
name: 'code',
|
||||
label: '编号',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: '简称',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: 'fullname',
|
||||
label: '全名',
|
||||
},
|
||||
{
|
||||
name: 'market',
|
||||
label: '市场',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
...remoteMappings('stock_market', 'market'),
|
||||
},
|
||||
{
|
||||
name: 'industry',
|
||||
label: '行业',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
label: '上市日期',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
...date('listedDate'),
|
||||
},
|
||||
...extraColumns,
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 100,
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '股票详情',
|
||||
size: 'full',
|
||||
...readOnlyDialogOptions(),
|
||||
body: [
|
||||
{
|
||||
type: 'property',
|
||||
items: [
|
||||
{label: '编码', content: '${code}'},
|
||||
{label: '名称', content: '${name}'},
|
||||
{label: '全名', content: '${fullname}'},
|
||||
{
|
||||
label: '市场',
|
||||
content: {
|
||||
...remoteMappings('stock_market', 'market'),
|
||||
value: '${market}',
|
||||
},
|
||||
},
|
||||
{label: '行业', content: '${industry}'},
|
||||
{label: '上市日期', content: '${listedDate}'},
|
||||
],
|
||||
},
|
||||
{type: 'divider'},
|
||||
{
|
||||
type: 'service',
|
||||
api: `get:${commonInfo.baseUrl}/stock/finance/\${${idField}}`,
|
||||
body: [
|
||||
'资产负债表',
|
||||
{
|
||||
className: 'my-2',
|
||||
type: 'property',
|
||||
column: 4,
|
||||
items: [
|
||||
{
|
||||
label: financePropertyLabel(idField, '总资产', 'FINANCE', 'totalAssets'),
|
||||
content: '${balanceSheet.totalAssets}',
|
||||
span: 2,
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '总负债', 'FINANCE', 'totalLiabilities'),
|
||||
content: '${balanceSheet.totalLiabilities}',
|
||||
span: 2,
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '流动资产', 'FINANCE', 'currentAssets'),
|
||||
content: '${balanceSheet.currentAssets}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '流动资产占比', 'PERCENTAGE', 'currentAssetsToTotalAssetsRatio'),
|
||||
content: '${balanceSheet.currentAssetsRatio}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '流动负债', 'FINANCE', 'currentLiabilities'),
|
||||
content: '${balanceSheet.currentLiabilities}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '流动负债占比', 'PERCENTAGE', 'currentLiabilitiesToTotalAssetsRatio'),
|
||||
content: '${balanceSheet.currentLiabilitiesRatio}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '非流动资产', 'FINANCE', 'fixedAssets'),
|
||||
content: '${balanceSheet.fixedAssets}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '非流动资产占比', 'PERCENTAGE', 'fixedAssetsToTotalAssetsRatio'),
|
||||
content: '${balanceSheet.fixedAssetsRatio}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '非流动负债', 'FINANCE', 'longTermLiabilities'),
|
||||
content: '${balanceSheet.longTermLiabilities}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '非流动负债占比', 'PERCENTAGE', 'longTermLiabilitiesToTotalAssetsRatio'),
|
||||
content: '${balanceSheet.longTermLiabilitiesRatio}',
|
||||
},
|
||||
],
|
||||
},
|
||||
'利润表',
|
||||
{
|
||||
className: 'my-2',
|
||||
type: 'property',
|
||||
items: [
|
||||
{
|
||||
label: financePropertyLabel(idField, '营业收入', 'FINANCE', 'operatingRevenue'),
|
||||
content: '${income.operatingRevenue}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '营业成本', 'FINANCE', 'operatingCost'),
|
||||
content: '${income.operatingCost}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '营业利润', 'FINANCE', 'operatingProfit'),
|
||||
content: '${income.operatingProfit}',
|
||||
},
|
||||
],
|
||||
},
|
||||
'现金流量表',
|
||||
{
|
||||
className: 'my-2',
|
||||
type: 'property',
|
||||
items: [
|
||||
{
|
||||
label: financePropertyLabel(idField, '净利润', 'FINANCE', 'netProfit'),
|
||||
content: '${cashFlow.netProfit}',
|
||||
span: 3,
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '营业活动现金流量', 'FINANCE', 'cashFlowFromOperatingActivities'),
|
||||
content: '${cashFlow.cashFlowFromOperatingActivities}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '投资活动现金流量', 'FINANCE', 'cashFlowFromInvestingActivities'),
|
||||
content: '${cashFlow.cashFlowFromInvestingActivities}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '筹资活动现金流量', 'FINANCE', 'cashFlowFromFinancingActivities'),
|
||||
content: '${cashFlow.cashFlowFromFinancingActivities}',
|
||||
},
|
||||
],
|
||||
},
|
||||
'财务指标',
|
||||
{
|
||||
className: 'my-2',
|
||||
type: 'property',
|
||||
column: 4,
|
||||
items: [
|
||||
{
|
||||
label: financePropertyLabel(idField, '流动比率', 'FINANCE', 'currentRatio'),
|
||||
content: '${indicate.currentRatio}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '速动比率', 'FINANCE', 'quickRatio'),
|
||||
content: '${indicate.quickRatio}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, 'ROE', 'FINANCE', 'returnOnEquity'),
|
||||
content: '${indicate.roe}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, 'ROA', 'FINANCE', 'returnOnAssets'),
|
||||
content: '${indicate.roa}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '应收账款周转率', 'FINANCE', 'accountsReceivableTurnover'),
|
||||
content: '${indicate.accountsReceivableTurnover}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '应收账款周转天数', 'DAYS', 'daysAccountsReceivableTurnover'),
|
||||
content: '${indicate.daysAccountsReceivableTurnover}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '存货周转率', 'FINANCE', 'inventoryTurnover'),
|
||||
content: '${indicate.inventoryTurnover}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '存货周转天数', 'DAYS', 'daysInventoryTurnover'),
|
||||
content: '${indicate.daysInventoryTurnover}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '固定资产周转率', 'FINANCE', 'fixedAssetsTurnover'),
|
||||
content: '${indicate.fixedAssetsTurnover}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '固定资产周转天数', 'DAYS', 'daysFixedAssetsTurnover'),
|
||||
content: '${indicate.daysFixedAssetsTurnover}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '总资产周转率', 'FINANCE', 'totalAssetsTurnover'),
|
||||
content: '${indicate.totalAssetsTurnover}',
|
||||
},
|
||||
{
|
||||
label: financePropertyLabel(idField, '总资产周转天数', 'DAYS', 'daysTotalAssetsTurnover'),
|
||||
content: '${indicate.daysTotalAssetsTurnover}',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{type: 'divider'},
|
||||
{
|
||||
type: 'service',
|
||||
api: `get:${commonInfo.baseUrl}/stock/daily/current/\${${idField}}`,
|
||||
body: [
|
||||
"现价 (${date})",
|
||||
{
|
||||
className: 'my-2',
|
||||
type: 'property',
|
||||
column: 4,
|
||||
items: [
|
||||
{label: '开盘价', content: '${open}'},
|
||||
{label: '收盘价', content: '${close}'},
|
||||
{label: '最高价', content: '${high}'},
|
||||
{label: '最低价', content: '${low}'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
candleChart(
|
||||
'100日线数据',
|
||||
'后复权数据',
|
||||
`get:${commonInfo.baseUrl}/stock/daily/\${${idField}}`,
|
||||
),
|
||||
candleChart(
|
||||
'50周线数据',
|
||||
'后复权数据',
|
||||
`get:${commonInfo.baseUrl}/stock/weekly/\${${idField}}`,
|
||||
),
|
||||
candleChart(
|
||||
'24月线数据',
|
||||
'后复权数据',
|
||||
`get:${commonInfo.baseUrl}/stock/monthly/\${${idField}}`,
|
||||
),
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user