├── .tokeignore ├── CHANGELOG.md ├── crates ├── rsquant-core │ ├── src │ │ ├── db │ │ │ ├── mod.rs │ │ │ └── service.rs │ │ ├── model │ │ │ ├── trade │ │ │ │ ├── mod.rs │ │ │ │ └── order.rs │ │ │ ├── market │ │ │ │ ├── mod.rs │ │ │ │ ├── ticker_price.rs │ │ │ │ └── kline.rs │ │ │ ├── account │ │ │ │ ├── mod.rs │ │ │ │ ├── coin_info.rs │ │ │ │ └── account_info.rs │ │ │ └── mod.rs │ │ ├── api │ │ │ ├── mod.rs │ │ │ ├── basic │ │ │ │ ├── mod.rs │ │ │ │ └── filters.rs │ │ │ ├── credential │ │ │ │ └── mod.rs │ │ │ └── req │ │ │ │ └── mod.rs │ │ ├── entity │ │ │ ├── mod.rs │ │ │ ├── side.rs │ │ │ └── order.rs │ │ ├── util │ │ │ ├── mod.rs │ │ │ ├── constants.rs │ │ │ ├── time │ │ │ │ ├── timezone.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── converter.rs │ │ │ │ └── current.rs │ │ │ ├── env.rs │ │ │ └── log.rs │ │ ├── trade │ │ │ ├── mod.rs │ │ │ ├── indicator │ │ │ │ ├── mod.rs │ │ │ │ ├── ema.rs │ │ │ │ ├── rsi.rs │ │ │ │ ├── data_item.rs │ │ │ │ └── macd.rs │ │ │ ├── macros.rs │ │ │ └── strategy │ │ │ │ ├── mod.rs │ │ │ │ └── rsi_and_double_ema.rs │ │ ├── actor │ │ │ ├── mod.rs │ │ │ ├── strategy.rs │ │ │ └── send_email.rs │ │ └── error.rs │ ├── migration │ │ ├── src │ │ │ ├── main.rs │ │ │ ├── lib.rs │ │ │ └── m20240621_165714_create_table.rs │ │ ├── Cargo.toml │ │ └── README.md │ ├── template │ │ └── email │ │ │ └── monitor.html │ └── Cargo.toml ├── rsquant-tool │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── rsquant-derive │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── rsquant-bt │ └── Cargo.toml └── bin │ ├── bt.rs │ ├── crawler.rs │ └── main.rs ├── web ├── src │ ├── utils │ │ └── constants.ts │ ├── assets │ │ └── favicon.ico │ ├── pages │ │ ├── Home.tsx │ │ └── GreetPage.tsx │ ├── types.ts │ ├── index.css │ ├── components │ │ └── SymbolCard.tsx │ ├── index.tsx │ └── logo.svg ├── postcss.config.js ├── tailwind.config.js ├── tsconfig.json ├── index.html ├── vite.config.ts ├── package.json └── README.md ├── docs └── assets │ ├── exec-flow.png │ ├── project-structure.png │ └── quant-trader-avatar.png ├── .gitmodules ├── TODO.md ├── .prettierrc ├── rust-toolchain.toml ├── rustfmt.toml ├── .markdownlint.yaml ├── .dockerignore ├── .cargo └── config.toml ├── deps └── binan_spot │ ├── src │ ├── http │ │ ├── mod.rs │ │ ├── method.rs │ │ ├── credentials.rs │ │ └── request.rs │ ├── user_data_stream │ │ ├── mod.rs │ │ └── user_data.rs │ ├── stream │ │ ├── mod.rs │ │ ├── close_listen_key.rs │ │ ├── new_listen_key.rs │ │ └── renew_listen_key.rs │ ├── margin_stream │ │ ├── mod.rs │ │ ├── close_listen_key.rs │ │ ├── new_listen_key.rs │ │ └── renew_listen_key.rs │ ├── isolated_margin_stream │ │ ├── mod.rs │ │ ├── new_listen_key.rs │ │ ├── close_listen_key.rs │ │ └── renew_listen_key.rs │ ├── websocket.rs │ ├── trade │ │ ├── order.rs │ │ ├── get_open_oco_orders.rs │ │ ├── order_limit_usage.rs │ │ └── cancel_open_orders.rs │ ├── ureq │ │ ├── error.rs │ │ ├── mod.rs │ │ └── response.rs │ ├── market_stream │ │ ├── trade.rs │ │ ├── agg_trade.rs │ │ ├── kline.rs │ │ ├── ticker.rs │ │ ├── book_ticker.rs │ │ ├── mini_ticker.rs │ │ ├── diff_depth.rs │ │ ├── partial_depth.rs │ │ ├── rolling_window_ticker.rs │ │ └── mod.rs │ ├── hyper │ │ ├── mod.rs │ │ ├── error.rs │ │ └── response.rs │ ├── market │ │ ├── ping.rs │ │ ├── time.rs │ │ ├── avg_price.rs │ │ ├── mod.rs │ │ ├── trades.rs │ │ ├── depth.rs │ │ └── exchange_info.rs │ ├── wallet │ │ ├── system_status.rs │ │ ├── dustable_assets.rs │ │ ├── api_key_permission.rs │ │ ├── account_status.rs │ │ ├── coin_info.rs │ │ ├── api_trading_status.rs │ │ └── disable_fast_withdraw.rs │ └── margin │ │ ├── margin_all_pairs.rs │ │ ├── margin_all_assets.rs │ │ ├── margin_asset.rs │ │ ├── margin_pair.rs │ │ ├── margin_price_index.rs │ │ ├── bnb_burn_status.rs │ │ ├── margin_account.rs │ │ ├── isolated_margin_all_symbols.rs │ │ ├── isolated_margin_symbol.rs │ │ └── isolated_margin_account_limit.rs │ └── Cargo.toml ├── docker-compose.yml ├── scripts ├── database.sh ├── docker.sh ├── check.sh ├── web.sh └── rust.sh ├── .github └── workflows │ ├── lint.yaml │ └── build.yaml ├── .gitignore ├── LICENSE ├── rsquant.sh ├── .pre-commit-config.yaml ├── Cargo.toml └── Dockerfile /.tokeignore: -------------------------------------------------------------------------------- 1 | binan_spot* 2 | 3 | *.toml 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.1 - Undefined 4 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/db/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod service; 2 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/model/trade/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod order; 2 | -------------------------------------------------------------------------------- /web/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const baseUrl = "ws://127.0.0.1:8000/" 2 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/model/market/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod kline; 2 | pub mod ticker_price; 3 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/model/account/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod account_info; 2 | pub mod coin_info; 3 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod basic; 2 | pub mod credential; 3 | pub mod req; 4 | -------------------------------------------------------------------------------- /crates/rsquant-tool/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub trait Name { 2 | fn get_name(&self) -> String; 3 | } 4 | -------------------------------------------------------------------------------- /docs/assets/exec-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hnlcf/rsquant/HEAD/docs/assets/exec-flow.png -------------------------------------------------------------------------------- /web/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hnlcf/rsquant/HEAD/web/src/assets/favicon.ico -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/barter-rs"] 2 | path = deps/barter-rs 3 | url = git@github.com:hnlcf/barter-rs.git 4 | -------------------------------------------------------------------------------- /docs/assets/project-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hnlcf/rsquant/HEAD/docs/assets/project-structure.png -------------------------------------------------------------------------------- /crates/rsquant-core/src/entity/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod order; 2 | pub mod side; 3 | 4 | pub use order::Entity as OrderEntity; 5 | -------------------------------------------------------------------------------- /docs/assets/quant-trader-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hnlcf/rsquant/HEAD/docs/assets/quant-trader-avatar.png -------------------------------------------------------------------------------- /web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [ ] api request 2 | - [ ] strategy 3 | - [ ] trade 4 | - [ ] scheduler 5 | - [ ] refactor to actor model with `actix` 6 | -------------------------------------------------------------------------------- /crates/rsquant-tool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsquant-tool" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "printWidth": 120, 5 | "semi": false, 6 | "singleQuote": false 7 | } 8 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod constants; 3 | pub mod email; 4 | pub mod env; 5 | pub mod log; 6 | pub mod time; 7 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = ["rustfmt", "clippy", "rust-analyzer"] 4 | targets = ["x86_64-unknown-linux-musl"] 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_layout = "Vertical" 2 | imports_granularity = "Crate" 3 | group_imports = "StdExternalCrate" 4 | normalize_comments = true 5 | reorder_impl_items = true 6 | -------------------------------------------------------------------------------- /crates/rsquant-core/migration/src/main.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | #[async_std::main] 4 | async fn main() { 5 | cli::run_cli(migration::Migrator).await; 6 | } 7 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | default: true 2 | MD013: false 3 | MD024: 4 | siblings_only: true 5 | MD029: 6 | style: ordered 7 | MD033: false 8 | MD041: false 9 | MD046: false 10 | MD049: false 11 | -------------------------------------------------------------------------------- /web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.pdb 3 | **/*.rs.bk 4 | *.sublime-* 5 | 6 | core.* 7 | nohup.out 8 | Cargo.lock 9 | 10 | debug/ 11 | target/ 12 | database/ 13 | log/ 14 | 15 | .vscode/ 16 | .idea/ 17 | .venv 18 | __pycache__ 19 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # [target.x86_64-unknown-linux-musl] 2 | # rustflags = [ 3 | # "-C", 4 | # "linker=clang", 5 | # "-C", 6 | # "link-arg=-fuse-ld=mold", 7 | # "-C", 8 | # "force-frame-pointers=yes", 9 | # ] 10 | -------------------------------------------------------------------------------- /web/src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import { Component, For, createSignal } from "solid-js" 2 | 3 | const Home: Component = () => { 4 | return ( 5 | <> 6 |

