├── .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 |
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 | | {{ header }} |
29 | {% endfor %}
30 |
31 |
32 |
33 | {% for s in buy_symbols %}
34 |
35 | | {{ s }} |
36 |
37 | {% endfor %}
38 |
39 |
40 |
41 | 卖空
42 |
43 |
44 |
45 | {% for header in headers %}
46 | | {{ header }} |
47 | {% endfor %}
48 |
49 |
50 |
51 | {% for s in sell_symbols %}
52 |
53 | | {{ s }} |
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