Hello rsquant

7 | 8 | ) 9 | } 10 | 11 | export default Home 12 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/api/basic/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod enum_def; 2 | pub mod filters; 3 | 4 | pub use binan_spot::{ 5 | http::{ 6 | Credentials, 7 | Method, 8 | }, 9 | hyper::create_query_string, 10 | utils::sign, 11 | }; 12 | -------------------------------------------------------------------------------- /deps/binan_spot/src/http/mod.rs: -------------------------------------------------------------------------------- 1 | mod credentials; 2 | mod method; 3 | 4 | pub mod error; 5 | pub mod request; 6 | 7 | pub use credentials::{ 8 | Credentials, 9 | HmacSignature, 10 | RsaSignature, 11 | Signature, 12 | }; 13 | pub use method::Method; 14 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/trade/mod.rs: -------------------------------------------------------------------------------- 1 | mod indicator; 2 | mod macros; 3 | mod strategy; 4 | 5 | pub use indicator::{ 6 | Indicator, 7 | ToDataItem, 8 | }; 9 | pub use strategy::{ 10 | CommonMacdAndRsiStrategy, 11 | DoubleEmaStrategy, 12 | Strategy, 13 | }; 14 | -------------------------------------------------------------------------------- /crates/rsquant-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsquant-derive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | rsquant-tool = { path = "../rsquant-tool" } 11 | 12 | proc-macro2 = "1.0.86" 13 | quote = "1.0.36" 14 | syn = "2.0.67" 15 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/actor/mod.rs: -------------------------------------------------------------------------------- 1 | mod binan_api; 2 | mod frontend; 3 | mod send_email; 4 | mod strategy; 5 | 6 | pub use binan_api::BinanApiActor; 7 | pub use frontend::{ 8 | run_web, 9 | SubscribeTickerActor, 10 | }; 11 | pub use send_email::EmailActor; 12 | pub use strategy::StrategyActor; 13 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/util/constants.rs: -------------------------------------------------------------------------------- 1 | pub const DEFAULT_APP_NAME: &str = "rsquant"; 2 | pub const DEFAULT_LOG_FILE: &str = "rsquant.log"; 3 | pub const DEFAULT_POSTGRES_ADDR: &str = "postgres://postgres:postgres@localhost/rsquant"; 4 | 5 | pub const DEFAULT_DATETIME_FORMAT_STR: &str = "%Y-%m-%d %H:%M:%S %z"; 6 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/trade/indicator/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod data_item; 2 | pub mod ema; 3 | pub mod macd; 4 | pub mod rsi; 5 | 6 | pub use data_item::ToDataItem; 7 | use ta::DataItem; 8 | 9 | pub trait Indicator { 10 | type Output; 11 | fn compute(&mut self, data: &[DataItem]) -> Self::Output; 12 | } 13 | -------------------------------------------------------------------------------- /deps/binan_spot/src/user_data_stream/mod.rs: -------------------------------------------------------------------------------- 1 | //! Binance SPOT User Data Websocket Streams 2 | //! 3 | //! A collection of SPOT User Data Websocket streams. 4 | mod user_data; 5 | 6 | pub use user_data::UserDataStream; 7 | 8 | pub fn user_data(listen_key: &str) -> UserDataStream { 9 | UserDataStream::new(listen_key) 10 | } 11 | -------------------------------------------------------------------------------- /web/src/types.ts: -------------------------------------------------------------------------------- 1 | export type SubscribeTickerRequest = MultipleTickerApiRequest 2 | export type SubscribeTickerResponse = TickerPrice[] 3 | 4 | export interface MultipleTickerApiRequest { 5 | symbols: string[] 6 | interval: number 7 | } 8 | 9 | export interface TickerPrice { 10 | symbol: string 11 | price: number 12 | } 13 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/util/time/timezone.rs: -------------------------------------------------------------------------------- 1 | pub struct TimeZoneConverter; 2 | 3 | impl TimeZoneConverter { 4 | pub fn convert_utc_to_local(utc_time: u64) -> u64 { 5 | utc_time + 8 * 60 * 60 * 1000 6 | } 7 | 8 | pub fn convert_local_to_utc(local_time: u64) -> u64 { 9 | local_time - 8 * 60 * 60 * 1000 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /crates/rsquant-core/migration/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use sea_orm_migration::prelude::*; 2 | 3 | mod m20240621_165714_create_table; 4 | 5 | pub struct Migrator; 6 | 7 | #[async_trait::async_trait] 8 | impl MigratorTrait for Migrator { 9 | fn migrations() -> Vec> { 10 | vec![Box::new(m20240621_165714_create_table::Migration)] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/trade/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! min { 3 | ($x:expr) => { 4 | $x 5 | }; 6 | ($x:expr, $y:expr) => { 7 | if $x < $y { 8 | $x 9 | } else { 10 | $y 11 | } 12 | }; 13 | ($x:expr, $($rest:expr),+) => { 14 | min!($x, min!($($rest),+)) 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | postgres: 5 | image: postgres:latest 6 | container_name: rsquant-pg 7 | environment: 8 | POSTGRES_DB: rsquant 9 | POSTGRES_USER: postgres 10 | POSTGRES_PASSWORD: postgres 11 | ports: 12 | - "5432:5432" 13 | volumes: 14 | - pg_data:/var/lib/postgresql/data 15 | 16 | volumes: 17 | pg_data: 18 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "types": ["vite/client"], 12 | "noEmit": true, 13 | "isolatedModules": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/trade/strategy/mod.rs: -------------------------------------------------------------------------------- 1 | use rsquant_tool::Name; 2 | use ta::DataItem; 3 | 4 | use crate::entity::side; 5 | 6 | mod common_macd_and_rsi; 7 | mod double_ema; 8 | mod rsi_and_double_ema; 9 | 10 | pub use common_macd_and_rsi::CommonMacdAndRsiStrategy; 11 | pub use double_ema::DoubleEmaStrategy; 12 | pub use rsi_and_double_ema::RsiAndDoubleEmaStrategy; 13 | 14 | pub trait Strategy: Name { 15 | fn check(&mut self, data: &[DataItem]) -> side::TradeSide; 16 | } 17 | -------------------------------------------------------------------------------- /web/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | margin: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", 8 | "Droid Sans", "Helvetica Neue", sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | code { 14 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; 15 | } 16 | -------------------------------------------------------------------------------- /scripts/database.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function setup() { 4 | if ! [ -x "${HOME}/.cargo/bin/diesel" ]; then 5 | cargo install diesel_cli --force --no-default-features --features postgres 6 | fi 7 | 8 | "${HOME}/.cargo/bin/diesel" database reset 9 | } 10 | 11 | function main() { 12 | local cmd="$1" 13 | local extra_args="${*:2}" 14 | 15 | case $cmd in 16 | "setup") 17 | setup "${extra_args}" 18 | ;; 19 | esac 20 | } 21 | 22 | main "$@" 23 | -------------------------------------------------------------------------------- /scripts/docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function build_image() { 4 | docker build --network=host -t quant-dev:latest . 5 | } 6 | 7 | function into_container() { 8 | docker run -it --name rsquant --network=host quant-dev:latest 9 | } 10 | 11 | function main() { 12 | local cmd="$1" 13 | local extra_args="${*:2}" 14 | 15 | case $cmd in 16 | "build") 17 | build_image "${extra_args}" 18 | ;; 19 | "into") 20 | into_container "${extra_args}" 21 | ;; 22 | esac 23 | } 24 | 25 | main "$@" 26 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solid App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import solidPlugin from "vite-plugin-solid" 3 | // import devtools from 'solid-devtools/vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | /* 8 | Uncomment the following line to enable solid-devtools. 9 | For more info see https://github.com/thetarnav/solid-devtools/tree/main/packages/extension#readme 10 | */ 11 | // devtools(), 12 | solidPlugin(), 13 | ], 14 | server: { 15 | host: "127.0.0.1", 16 | port: 3000, 17 | }, 18 | build: { 19 | target: "esnext", 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/entity/side.rs: -------------------------------------------------------------------------------- 1 | use sea_orm::{ 2 | DeriveActiveEnum, 3 | EnumIter, 4 | }; 5 | use serde::{ 6 | Deserialize, 7 | Serialize, 8 | }; 9 | 10 | #[derive( 11 | Debug, Copy, Default, Clone, PartialEq, Eq, Serialize, Deserialize, EnumIter, DeriveActiveEnum, 12 | )] 13 | #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "trade_side")] 14 | pub enum TradeSide { 15 | #[sea_orm(string_value = "buy")] 16 | Buy, 17 | #[sea_orm(string_value = "sell")] 18 | Sell, 19 | #[default] 20 | #[sea_orm(string_value = "nop")] 21 | Nop, 22 | } 23 | -------------------------------------------------------------------------------- /deps/binan_spot/src/stream/mod.rs: -------------------------------------------------------------------------------- 1 | //! Market Data 2 | 3 | pub mod close_listen_key; 4 | pub mod new_listen_key; 5 | pub mod renew_listen_key; 6 | 7 | use close_listen_key::CloseListenKey; 8 | use new_listen_key::NewListenKey; 9 | use renew_listen_key::RenewListenKey; 10 | 11 | pub fn new_listen_key() -> NewListenKey { 12 | NewListenKey::new() 13 | } 14 | 15 | pub fn renew_listen_key(listen_key: &str) -> RenewListenKey { 16 | RenewListenKey::new(listen_key) 17 | } 18 | 19 | pub fn close_listen_key(listen_key: &str) -> CloseListenKey { 20 | CloseListenKey::new(listen_key) 21 | } 22 | -------------------------------------------------------------------------------- /deps/binan_spot/src/margin_stream/mod.rs: -------------------------------------------------------------------------------- 1 | //! Market Data 2 | 3 | pub mod close_listen_key; 4 | pub mod new_listen_key; 5 | pub mod renew_listen_key; 6 | 7 | use close_listen_key::CloseListenKey; 8 | use new_listen_key::NewListenKey; 9 | use renew_listen_key::RenewListenKey; 10 | 11 | pub fn new_listen_key() -> NewListenKey { 12 | NewListenKey::new() 13 | } 14 | 15 | pub fn renew_listen_key(listen_key: &str) -> RenewListenKey { 16 | RenewListenKey::new(listen_key) 17 | } 18 | 19 | pub fn close_listen_key(listen_key: &str) -> CloseListenKey { 20 | CloseListenKey::new(listen_key) 21 | } 22 | -------------------------------------------------------------------------------- /scripts/check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function rust_lint() { 4 | cargo test 5 | cargo fmt 6 | cargo clippy --all 7 | 8 | } 9 | 10 | function git_lint() { 11 | pre-commit run --all-files 12 | } 13 | 14 | function main() { 15 | 16 | local cmd="$1" 17 | local extra_args="${*:2}" 18 | 19 | case $cmd in 20 | "rust") 21 | rust_lint "${extra_args}" 22 | ;; 23 | "git") 24 | git_lint "${extra_args}" 25 | ;; 26 | "all") 27 | rust_lint "${extra_args}" 28 | git_lint "${extra_args}" 29 | ;; 30 | esac 31 | } 32 | 33 | main "$@" 34 | -------------------------------------------------------------------------------- /crates/rsquant-bt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsquant-bt" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { workspace = true } 8 | tracing = { workspace = true } 9 | chrono = { workspace = true } 10 | 11 | barter = { path = "../../deps/barter-rs" } 12 | barter-data = "0.7.0" 13 | barter-integration = "0.7.2" 14 | 15 | tokio-stream = { version = "0.1.9", features = ["sync"] } 16 | futures = "0.3.21" 17 | 18 | uuid = { version = "1.8.0", features = ["v4", "serde"] } 19 | parking_lot = "0.12.3" 20 | serde = { version = "1.0.143", features = ["derive"] } 21 | serde_json = "1.0.83" 22 | ta = "0.5.0" 23 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-template-solid", 3 | "version": "0.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "vite", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "autoprefixer": "^10.4.19", 14 | "postcss": "^8.4.38", 15 | "solid-devtools": "^0.29.2", 16 | "tailwindcss": "^3.4.3", 17 | "typescript": "^5.3.3", 18 | "vite": "^5.0.11", 19 | "vite-plugin-solid": "^2.8.2" 20 | }, 21 | "dependencies": { 22 | "@solidjs/router": "^0.13.3", 23 | "solid-js": "^1.8.11" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /deps/binan_spot/src/isolated_margin_stream/mod.rs: -------------------------------------------------------------------------------- 1 | //! Market Data 2 | 3 | pub mod close_listen_key; 4 | pub mod new_listen_key; 5 | pub mod renew_listen_key; 6 | 7 | use close_listen_key::CloseListenKey; 8 | use new_listen_key::NewListenKey; 9 | use renew_listen_key::RenewListenKey; 10 | 11 | pub fn new_listen_key(symbol: &str) -> NewListenKey { 12 | NewListenKey::new(symbol) 13 | } 14 | 15 | pub fn renew_listen_key(symbol: &str, listen_key: &str) -> RenewListenKey { 16 | RenewListenKey::new(symbol, listen_key) 17 | } 18 | 19 | pub fn close_listen_key(symbol: &str, listen_key: &str) -> CloseListenKey { 20 | CloseListenKey::new(symbol, listen_key) 21 | } 22 | -------------------------------------------------------------------------------- /deps/binan_spot/src/websocket.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// Websocket stream. 4 | /// 5 | /// The `Stream` trait is a simplified interface for Binance approved 6 | /// websocket streams. 7 | pub struct Stream { 8 | stream_name: String, 9 | } 10 | 11 | impl Stream { 12 | pub fn new(stream_name: &str) -> Self { 13 | Self { 14 | stream_name: stream_name.to_owned(), 15 | } 16 | } 17 | 18 | pub fn as_str(&self) -> &str { 19 | &self.stream_name 20 | } 21 | } 22 | 23 | impl fmt::Display for Stream { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | write!(f, "{}", self.stream_name) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | pre-commit: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - uses: actions/setup-python@v3 13 | 14 | - uses: pre-commit/action@v3.0.0 15 | 16 | check-rs: 17 | needs: pre-commit 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | submodules: true 24 | 25 | - name: Download build image 26 | run: docker pull clux/muslrust:nightly 27 | 28 | - name: Test 29 | run: docker run -v $PWD:/volume --rm -t clux/muslrust:nightly bash -c 'cargo test' 30 | -------------------------------------------------------------------------------- /scripts/web.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT=$(pwd) 4 | PY_DIR="${ROOT}/visualize" 5 | PY_SRC_DIR="${PY_DIR}/src" 6 | 7 | function setup() { 8 | if ! [ -x "${HOME}/.local/bin/poetry" ]; then 9 | curl -sSL https://install.python-poetry.org | python3 - 10 | fi 11 | 12 | "${HOME}/.local/bin/poetry" install 13 | } 14 | 15 | function run() { 16 | "${HOME}/.local/bin/poetry" run python3 "${PY_SRC_DIR}/app.py" 17 | } 18 | 19 | function main() { 20 | local cmd="$1" 21 | local extra_args="${*:2}" 22 | 23 | case $cmd in 24 | "setup") 25 | setup "${extra_args}" 26 | ;; 27 | "run") 28 | run "${extra_args}" 29 | ;; 30 | esac 31 | } 32 | 33 | main "$@" 34 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/model/trade/order.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use crate::model::DecodeFromStr; 4 | 5 | #[derive(Debug, Deserialize)] 6 | pub struct OrderResponse { 7 | pub symbol: String, 8 | pub order_id: u64, 9 | pub order_list_id: i64, 10 | pub client_order_id: String, 11 | pub transact_time: u64, 12 | pub price: String, 13 | pub orig_qty: String, 14 | pub executed_qty: String, 15 | pub cummulative_quote_qty: String, 16 | pub status: String, 17 | pub time_in_force: String, 18 | pub r#type: String, 19 | pub side: String, 20 | pub working_time: u64, 21 | pub self_trade_prevention_mode: String, 22 | } 23 | 24 | impl DecodeFromStr<'_, OrderResponse> for OrderResponse {} 25 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/util/time/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono; 2 | 3 | mod converter; 4 | mod current; 5 | mod timezone; 6 | 7 | pub use converter::TimeConverter; 8 | pub use current::{ 9 | CurrentTime, 10 | DurationInterval, 11 | GetDuration, 12 | }; 13 | pub use timezone::TimeZoneConverter; 14 | 15 | pub struct LocalTimeTool; 16 | pub struct UtcTimeTool; 17 | 18 | pub fn u64_to_datetime<'de, D>(deserializer: D) -> Result 19 | where 20 | D: serde::Deserializer<'de>, 21 | { 22 | let timestamp: i64 = serde::Deserialize::deserialize(deserializer)?; 23 | let naive = LocalTimeTool::to_date_time(timestamp) 24 | .unwrap_or_default() 25 | .naive_local(); 26 | 27 | Ok(naive) 28 | } 29 | -------------------------------------------------------------------------------- /deps/binan_spot/src/trade/order.rs: -------------------------------------------------------------------------------- 1 | use strum::Display; 2 | 3 | #[derive(Copy, Clone, Display, PartialEq, Eq)] 4 | #[strum(serialize_all = "UPPERCASE")] 5 | pub enum Side { 6 | Buy, 7 | Sell, 8 | } 9 | 10 | #[derive(Copy, Clone, Display)] 11 | #[strum(serialize_all = "UPPERCASE")] 12 | pub enum TimeInForce { 13 | Gtc, 14 | Ioc, 15 | Fok, 16 | } 17 | 18 | #[derive(Copy, Clone, Display)] 19 | #[strum(serialize_all = "UPPERCASE")] 20 | pub enum NewOrderResponseType { 21 | Ack, 22 | Result, 23 | Full, 24 | } 25 | 26 | #[derive(Copy, Clone, Display)] 27 | pub enum CancelReplaceMode { 28 | #[strum(serialize = "STOP_ON_FAILURE")] 29 | StopOnFailure, 30 | #[strum(serialize = "ALLOW_FAILURE")] 31 | AllowFailure, 32 | } 33 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/trade/indicator/ema.rs: -------------------------------------------------------------------------------- 1 | use polars::series::Series; 2 | use ta::{ 3 | indicators::ExponentialMovingAverage as Ema, 4 | Close, 5 | DataItem, 6 | Next, 7 | }; 8 | 9 | use super::Indicator; 10 | 11 | #[derive(Default, Debug, Clone)] 12 | pub struct EmaOutputBuilder { 13 | ema: Ema, 14 | } 15 | 16 | impl EmaOutputBuilder { 17 | pub fn new(period: usize) -> Self { 18 | Self { 19 | ema: Ema::new(period).unwrap(), 20 | } 21 | } 22 | } 23 | 24 | impl Indicator for EmaOutputBuilder { 25 | type Output = Series; 26 | 27 | fn compute(&mut self, data: &[DataItem]) -> Self::Output { 28 | let f = |v: &DataItem| self.ema.next(v.close()); 29 | data.iter().map(f).collect() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/trade/indicator/rsi.rs: -------------------------------------------------------------------------------- 1 | use polars::series::Series; 2 | use ta::{ 3 | indicators::RelativeStrengthIndex as Rsi, 4 | Close, 5 | DataItem, 6 | Next, 7 | }; 8 | 9 | use super::Indicator; 10 | 11 | #[derive(Default, Debug, Clone)] 12 | pub struct RsiOutputBuilder { 13 | rsi: Rsi, 14 | } 15 | 16 | impl RsiOutputBuilder { 17 | pub fn new(period: usize) -> Self { 18 | Self { 19 | rsi: Rsi::new(period).unwrap(), 20 | } 21 | } 22 | } 23 | 24 | impl Indicator for RsiOutputBuilder { 25 | type Output = Series; 26 | 27 | fn compute(&mut self, data: &[DataItem]) -> Self::Output { 28 | let f = |v: &DataItem| self.rsi.next(v.close()); 29 | data.iter().map(f).collect() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/rsquant-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{ 2 | self, 3 | TokenStream, 4 | }; 5 | use quote::quote; 6 | use syn::{ 7 | parse_macro_input, 8 | DeriveInput, 9 | }; 10 | 11 | #[proc_macro_derive(Name)] 12 | pub fn derive(input: TokenStream) -> TokenStream { 13 | let DeriveInput { 14 | ident, generics, .. 15 | } = parse_macro_input!(input); 16 | let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); 17 | 18 | let ident_str = ident.to_string(); 19 | let output = quote! { 20 | impl #impl_generics Name for #ident #type_generics #where_clause { 21 | fn get_name(&self) -> String { 22 | #ident_str.into() 23 | } 24 | } 25 | }; 26 | output.into() 27 | } 28 | -------------------------------------------------------------------------------- /deps/binan_spot/src/ureq/error.rs: -------------------------------------------------------------------------------- 1 | use http::{ 2 | uri::InvalidUri, 3 | Error as HttpError, 4 | }; 5 | use ureq::Error as UreqError; 6 | 7 | use crate::http::error::{ 8 | ClientError, 9 | HttpError as BinanceHttpError, 10 | }; 11 | 12 | /// Communication error with the server. 13 | #[derive(Debug)] 14 | pub enum Error { 15 | /// 4XX error from the server. 16 | Client(ClientError), 17 | /// 5XX error from the server. 18 | Server(BinanceHttpError), 19 | /// The format of the API secret is invalid. 20 | InvalidApiSecret, 21 | Parse(HttpError), 22 | Send(UreqError), 23 | } 24 | 25 | impl From for Box { 26 | fn from(err: InvalidUri) -> Box { 27 | Box::new(Error::Parse(err.into())) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/rsquant-core/migration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "migration" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | name = "migration" 9 | path = "src/lib.rs" 10 | 11 | [dependencies] 12 | 13 | rsquant-core = { path = "../" } 14 | async-std = { version = "1", features = ["attributes", "tokio1"] } 15 | 16 | [dependencies.sea-orm-migration] 17 | version = "0.12.0" 18 | features = [ 19 | # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. 20 | # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. 21 | # e.g. 22 | "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature 23 | "sqlx-postgres", # `DATABASE_DRIVER` feature 24 | ] 25 | -------------------------------------------------------------------------------- /crates/bin/bt.rs: -------------------------------------------------------------------------------- 1 | use tracing_subscriber::{ 2 | layer::SubscriberExt, 3 | util::SubscriberInitExt, 4 | EnvFilter, 5 | }; 6 | 7 | #[tokio::main] 8 | async fn main() { 9 | let filter_layer = EnvFilter::try_from_env("QUANT_LOG_LEVEL") 10 | .or_else(|_| EnvFilter::try_new("info")) 11 | .unwrap(); 12 | 13 | let tui_layer = tracing_subscriber::fmt::layer() 14 | .with_target(true) 15 | .with_ansi(true) 16 | .with_file(false) 17 | .with_line_number(true) 18 | .with_thread_names(false) 19 | .with_thread_ids(false) 20 | .with_writer(std::io::stdout); 21 | 22 | tracing_subscriber::registry() 23 | .with(tui_layer) 24 | .with(filter_layer) 25 | .init(); 26 | 27 | rsquant_bt::run_bt().await.unwrap(); 28 | } 29 | -------------------------------------------------------------------------------- /deps/binan_spot/src/http/method.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq, Eq, Clone, Debug)] 2 | pub enum Method { 3 | Get, 4 | Post, 5 | Put, 6 | Delete, 7 | } 8 | 9 | impl AsRef for Method { 10 | fn as_ref(&self) -> &str { 11 | match self { 12 | Method::Post => "POST", 13 | Method::Delete => "DELETE", 14 | Method::Get => "GET", 15 | Method::Put => "PUT", 16 | } 17 | } 18 | } 19 | 20 | impl From for reqwest::Method { 21 | fn from(method: Method) -> Self { 22 | match method { 23 | Method::Get => reqwest::Method::GET, 24 | Method::Post => reqwest::Method::POST, 25 | Method::Delete => reqwest::Method::DELETE, 26 | Method::Put => reqwest::Method::PUT, 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/model/market/ticker_price.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::str::FromStr; 3 | 4 | use rust_decimal::Decimal; 5 | use serde::{ 6 | Deserialize, 7 | Serialize, 8 | }; 9 | 10 | use crate::model::DecodeFromStr; 11 | 12 | #[derive(Debug, Serialize, Deserialize)] 13 | pub struct TickerPrice { 14 | pub symbol: String, 15 | pub price: String, 16 | } 17 | 18 | impl TickerPrice { 19 | pub fn price(&self) -> Decimal { 20 | Decimal::from_str(self.price.as_str()).unwrap() 21 | } 22 | } 23 | 24 | impl DecodeFromStr<'_, TickerPrice> for TickerPrice {} 25 | impl DecodeFromStr<'_, Vec> for Vec {} 26 | 27 | impl fmt::Display for TickerPrice { 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 | write!(f, "{{ symbol: {}, price: {} }}", self.symbol, self.price) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Rust 2 | *.pdb 3 | **/*.rs.bk 4 | 5 | debug/ 6 | target/ 7 | 8 | ### IDE config 9 | .vscode/ 10 | .idea/ 11 | 12 | *.sublime-* 13 | 14 | ### Project 15 | log 16 | .venv 17 | __pycache__ 18 | poetry.lock 19 | rsquant.json 20 | .env 21 | 22 | deps/binan_spot_examples 23 | 24 | ### Build and Run 25 | core.* 26 | nohup.out 27 | 28 | .gdb* 29 | 30 | ### Documentations 31 | *.excalidraw 32 | 33 | ### Web 34 | 35 | # dependencies 36 | node_modules 37 | .pnp 38 | .pnp.js 39 | pnpm-lock.yaml 40 | 41 | # testing 42 | coverage 43 | 44 | # next.js 45 | .next/ 46 | out/ 47 | 48 | # production 49 | build 50 | _build 51 | dist 52 | 53 | # misc 54 | .DS_Store 55 | *.pem 56 | 57 | # debug 58 | npm-debug.log* 59 | yarn-debug.log* 60 | yarn-error.log* 61 | 62 | # local env files 63 | .env*.local 64 | 65 | # vercel 66 | .vercel 67 | 68 | # typescript 69 | *.tsbuildinfo 70 | next-env.d.ts 71 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/trade/indicator/data_item.rs: -------------------------------------------------------------------------------- 1 | use ta::DataItem; 2 | 3 | use crate::model::kline::Kline; 4 | 5 | pub trait ToDataItem { 6 | fn to_data_item(&self) -> Result>; 7 | } 8 | 9 | impl ToDataItem for Kline { 10 | fn to_data_item(&self) -> Result> { 11 | let open: f64 = fast_float::parse(&self.open_price)?; 12 | let high: f64 = fast_float::parse(&self.high_price)?; 13 | let low: f64 = fast_float::parse(&self.low_price)?; 14 | let close: f64 = fast_float::parse(&self.close_price)?; 15 | let volume: f64 = fast_float::parse(&self.volume)?; 16 | 17 | Ok(DataItem::builder() 18 | .open(open) 19 | .high(high) 20 | .low(low) 21 | .close(close) 22 | .volume(volume) 23 | .build()?) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/src/components/SymbolCard.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "solid-js" 2 | 3 | type SymbolCardProps = { 4 | symbol: string 5 | price: number | undefined 6 | class: string 7 | onClick: (event: MouseEvent) => any | undefined 8 | } 9 | 10 | const SymbolCard: Component = (props) => { 11 | return ( 12 |
13 |
14 |
{props.symbol}
15 |

{props.symbol}

16 |

17 | {props.price} USDT 18 |

19 |
20 |
21 | ) 22 | } 23 | 24 | export default SymbolCard 25 | -------------------------------------------------------------------------------- /web/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from "solid-js/web" 3 | import { Router, Route, A } from "@solidjs/router" 4 | 5 | import Home from "./pages/Home" 6 | import SubscribeTicker from "./pages/SubscribeTicker" 7 | 8 | import "./index.css" 9 | 10 | const root = document.getElementById("root") 11 | 12 | if (import.meta.env.DEV && !(root instanceof HTMLElement)) { 13 | throw new Error( 14 | "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?" 15 | ) 16 | } 17 | 18 | const App = (props: any) => ( 19 | <> 20 | 24 |

Rsquant

25 | {props.children} 26 | 27 | ) 28 | 29 | render( 30 | () => ( 31 | 32 | 33 | 34 | 35 | ), 36 | root! 37 | ) 38 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market_stream/trade.rs: -------------------------------------------------------------------------------- 1 | use crate::websocket::Stream; 2 | 3 | /// Aggregate Trade Stream 4 | /// 5 | /// The Trade Streams push raw trade information; each trade has a unique buyer and seller. 6 | /// 7 | /// Update Speed: Real-time. 8 | /// 9 | /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#trade-streams) 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | /// use binance_spot_connector_rust::market_stream::trade::TradeStream; 15 | /// 16 | /// let stream = TradeStream::new("BTCUSDT"); 17 | /// ``` 18 | pub struct TradeStream { 19 | symbol: String, 20 | } 21 | 22 | impl TradeStream { 23 | pub fn new(symbol: &str) -> Self { 24 | Self { 25 | symbol: symbol.to_lowercase(), 26 | } 27 | } 28 | } 29 | 30 | impl From for Stream { 31 | /// Returns stream name as `@trade` 32 | fn from(stream: TradeStream) -> Stream { 33 | Stream::new(&format!("{}@trade", stream.symbol)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/util/env.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | pub struct EnvManager; 4 | 5 | impl EnvManager { 6 | pub fn get_env_var(key: &str) -> Option { 7 | env::var(key).ok().map_or_else( 8 | || { 9 | tracing::warn!("Environment variable `{}` is unset!", key); 10 | None 11 | }, 12 | Some, 13 | ) 14 | } 15 | 16 | pub fn get_env_var_or(key: &str, default: impl Into) -> String { 17 | match env::var(key) { 18 | Ok(v) => v, 19 | Err(_) => default.into(), 20 | } 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use std::env; 27 | 28 | use super::EnvManager; 29 | 30 | #[allow(deprecated)] 31 | #[test] 32 | fn test_get_env_var() { 33 | let actual = EnvManager::get_env_var("HOME").unwrap_or("".into()); 34 | let expect = env::home_dir().unwrap().display().to_string(); 35 | assert_eq!(actual, expect); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/model/account/coin_info.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use rust_decimal::Decimal; 4 | use serde::Deserialize; 5 | 6 | #[derive(Debug, Clone, Deserialize)] 7 | pub struct CoinInfo { 8 | /// 资产名称 9 | pub asset: String, 10 | /// 可用余额 11 | pub free: Decimal, 12 | /// 不可用余额 13 | pub locked: Decimal, 14 | } 15 | 16 | impl CoinInfo { 17 | pub fn asset(&self) -> String { 18 | self.asset.to_owned() 19 | } 20 | 21 | pub fn free(&self) -> Decimal { 22 | self.free.to_owned() 23 | } 24 | 25 | pub fn locked(&self) -> Decimal { 26 | self.locked.to_owned() 27 | } 28 | 29 | pub fn is_zero(&self) -> bool { 30 | self.free.is_zero() && self.locked.is_zero() 31 | } 32 | } 33 | 34 | impl fmt::Display for CoinInfo { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | write!( 37 | f, 38 | "{: <10} {:0<20}\t{:0<20}", 39 | self.asset, self.free, self.locked 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/entity/order.rs: -------------------------------------------------------------------------------- 1 | use rust_decimal::Decimal; 2 | use sea_orm::{ 3 | ActiveModelBehavior, 4 | DeriveEntityModel, 5 | DerivePrimaryKey, 6 | DeriveRelation, 7 | EntityTrait, 8 | EnumIter, 9 | PrimaryKeyTrait, 10 | }; 11 | use serde::{ 12 | Deserialize, 13 | Serialize, 14 | }; 15 | 16 | use super::side::TradeSide; 17 | 18 | #[derive(Default, Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)] 19 | #[sea_orm(table_name = "orders")] 20 | pub struct Model { 21 | #[sea_orm(primary_key, auto_increment = true)] 22 | #[serde(skip_deserializing)] 23 | pub id: i32, 24 | 25 | pub order_id: u64, 26 | 27 | pub symbol: String, 28 | 29 | pub price: Decimal, 30 | 31 | pub quantity: Decimal, 32 | 33 | pub side: TradeSide, 34 | 35 | pub time_in_force: String, 36 | 37 | pub r#type: String, 38 | } 39 | 40 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 41 | pub enum Relation {} 42 | 43 | impl ActiveModelBehavior for ActiveModel {} 44 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market_stream/agg_trade.rs: -------------------------------------------------------------------------------- 1 | use crate::websocket::Stream; 2 | 3 | /// Aggregate Trade Stream 4 | /// 5 | /// The Aggregate Trade Streams push trade information that is aggregated for a single taker order. 6 | /// 7 | /// Update Speed: Real-time. 8 | /// 9 | /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#aggregate-trade-streams) 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | /// use binance_spot_connector_rust::market_stream::agg_trade::AggTradeStream; 15 | /// 16 | /// let stream = AggTradeStream::new("BTCUSDT"); 17 | /// ``` 18 | pub struct AggTradeStream { 19 | symbol: String, 20 | } 21 | 22 | impl AggTradeStream { 23 | pub fn new(symbol: &str) -> Self { 24 | Self { 25 | symbol: symbol.to_lowercase(), 26 | } 27 | } 28 | } 29 | 30 | impl From for Stream { 31 | /// Returns stream name as `@aggTrade` 32 | fn from(stream: AggTradeStream) -> Stream { 33 | Stream::new(&format!("{}@aggTrade", stream.symbol)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub use account::{ 2 | account_info, 3 | coin_info, 4 | }; 5 | pub use market::{ 6 | kline, 7 | ticker_price, 8 | }; 9 | use serde::Deserialize; 10 | pub use trade::order; 11 | 12 | pub mod account; 13 | pub mod market; 14 | pub mod trade; 15 | 16 | pub trait DecodeFromStr<'a, T> 17 | where 18 | T: Deserialize<'a>, 19 | { 20 | fn decode_from_str(data: &'a str) -> Result { 21 | match serde_json::from_str(data) { 22 | Ok(t) => { 23 | tracing::trace!("Deserialize response string to data structure."); 24 | Ok(t) 25 | } 26 | Err(e) => { 27 | tracing::error!( 28 | "Failed to deserialize response string to data structure: {} for data `{}`.", 29 | e, 30 | data 31 | ); 32 | Err(e) 33 | } 34 | } 35 | } 36 | } 37 | 38 | pub trait IntoTarget { 39 | fn into_target(self) -> T; 40 | } 41 | -------------------------------------------------------------------------------- /crates/rsquant-core/migration/README.md: -------------------------------------------------------------------------------- 1 | # Running Migrator CLI 2 | 3 | - Generate a new migration file 4 | 5 | ```sh 6 | cargo run -- generate MIGRATION_NAME 7 | ``` 8 | 9 | - Apply all pending migrations 10 | 11 | ```sh 12 | cargo run 13 | ``` 14 | 15 | ```sh 16 | cargo run -- up 17 | ``` 18 | 19 | - Apply first 10 pending migrations 20 | 21 | ```sh 22 | cargo run -- up -n 10 23 | ``` 24 | 25 | - Rollback last applied migrations 26 | 27 | ```sh 28 | cargo run -- down 29 | ``` 30 | 31 | - Rollback last 10 applied migrations 32 | 33 | ```sh 34 | cargo run -- down -n 10 35 | ``` 36 | 37 | - Drop all tables from the database, then reapply all migrations 38 | 39 | ```sh 40 | cargo run -- fresh 41 | ``` 42 | 43 | - Rollback all applied migrations, then reapply all migrations 44 | 45 | ```sh 46 | cargo run -- refresh 47 | ``` 48 | 49 | - Rollback all applied migrations 50 | 51 | ```sh 52 | cargo run -- reset 53 | ``` 54 | 55 | - Check the status of all migrations 56 | 57 | ```sh 58 | cargo run -- status 59 | ``` 60 | -------------------------------------------------------------------------------- /scripts/rust.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT=$(pwd) 4 | BIN_NAME="rsquant" 5 | 6 | function build() { 7 | if [ "$1" = "-d" ] || [ "$1" = "--debug" ]; then 8 | cargo build 9 | else 10 | cargo build --release 11 | fi 12 | } 13 | 14 | function run() { 15 | if [ "$1" = "-d" ] || [ "$1" = "--debug" ]; then 16 | build "--debug" 17 | "${ROOT}/target/debug/${BIN_NAME}" >/dev/null 2>&1 & 18 | else 19 | build "--release" 20 | "${ROOT}/target/release/${BIN_NAME}" >/dev/null 2>&1 & 21 | fi 22 | } 23 | 24 | function test() { 25 | build 26 | cargo nextest run "$@" 27 | } 28 | 29 | function setup() { 30 | mkdir -p log 31 | build 32 | } 33 | 34 | function main() { 35 | local cmd="$1" 36 | local extra_args="${*:2}" 37 | 38 | case $cmd in 39 | "setup") 40 | setup 41 | ;; 42 | "run") 43 | run "${extra_args}" 44 | ;; 45 | "build") 46 | build "${extra_args}" 47 | ;; 48 | "test") 49 | test "${extra_args}" 50 | ;; 51 | esac 52 | } 53 | 54 | main "$@" 55 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/actor/strategy.rs: -------------------------------------------------------------------------------- 1 | use actix::{ 2 | Actor, 3 | Handler, 4 | }; 5 | 6 | use crate::{ 7 | entity::side, 8 | message::KlineStrategyRequest, 9 | trade::Strategy, 10 | Result, 11 | }; 12 | 13 | pub struct StrategyActor { 14 | inner: Box, 15 | } 16 | 17 | impl StrategyActor { 18 | pub fn new(inner: Box) -> Self { 19 | Self { inner } 20 | } 21 | } 22 | 23 | impl Actor for StrategyActor { 24 | type Context = actix::Context; 25 | 26 | fn started(&mut self, _ctx: &mut Self::Context) { 27 | tracing::info!( 28 | "[strategy:{}]: strategy actor started", 29 | self.inner.get_name() 30 | ); 31 | } 32 | } 33 | 34 | impl Handler for StrategyActor { 35 | type Result = Result; 36 | 37 | fn handle(&mut self, msg: KlineStrategyRequest, _ctx: &mut Self::Context) -> Self::Result { 38 | let res = self.inner.check(&msg.data); 39 | tracing::info!("[strategy:{}]: {:?}", self.inner.get_name(), res); 40 | Ok(res) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Changfeng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /web/src/pages/GreetPage.tsx: -------------------------------------------------------------------------------- 1 | import { createSignal, type Component } from "solid-js" 2 | 3 | const GreetPage: Component = () => { 4 | const [greetMsg, setGreetMsg] = createSignal("Hello World") 5 | const [name, setName] = createSignal("") 6 | 7 | const greet = () => { 8 | setGreetMsg(`Hello ${name().toUpperCase()}`) 9 | } 10 | 11 | return ( 12 |
13 |

{greetMsg()}

14 | 15 |
{ 18 | e.preventDefault() 19 | greet() 20 | }} 21 | > 22 | setName(e.currentTarget.value)} 27 | /> 28 | 29 | 32 |
33 |
34 | ) 35 | } 36 | 37 | export default GreetPage 38 | -------------------------------------------------------------------------------- /deps/binan_spot/src/hyper/mod.rs: -------------------------------------------------------------------------------- 1 | //! Binance client using Hyper. 2 | //! 3 | //! # Example 4 | //! 5 | //! ```no_run 6 | //! use binance_spot_connector_rust::{ 7 | //! http::{request::RequestBuilder, Credentials, Method}, 8 | //! hyper::{BinanceHttpClient, Error}, 9 | //! }; 10 | //! use env_logger::Builder; 11 | //! 12 | //! #[tokio::main] 13 | //! async fn main() -> Result<(), Error> { 14 | //! Builder::from_default_env() 15 | //! .filter(None, log::LevelFilter::Info) 16 | //! .init(); 17 | //! let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); 18 | //! let client = BinanceHttpClient::default().credentials(credentials); 19 | //! let request = RequestBuilder::new(Method::Post, "/api/v3/order").params(vec![ 20 | //! ("symbol", "BNBUSDT"), 21 | //! ("side", "SELL"), 22 | //! ("type", "LIMIT"), 23 | //! ("quantity", "0.1"), 24 | //! ("price", "320.2"), 25 | //! ]); 26 | //! let data = client.send(request).await?.into_body_str().await?; 27 | //! log::info!("{}", data); 28 | //! Ok(()) 29 | //! } 30 | //! ``` 31 | 32 | mod client; 33 | mod error; 34 | mod response; 35 | 36 | pub use client::*; 37 | pub use error::*; 38 | pub use response::*; 39 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market_stream/kline.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | market::klines::KlineInterval, 3 | websocket::Stream, 4 | }; 5 | 6 | /// Kline/Candlestick Stream 7 | /// 8 | /// The Kline/Candlestick Stream push updates to the current klines/candlestick every second. 9 | /// 10 | /// Update Speed: 2000ms 11 | /// 12 | /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-streams) 13 | /// 14 | /// # Example 15 | /// 16 | /// ``` 17 | /// use binance_spot_connector_rust::{ market::klines::KlineInterval, market_stream::kline::KlineStream }; 18 | /// 19 | /// let stream = KlineStream::new("BTCUSDT", KlineInterval::Minutes1); 20 | /// ``` 21 | pub struct KlineStream { 22 | symbol: String, 23 | interval: KlineInterval, 24 | } 25 | 26 | impl KlineStream { 27 | pub fn new(symbol: &str, interval: KlineInterval) -> Self { 28 | Self { 29 | symbol: symbol.to_lowercase(), 30 | interval, 31 | } 32 | } 33 | } 34 | 35 | impl From for Stream { 36 | /// Returns stream name as `@kline_interval` 37 | fn from(stream: KlineStream) -> Stream { 38 | Stream::new(&format!("{}@kline_{}", stream.symbol, stream.interval)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. 4 | 5 | This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. 6 | 7 | ```bash 8 | npm install # or pnpm install or yarn install 9 | ``` 10 | 11 | ### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) 12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm run dev` or `npm start` 18 | 19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 21 | 22 | The page will reload if you make edits.
23 | 24 | ### `npm run build` 25 | 26 | Builds the app for production to the `dist` folder.
27 | It correctly bundles Solid in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes.
30 | Your app is ready to be deployed! 31 | 32 | ## Deployment 33 | 34 | You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) 35 | -------------------------------------------------------------------------------- /rsquant.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT=$(pwd) 4 | 5 | function setup_envs() { 6 | export "$(grep -v '^#' .env | xargs -d '\n')" 7 | } 8 | 9 | function main() { 10 | setup_envs 11 | 12 | local cmd="$1" 13 | local extra_args="${*:2}" 14 | local db_sh="${ROOT}/scripts/database.sh" 15 | local web_sh="${ROOT}/scripts/web.sh" 16 | local rust_sh="${ROOT}/scripts/rust.sh" 17 | local check_sh="${ROOT}/scripts/check.sh" 18 | local docker_sh="${ROOT}/scripts/docker.sh" 19 | 20 | case $cmd in 21 | "setup") 22 | bash "${db_sh}" setup 23 | bash "${web_sh}" setup 24 | bash "${rust_sh}" setup 25 | ;; 26 | "web") 27 | bash "${web_sh}" run 28 | ;; 29 | "run") 30 | bash "${rust_sh}" run "${extra_args}" 31 | ;; 32 | "build") 33 | bash "${rust_sh}" build "${extra_args}" 34 | ;; 35 | "test") 36 | bash "${rust_sh}" test "${extra_args}" 37 | ;; 38 | "setup-docker") 39 | bash "${docker_sh}" build 40 | bash "${docker_sh}" into 41 | ;; 42 | "lint-rs") 43 | bash "${check_sh}" rust 44 | ;; 45 | "lint-git") 46 | bash "${check_sh}" git 47 | ;; 48 | "lint-all") 49 | bash "${check_sh}" all 50 | ;; 51 | esac 52 | } 53 | 54 | main "$@" 55 | -------------------------------------------------------------------------------- /deps/binan_spot/src/user_data_stream/user_data.rs: -------------------------------------------------------------------------------- 1 | use crate::websocket::Stream; 2 | 3 | /// User Data Stream. 4 | /// 5 | /// A User Data Stream listenKey is valid for 60 minutes after creation. 6 | /// 7 | /// Possible Updates: 8 | /// 9 | /// * `outboundAccountPosition` is sent any time an account balance has 10 | /// changed and contains the assets that were possibly changed by 11 | /// the event that generated the balance change. 12 | /// 13 | /// * `balanceUpdate` occurs during the following: Deposits or 14 | /// withdrawals from the account; Transfer of funds between 15 | /// accounts (e.g. Spot to Margin). 16 | /// 17 | /// * `executionReport` occurs when an order is updated. If the order is 18 | /// an OCO, an event will be displayed named `ListStatus` in addition 19 | /// to the `executionReport` event. 20 | /// 21 | /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#user-data-streams) 22 | pub struct UserDataStream { 23 | listen_key: String, 24 | } 25 | 26 | impl UserDataStream { 27 | pub fn new(listen_key: &str) -> Self { 28 | Self { 29 | listen_key: listen_key.to_owned(), 30 | } 31 | } 32 | } 33 | 34 | impl From for Stream { 35 | /// Returns stream name as `` 36 | fn from(stream: UserDataStream) -> Stream { 37 | Stream::new(&stream.listen_key) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/rsquant-core/src/trade/indicator/macd.rs: -------------------------------------------------------------------------------- 1 | use polars::{ 2 | df, 3 | frame::DataFrame, 4 | }; 5 | use ta::{ 6 | indicators::MovingAverageConvergenceDivergence as Macd, 7 | Close, 8 | DataItem, 9 | Next, 10 | }; 11 | 12 | use super::Indicator; 13 | 14 | #[derive(Default, Debug, Clone)] 15 | pub struct MacdOutputBuilder { 16 | macd: Macd, 17 | } 18 | 19 | impl MacdOutputBuilder { 20 | pub fn new(fast_period: usize, slow_period: usize, signal_period: usize) -> Self { 21 | Self { 22 | macd: Macd::new(fast_period, slow_period, signal_period).unwrap(), 23 | } 24 | } 25 | } 26 | 27 | impl Indicator for MacdOutputBuilder { 28 | type Output = DataFrame; 29 | 30 | fn compute(&mut self, data: &[DataItem]) -> Self::Output { 31 | let f = |v: &DataItem| self.macd.next(v.close()); 32 | let macd = data.iter().map(f).collect::>(); 33 | let macd_line = macd.iter().map(|m| m.macd).collect::>(); 34 | let signal_line = macd.iter().map(|m| m.signal).collect::>(); 35 | let histogram = macd.iter().map(|m| m.histogram).collect::>(); 36 | 37 | df! { 38 | "macd" => &macd_line, 39 | "signal" => &signal_line, 40 | "histogram" => &histogram, 41 | } 42 | .expect("create DataFrame failed") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market_stream/ticker.rs: -------------------------------------------------------------------------------- 1 | use crate::websocket::Stream; 2 | 3 | /// Ticker Stream 4 | /// 5 | /// 24hr rolling window ticker statistics for a single symbol. These are NOT the statistics of the UTC day, but a 24hr rolling window for the previous 24hrs. 6 | /// 7 | /// Update Speed: 1000ms. 8 | /// 9 | /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-ticker-streams) 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | /// use binance_spot_connector_rust::market_stream::ticker::TickerStream; 15 | /// 16 | /// let individual_symbol_stream = TickerStream::from_symbol("BTCUSDT"); 17 | /// let all_symbols_stream = TickerStream::all_symbols(); 18 | /// ``` 19 | pub struct TickerStream { 20 | symbol: Option, 21 | } 22 | 23 | impl TickerStream { 24 | pub fn all_symbols() -> Self { 25 | Self { symbol: None } 26 | } 27 | 28 | pub fn from_symbol(symbol: &str) -> Self { 29 | Self { 30 | symbol: Some(symbol.to_lowercase()), 31 | } 32 | } 33 | } 34 | 35 | impl From for Stream { 36 | /// Returns stream name as `@ticker` or `!ticker@arr` 37 | fn from(stream: TickerStream) -> Stream { 38 | if let Some(symbol) = stream.symbol { 39 | Stream::new(&format!("{}@ticker", symbol)) 40 | } else { 41 | Stream::new("!ticker@arr") 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market_stream/book_ticker.rs: -------------------------------------------------------------------------------- 1 | use crate::websocket::Stream; 2 | 3 | /// Book Ticker Stream 4 | /// 5 | /// Pushes any update to the best bid or ask's price or quantity in real-time for a specified symbol. 6 | /// 7 | /// Update Speed: Real-time. 8 | /// 9 | /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-book-ticker-streams) 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | /// use binance_spot_connector_rust::market_stream::book_ticker::BookTickerStream; 15 | /// 16 | /// let individual_symbol_stream = BookTickerStream::from_symbol("BTCUSDT"); 17 | /// let all_symbols_stream = BookTickerStream::all_symbols(); 18 | /// ``` 19 | pub struct BookTickerStream { 20 | symbol: Option, 21 | } 22 | 23 | impl BookTickerStream { 24 | pub fn all_symbols() -> Self { 25 | Self { symbol: None } 26 | } 27 | 28 | pub fn from_symbol(symbol: &str) -> Self { 29 | Self { 30 | symbol: Some(symbol.to_lowercase()), 31 | } 32 | } 33 | } 34 | 35 | impl From for Stream { 36 | /// Returns stream name as `@bookTicker` or `!bookTicker@arr` 37 | fn from(stream: BookTickerStream) -> Stream { 38 | if let Some(symbol) = stream.symbol { 39 | Stream::new(&format!("{}@bookTicker", symbol)) 40 | } else { 41 | Stream::new("!bookTicker@arr") 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market_stream/mini_ticker.rs: -------------------------------------------------------------------------------- 1 | use crate::websocket::Stream; 2 | 3 | /// Mini Ticker Stream 4 | /// 5 | /// 24hr rolling window mini-ticker statistics. These are NOT the statistics of the UTC day, but a 24hr rolling window for the previous 24hrs. 6 | /// 7 | /// Update Speed: 1000ms. 8 | /// 9 | /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-mini-ticker-stream) 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | /// use binance_spot_connector_rust::market_stream::mini_ticker::MiniTickerStream; 15 | /// 16 | /// let individual_symbol_stream = MiniTickerStream::from_symbol("BTCUSDT"); 17 | /// let all_symbols_stream = MiniTickerStream::all_symbols(); 18 | /// ``` 19 | pub struct MiniTickerStream { 20 | symbol: Option, 21 | } 22 | 23 | impl MiniTickerStream { 24 | pub fn all_symbols() -> Self { 25 | Self { symbol: None } 26 | } 27 | 28 | pub fn from_symbol(symbol: &str) -> Self { 29 | Self { 30 | symbol: Some(symbol.to_lowercase()), 31 | } 32 | } 33 | } 34 | 35 | impl From for Stream { 36 | /// Returns stream name as `@miniTicker` or `!miniTicker@arr` 37 | fn from(stream: MiniTickerStream) -> Stream { 38 | if let Some(symbol) = stream.symbol { 39 | Stream::new(&format!("{}@miniTicker", symbol)) 40 | } else { 41 | Stream::new("!miniTicker@arr") 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/rsquant-core/template/email/monitor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | 21 |

{{ datetime }} {{ interval }} 趋势跟踪

22 |

23 |

买多

24 | 25 | 26 | 27 | {% for header in headers %} 28 | 29 | {% endfor %} 30 | 31 | 32 | 33 | {% for s in buy_symbols %} 34 | 35 | 36 | 37 | {% endfor %} 38 | 39 | 40 |

41 |

卖空

42 | 43 | 44 | 45 | {% for header in headers %} 46 | 47 | {% endfor %} 48 | 49 | 50 | 51 | {% for s in sell_symbols %} 52 | 53 | 54 | 55 | {% endfor %} 56 | 57 | 58 |

59 | 60 | 61 | -------------------------------------------------------------------------------- /deps/binan_spot/src/ureq/mod.rs: -------------------------------------------------------------------------------- 1 | //! Binance HTTP blocking client using ureq. 2 | //! 3 | //! # Usage 4 | //! 5 | //! ```no_run 6 | //! use binance_spot_connector_rust::{ market::{self, klines::KlineInterval}, ureq::BinanceHttpClient }; 7 | //! 8 | //! let client = BinanceHttpClient::default(); 9 | //! 10 | //! let request = market::klines("BTCUSDT", KlineInterval::Minutes1); 11 | //! 12 | //! let data = client.send(request).expect("Failed to send request").into_body_str().expect("Failed to parse body"); 13 | //! ``` 14 | //! 15 | //! # Testnet 16 | //! 17 | //! Can be configured to communicate with the testnet environment by specifying the base url on initialization. 18 | //! 19 | //! ``` 20 | //! use binance_spot_connector_rust::ureq::BinanceHttpClient; 21 | //! 22 | //! let testnet_client = BinanceHttpClient::with_url("https://testnet.binance.vision"); 23 | //! ``` 24 | //! 25 | //! # Errors 26 | //! 27 | //! All errors emitted by the client can be converted into [Error]. 28 | //! 29 | //! # Timeout 30 | //! 31 | //! ``` 32 | //! use ureq::{Agent, AgentBuilder}; 33 | //! use binance_spot_connector_rust::ureq::BinanceHttpClient; 34 | //! use std::time::Duration; 35 | //! 36 | //! let agent: Agent = AgentBuilder::new() 37 | //! .timeout(Duration::from_secs(5)) 38 | //! .build(); 39 | //! 40 | //! let client = BinanceHttpClient::new(agent, "https://api1.binance.com"); 41 | //! ``` 42 | 43 | mod client; 44 | mod error; 45 | mod response; 46 | 47 | pub use client::*; 48 | pub use error::*; 49 | pub use response::*; 50 | -------------------------------------------------------------------------------- /crates/rsquant-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsquant-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tracing = { workspace = true } 10 | tracing-subscriber = { workspace = true } 11 | chrono = { workspace = true } 12 | serde = { workspace = true } 13 | rust_decimal = { workspace = true } 14 | fast-float = { workspace = true } 15 | thiserror = { workspace = true } 16 | serde_json = { workspace = true } 17 | actix = { workspace = true } 18 | actix-web = { workspace = true } 19 | tokio = { workspace = true } 20 | 21 | 22 | binan_spot = { path = "../../deps/binan_spot", features = ["full"] } 23 | rsquant-derive = { path = "../rsquant-derive" } 24 | rsquant-tool = { path = "../rsquant-tool" } 25 | 26 | once_cell = "1.19.0" 27 | itertools = "0.13.0" 28 | clokwerk = "0.4.0" 29 | lettre = "0.11.7" 30 | tera = "1.14.0" 31 | tracing-appender = "0.2.3" 32 | url = "2.5.0" 33 | sha2 = "0.10.8" 34 | http = "1.1.0" 35 | reqwest = { version = "0.12.4", features = ["rustls-tls", "gzip"] } 36 | futures = "0.3.30" 37 | actix-web-actors = "4.3.0" 38 | actix-cors = "0.7.0" 39 | ta = "0.5.0" 40 | polars = { version = "0.40.0", features = [ 41 | "lazy", 42 | "temporal", 43 | "describe", 44 | "json", 45 | "parquet", 46 | "dtype-datetime", 47 | "diff", 48 | ] } 49 | sea-orm = { version = "0.12.15", features = [ 50 | "macros", 51 | "sqlx-postgres", 52 | "debug-print", 53 | "runtime-tokio-native-tls", 54 | ] } 55 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market/ping.rs: -------------------------------------------------------------------------------- 1 | use crate::http::{ 2 | request::Request, 3 | Method, 4 | }; 5 | 6 | /// `GET /api/v3/ping` 7 | /// 8 | /// Test connectivity to the Rest API. 9 | /// 10 | /// Weight(IP): 1 11 | /// 12 | /// # Example 13 | /// 14 | /// ``` 15 | /// use binance_spot_connector_rust::market; 16 | /// 17 | /// let request = market::ping(); 18 | /// ``` 19 | pub struct Ping {} 20 | 21 | impl Ping { 22 | pub fn new() -> Self { 23 | Self {} 24 | } 25 | } 26 | 27 | impl From for Request { 28 | fn from(_request: Ping) -> Request { 29 | let params = vec![]; 30 | 31 | Request { 32 | path: "/api/v3/ping".to_owned(), 33 | method: Method::Get, 34 | params, 35 | credentials: None, 36 | sign: false, 37 | } 38 | } 39 | } 40 | 41 | impl Default for Ping { 42 | fn default() -> Self { 43 | Self::new() 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::Ping; 50 | use crate::http::{ 51 | request::Request, 52 | Method, 53 | }; 54 | 55 | #[test] 56 | fn market_ping_convert_to_request_test() { 57 | let request: Request = Ping::new().into(); 58 | 59 | assert_eq!( 60 | request, 61 | Request { 62 | path: "/api/v3/ping".to_owned(), 63 | credentials: None, 64 | method: Method::Get, 65 | params: vec![], 66 | sign: false 67 | } 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /web/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market_stream/diff_depth.rs: -------------------------------------------------------------------------------- 1 | use crate::websocket::Stream; 2 | 3 | /// Diff. Depth Stream 4 | /// 5 | /// Order book price and quantity depth updates used to locally manage an order book. 6 | /// 7 | /// Update Speed: 1000ms or 100ms. 8 | /// 9 | /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#partial-book-depth-streams) 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | /// use binance_spot_connector_rust::market_stream::diff_depth::DiffDepthStream; 15 | /// 16 | /// let stream = DiffDepthStream::from_1000ms("BTCUSDT"); 17 | /// let faster_update_speed_stream = DiffDepthStream::from_100ms("BTCUSDT"); 18 | /// ``` 19 | pub struct DiffDepthStream { 20 | symbol: String, 21 | faster_update_speed: bool, 22 | } 23 | 24 | impl DiffDepthStream { 25 | pub fn from_1000ms(symbol: &str) -> Self { 26 | Self { 27 | symbol: symbol.to_lowercase(), 28 | faster_update_speed: false, 29 | } 30 | } 31 | 32 | pub fn from_100ms(symbol: &str) -> Self { 33 | Self { 34 | symbol: symbol.to_lowercase(), 35 | faster_update_speed: true, 36 | } 37 | } 38 | } 39 | 40 | impl From for Stream { 41 | /// Returns stream name as `@depth` or `@depth@100ms` 42 | fn from(stream: DiffDepthStream) -> Stream { 43 | if stream.faster_update_speed { 44 | Stream::new(&format!("{}@depth@100ms", stream.symbol)) 45 | } else { 46 | Stream::new(&format!("{}@depth", stream.symbol)) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /deps/binan_spot/src/market/time.rs: -------------------------------------------------------------------------------- 1 | use crate::http::{ 2 | request::Request, 3 | Method, 4 | }; 5 | 6 | /// `GET /api/v3/time` 7 | /// 8 | /// Test connectivity to the Rest API and get the current server time. 9 | /// 10 | /// Weight(IP): 1 11 | /// 12 | /// # Example 13 | /// 14 | /// ``` 15 | /// use binance_spot_connector_rust::market; 16 | /// 17 | /// let request = market::time(); 18 | /// ``` 19 | pub struct Time {} 20 | 21 | impl Time { 22 | pub fn new() -> Self { 23 | Self {} 24 | } 25 | } 26 | 27 | impl From