Repository: yasstake/rbot (language: python)

Issue:
取引ペアーの拡大
取引ペアーを拡大する。

Step1: Pythonで拡張のサンプルを記述し公開する。
　　　　　通貨ペアーは定数定義なので、Pythonオブジェクトとして設定できる。
　　　　　ETHUSDTをサンプルに拡張の方法をドキュメント化する。

Step2:取引量上位からいくつか実装に取り込む。
　　　　　上記のRust実装をPyPIにあげられるように実装する。


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index e88c468f..95e75c53 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -74,7 +74,7 @@ tokio-stream = { version = "0.1.14" }
 
 rusqlite = { version = "0.31.0", features = ["bundled"] }
     
-polars = { version = "0.40.0", features = [
+polars = { version = "0.41.3", features = [
         "lazy", 
         "ndarray", 
         "dtype-time", 
@@ -106,7 +106,7 @@ polars = { version = "0.40.0", features = [
 #] }
 # polars-time = { version = "0.38.3" }
 # polars-ops = { version = "0.38.3" }
-pyo3-polars = { version = "0.14.0" }
+pyo3-polars = { version = "0.15.0" }
 
 
 ndarray = { version = "0.15" }
@@ -133,7 +133,7 @@ sha2 = { version = "0.10.7" }
 hex = { version = "0.4.3" }
 
 #tungstenite = { version = "0.21", features = ["native-tls"] }
-tokio-tungstenite = { version = "0.21.0", features = ["native-tls"] }
+tokio-tungstenite = { version = "0.23.1", features = ["native-tls"] }
 
 async-std = { version = "1.12", features = ["attributes"] }
 
diff --git a/exchanges/binance/Cargo.toml b/exchanges/binance/Cargo.toml
index 7810d99c..b3ef43c1 100644
--- a/exchanges/binance/Cargo.toml
+++ b/exchanges/binance/Cargo.toml
@@ -35,7 +35,7 @@ async-stream = {workspace = true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.2"
+version = "0.21.0"
 features = ["rust_decimal", "auto-initialize", "abi3-py38"]
 
 [features]
diff --git a/exchanges/binance/src/ws.rs b/exchanges/binance/src/ws.rs
index 0b2c7875..bd6e7fe2 100644
--- a/exchanges/binance/src/ws.rs
+++ b/exchanges/binance/src/ws.rs
@@ -2,6 +2,8 @@ use std::time::Duration;
 
 use futures::Stream;
 use futures::StreamExt;
+use rbot_lib::common::PriceType;
+use rbot_lib::common::FeeType;
 use rbot_lib::net::ReceiveMessage;
 // use serde::{Deserialize as _, Serialize as _};
 use tokio::task::JoinHandle;
@@ -170,7 +172,21 @@ pub struct BinancePrivateWsClient {
 
 impl BinancePrivateWsClient {
     pub async fn new(server: &BinanceServerConfig) -> Self {
-        let dummy_config = MarketConfig::new("dummy", "dummy", "dummy", 0, 0, 0);
+        let dummy_config = MarketConfig::new(
+            "dummy",
+            "dummy",
+            "dummy",
+            "dummy",
+            0,
+            PriceType::Home,
+            0,
+            0,
+            0.1,
+            0.0,
+            0.0,
+            FeeType::Home,
+            vec!["dummy".to_string()],
+        );
 
         let listen_key = Self::make_listen_key(server).await.unwrap();
         let url = Self::make_connect_url(server, &listen_key).unwrap();
@@ -203,7 +219,7 @@ impl BinancePrivateWsClient {
 
         let hander = tokio::task::spawn(async move {
             loop {
-                sleep(Duration::from_secs(60*60)).await;
+                sleep(Duration::from_secs(60 * 60)).await;
                 let r = BinanceRestApi::extend_listen_key(&server, &key).await;
                 log::info!("Extend listen key");
                 if r.is_err() {
@@ -335,7 +351,7 @@ mod tests {
     #[tokio::test]
     async fn test_binance_private_ws_client() {
         init_debug_log();
-        
+
         let server = BinanceServerConfig::new(false);
         let mut client = BinancePrivateWsClient::new(&server).await;
 
diff --git a/exchanges/bybit/Cargo.toml b/exchanges/bybit/Cargo.toml
index 64afe5a1..e89735a0 100644
--- a/exchanges/bybit/Cargo.toml
+++ b/exchanges/bybit/Cargo.toml
@@ -41,7 +41,7 @@ async-stream = {workspace = true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.2"
+version = "0.21.0"
 features = ["rust_decimal", "auto-initialize", "abi3-py38"]
 
 [features]
diff --git a/exchanges/bybit/src/config.rs b/exchanges/bybit/src/config.rs
index 814e0374..178618f6 100644
--- a/exchanges/bybit/src/config.rs
+++ b/exchanges/bybit/src/config.rs
@@ -2,8 +2,7 @@
 use std::env;
 
 use pyo3::prelude::*;
-use rust_decimal_macros::dec;
-use serde_derive::{Serialize, Deserialize};
+use serde_derive::{Deserialize, Serialize};
 
 use rbot_lib::common::{FeeType, MarketConfig, PriceType, SecretString, ServerConfig};
 
@@ -26,21 +25,21 @@ impl BybitServerConfig {
     #[new]
     pub fn new(production: bool) -> Self {
         let rest_server = if production {
-            "https://api.bybit.com"            
+            "https://api.bybit.com"
         } else {
             "https://api-testnet.bybit.com"
         }
         .to_string();
 
         let public_ws_server = if production {
-            "wss://stream.bybit.com/v5/public"            
+            "wss://stream.bybit.com/v5/public"
         } else {
             "wss://stream-testnet.bybit.com/v5/public"
         }
         .to_string();
 
         let private_ws_server = if production {
-            "wss://stream.bybit.com/v5/private"            
+            "wss://stream.bybit.com/v5/private"
         } else {
             "wss://stream-testnet.bybit.com/v5/private"
         }
@@ -56,7 +55,7 @@ impl BybitServerConfig {
             private_ws: private_ws_server,
             history_web_base: "https://public.bybit.com".to_string(),
             api_key: SecretString::new(&api_key),
-            api_secret: SecretString::new(&api_secret)
+            api_secret: SecretString::new(&api_secret),
         };
     }
 
@@ -64,11 +63,9 @@ impl BybitServerConfig {
         let repr = serde_json::to_string(&self).unwrap();
         Ok(repr)
     }
-
 }
 
 impl ServerConfig for BybitServerConfig {
-
     fn get_public_ws_server(&self) -> String {
         self.public_ws.clone()
     }
@@ -92,13 +89,15 @@ impl ServerConfig for BybitServerConfig {
     fn get_historical_web_base(&self) -> String {
         self.history_web_base.clone()
     }
-
 }
 
 #[derive(Debug, Clone, Serialize)]
 #[pyclass]
 pub struct BybitConfig {}
 
+// 取引ペアーの制限については以下を参照
+// https://www.bybit.com/ja-JP/announcement-info/transact-parameters/
+
 #[pymethods]
 impl BybitConfig {
     #[new]
@@ -134,27 +133,167 @@ impl BybitConfig {
 
     #[classattr]
     pub fn BTCUSDT() -> MarketConfig {
-        MarketConfig {
-            exchange_name: BYBIT.to_string(),
-            price_unit: dec![0.1],
-            price_scale: 2,
-            size_unit: dec![0.001],
-            size_scale: 4,
-            maker_fee: dec![0.00_01],
-            taker_fee: dec![0.00_01],
-            price_type: PriceType::Home,
-            fee_type: FeeType::Home,
-            home_currency: "USDT".to_string(),
-            foreign_currency: "BTC".to_string(),
-            market_order_price_slip: dec![0.01],
-            board_depth: 200,
-            trade_category: "linear".to_string(),
-            trade_symbol: "BTCUSDT".to_string(),
-            public_subscribe_channel: vec![
+        MarketConfig::new(
+            BYBIT,
+            "linear",
+            "USDT",
+            "BTC",
+            1,
+            PriceType::Home,
+            3,
+            200,
+            0.1,
+            0.00_01,
+            0.00_01,
+            FeeType::Home,
+            vec![
                 "publicTrade.BTCUSDT".to_string(),
                 "orderbook.200.BTCUSDT".to_string(),
             ],
-        }
+        )
+    }
+
+    #[classattr]
+    pub fn BTCUSDC() -> MarketConfig {
+        let mut config = MarketConfig::new(
+            BYBIT,
+            "linear",
+            "USDC",
+            "BTC",
+            1,
+            PriceType::Home,
+            3,
+            200,
+            0.1,
+            0.00_01,
+            0.00_01,
+            FeeType::Home,
+            vec![
+                "publicTrade.BTCPERP".to_string(),
+                "orderbook.200.BTCPERP".to_string(),
+            ],
+        );
+
+        config.trade_symbol = "BTCPERP".to_string();
+        config
+    }
+
+
+    #[classattr]
+    pub fn ETHUSDT() -> MarketConfig {
+        let config = MarketConfig::new(
+            BYBIT,
+            "linear",
+            "USDT",            
+            "ETH",
+            2,
+            PriceType::Home,
+            2,
+            200,
+            0.1,
+            0.00_01,
+            0.00_01,
+            FeeType::Home,
+            vec![
+                "publicTrade.ETHUSDT".to_string(),
+                "orderbook.200.ETHUSDT".to_string(),
+            ],
+        );
+
+        config
+    }
+
+    #[classattr]
+    pub fn ETHUSDC() -> MarketConfig {
+        let mut config = MarketConfig::new(
+            BYBIT,
+            "linear",
+            "USDC",            
+            "ETH",
+            2,
+            PriceType::Home,
+            2,
+            200,
+            0.1,
+            0.00_01,
+            0.00_01,
+            FeeType::Home,
+            vec![
+                "publicTrade.ETHPERP".to_string(),
+                "orderbook.200.ETHPERP".to_string(),
+            ],
+        );
+
+        config.trade_symbol = "ETHPERP".to_string();
+        config
+    }
+
+    #[classattr]
+    pub fn MNTUSDT() -> MarketConfig {
+        MarketConfig::new(
+            BYBIT,
+            "linear",
+            "USDT",            
+            "MNT",
+            1,
+            PriceType::Home,
+            0,
+            200,
+            0.1,
+            0.00_01,
+            0.00_01,
+            FeeType::Home,
+            vec![
+                "publicTrade.MNTUSDT".to_string(),
+                "orderbook.200.MNTUSDT".to_string(),
+            ],
+        )
+    }
+
+
+    #[classattr]
+    pub fn SOLUSDT() -> MarketConfig {
+        MarketConfig::new(
+            BYBIT,
+            "linear",
+            "USDT",            
+            "SOL",
+            2,
+            PriceType::Home,
+            1,
+            200,
+            0.1,
+            0.00_01,
+            0.00_01,
+            FeeType::Home,
+            vec![
+                "publicTrade.SOLUSDT".to_string(),
+                "orderbook.200.SOLUSDT".to_string(),
+            ],
+        )
+    }
+
+
+    #[classattr]
+    pub fn USDCUSDT() -> MarketConfig {
+        MarketConfig::new(
+            BYBIT,
+            "linear",
+            "USDT",            
+            "USDC",
+            4,
+            PriceType::Home,
+            1,
+            200,
+            0.1,
+            0.00_01,
+            0.00_01,
+            FeeType::Home,
+            vec![
+                "publicTrade.USDCUSDT".to_string(),
+                "orderbook.200.USDCUSDT".to_string(),
+            ],
+        )
     }
 
     pub fn __repr__(&self) -> PyResult<String> {
@@ -163,7 +302,6 @@ impl BybitConfig {
     }
 }
 
-
 #[cfg(test)]
 mod test_bybit_config {
     use super::*;
@@ -174,11 +312,36 @@ mod test_bybit_config {
         println!("{:?}", config);
     }
 
-
     #[test]
     fn test_bybit_config() {
         let config = BybitConfig::new();
         println!("{:?}", config);
     }
-}
 
+    #[test]
+    fn test_create_market_config() {
+        let config = BybitConfig::BTCUSDT();
+        println!("{:?}", config);
+
+        let new_config = MarketConfig::new(
+            BYBIT,
+            "linear",
+            "USDT",
+            "BTC",
+            1,
+            PriceType::Home,
+            3,
+            200,
+            0.1,
+            0.00_01,
+            0.00_01,
+            FeeType::Home,
+            vec![
+                "publicTrade.BTCUSDT".to_string(),
+                "orderbook.200.BTCUSDT".to_string(),
+            ],
+        );
+
+        assert_eq!(config, new_config);
+    }
+}
diff --git a/exchanges/bybit/src/ws.rs b/exchanges/bybit/src/ws.rs
index 53722061..e5087750 100644
--- a/exchanges/bybit/src/ws.rs
+++ b/exchanges/bybit/src/ws.rs
@@ -27,6 +27,7 @@ use crate::message::BybitOrderStatus;
 use crate::message::BybitPublicWsMessage;
 use crate::message::BybitUserMessage;
 use crate::message::BybitUserWsMessage;
+use crate::BybitConfig;
 
 use super::config::BybitServerConfig;
 
@@ -178,7 +179,7 @@ pub struct BybitPrivateWsClient {
 
 impl BybitPrivateWsClient {
     pub async fn new(server: &BybitServerConfig) -> Self {
-        let dummy_config = MarketConfig::new("dummy", "dummy", "dummy", 0, 0, 0);
+        let dummy_config = BybitConfig::BTCUSDT();
 
         let mut private_ws = AutoConnectClient::new(
             server,
diff --git a/rbot/Cargo.toml b/rbot/Cargo.toml
index 08160d04..dc10aea5 100644
--- a/rbot/Cargo.toml
+++ b/rbot/Cargo.toml
@@ -18,14 +18,14 @@ rbot_server = {path="../rbot_server"}
 rbot_market = {path="../rbot_market"}
 once_cell = {workspace = true}
 
-console-subscriber = {version="0.2.0"}
+console-subscriber = {version="0.3.0"}
 
 tracing = {workspace= true}
 tracing-subscriber = {workspace= true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.2"
+version = "0.21.0"
 features = ["rust_decimal", "auto-initialize", "abi3-py38"]
 
 [features]
diff --git a/rbot/src/lib.rs b/rbot/src/lib.rs
index b2635fcc..104fecc7 100644
--- a/rbot/src/lib.rs
+++ b/rbot/src/lib.rs
@@ -2,9 +2,11 @@
 // All rights reserved. Absolutely NO warranty.
 
 
-use pyo3::{pymodule, types::PyModule, wrap_pyfunction, PyResult, Python};
+use pyo3::{pymodule, types::PyModule, wrap_pyfunction, Bound, PyResult};
 use rbot_lib::{common::{
-    get_orderbook, get_orderbook_list, init_debug_log, init_log, time_string, AccountCoins, AccountPair, BoardItem, MarketConfig, MarketMessage, Order, OrderSide, OrderStatus, OrderType, Trade, DAYS, DAYS_BEFORE, FLOOR_SEC, HHMM, MIN, NOW, SEC
+    get_orderbook, get_orderbook_list, init_debug_log, init_log, time_string, AccountCoins, AccountPair, 
+    FeeType, PriceType,
+    BoardItem, MarketConfig, Order, OrderSide, OrderStatus, OrderType, Trade, DAYS, DAYS_BEFORE, FLOOR_SEC, HHMM, MIN, NOW, SEC
 }, db::{get_db_root, set_db_root}};
 
 use rbot_session::{Logger, Session, Runner, ExecuteMode};
@@ -14,7 +16,8 @@ use binance::{Binance, BinanceConfig};
 
 // use console_subscriber;
 #[pymodule]
-fn rbot(_py: Python, m: &PyModule) -> PyResult<()> {
+fn rbot(m: &Bound<'_, PyModule>) -> PyResult<()> {
+// fn rbot(_py: Python, m: &PyModule) -> PyResult<()> {
     // console_subscriber::init();  // for tokio thread debug
     //tracing_subscriber::fmt::init();
 
@@ -58,10 +61,9 @@ fn rbot(_py: Python, m: &PyModule) -> PyResult<()> {
     m.add_class::<Runner>()?;
     m.add_class::<ExecuteMode>()?;
 
-    //    m.add_class::<MarketMessage>()?;
-    //m.add_class::<Broadcast>()?;
-    //m.add_class::<BroadcastMessage>()?;
-    
+    m.add_class::<FeeType>()?;
+    m.add_class::<PriceType>()?;
+
     // Binance
     m.add_class::<Binance>()?;
     m.add_class::<BinanceConfig>()?;
@@ -70,5 +72,6 @@ fn rbot(_py: Python, m: &PyModule) -> PyResult<()> {
     m.add_class::<Bybit>()?;
     m.add_class::<BybitConfig>()?;    
 
+
     Ok(())
 }
diff --git a/rbot_lib/Cargo.toml b/rbot_lib/Cargo.toml
index 70c7ed88..c49fc2ac 100644
--- a/rbot_lib/Cargo.toml
+++ b/rbot_lib/Cargo.toml
@@ -63,7 +63,7 @@ tempfile={workspace=true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.2"
+version = "0.21.0"
 features = ["rust_decimal", "auto-initialize", "abi3-py38", "anyhow"]
 
 [features]
diff --git a/rbot_lib/src/common/config.rs b/rbot_lib/src/common/config.rs
index 4ad108df..60de22b7 100644
--- a/rbot_lib/src/common/config.rs
+++ b/rbot_lib/src/common/config.rs
@@ -2,22 +2,24 @@
 // ABUSOLUTELY NO WARRANTY.
 
 use pyo3::{pyclass, pymethods};
-use rust_decimal::Decimal;
+use rust_decimal::{prelude::FromPrimitive, Decimal};
 use rust_decimal_macros::dec;
 use serde_derive::{Serialize, Deserialize};
 
+use crate::db::KEY::price;
+
 use super::SecretString;
 
 
 #[pyclass]
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum FeeType {
     Home,
     Foreign,
     Both,
 }
 #[pyclass]
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum PriceType {
     Home,
     Foreign,
@@ -25,7 +27,7 @@ pub enum PriceType {
 }
 
 #[pyclass]
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct MarketConfig {
     #[pyo3(set)]
     pub exchange_name: String,
@@ -78,38 +80,48 @@ impl MarketConfig {
     pub fn __str__(&self) -> String {
         self.__repr__()
     }
-}
 
-
-impl MarketConfig {
+    #[new]
     pub fn new(
+        exchange_name: &str,
         trade_category: &str,
         home_currency: &str,
         foreign_currency: &str,
         price_scale: u32,
+        price_type: PriceType,
         size_scale: u32,
         board_depth: u32,
+        market_order_price_slip: f64,
+        maker_fee: f64,
+        taker_fee: f64,
+        fee_type: FeeType,
+        public_subscribe_channel: Vec<String>,
     ) -> Self {
+        let maker_fee = Decimal::from_f64(maker_fee).unwrap();
+        let taker_fee = Decimal::from_f64(taker_fee).unwrap();
+
         Self {
-            exchange_name: "DUMMY".to_string(),
+            exchange_name: exchange_name.to_string(),
             price_unit: Decimal::new(1, price_scale),
             price_scale,
             size_unit: Decimal::new(1, size_scale),
             size_scale,
-            maker_fee: dec![0.0], // dec![0.00_015],  // 0.015%
-            taker_fee: dec![0.0], // dec![0.00_015],  // 0.015%
-            price_type: PriceType::Foreign,
-            fee_type: FeeType::Home,
+            maker_fee,
+            taker_fee,
+            price_type,
+            fee_type,
             home_currency: home_currency.to_string(),
             foreign_currency: foreign_currency.to_string(),
-            market_order_price_slip: dec![0.0],
-            board_depth: board_depth,
+            market_order_price_slip: Decimal::from_f64(market_order_price_slip).unwrap(),
+            board_depth,
             trade_category: trade_category.to_string(),
             trade_symbol: format!("{}{}", foreign_currency, home_currency),
-            public_subscribe_channel: vec![],
+            public_subscribe_channel: public_subscribe_channel
         }
     }
+}
 
+impl MarketConfig {
     pub fn new_bitflyer(
         trade_category: &str,
         home_currency: &str,
diff --git a/rbot_lib/src/common/order.rs b/rbot_lib/src/common/order.rs
index fe374d0d..b49681f4 100644
--- a/rbot_lib/src/common/order.rs
+++ b/rbot_lib/src/common/order.rs
@@ -3,16 +3,16 @@
 
 use std::path::Display;
 
-use crate::common::time::time_string;
-use crate::db::get_db_root;
 use super::time::MicroSec;
 use super::FeeType;
 use super::MarketConfig;
 use super::MarketMessage;
 use super::SEC;
+use crate::common::time::time_string;
+use crate::db::get_db_root;
 use async_std::stream::Cloned;
 
-use polars::prelude::DataFrame;    
+use polars::prelude::DataFrame;
 use polars::prelude::NamedFrom;
 use polars::prelude::TimeUnit;
 use polars::series::Series;
@@ -235,8 +235,8 @@ pub enum LogStatus {
     FixRestApiBlock, // データが確定(アーカイブ）し、ブロックの中間を表す（REST API）
     FixRestApiEnd,
     ExpireControlForce, // 削除指示（アーカイブ意外は強制削除）
-    ExpireControl, // 削除指示(通常：WSデータのみ削除)
-    Unknown,       // 未知のステータス / 未確定のステータス
+    ExpireControl,      // 削除指示(通常：WSデータのみ削除)
+    Unknown,            // 未知のステータス / 未確定のステータス
 }
 
 impl Default for LogStatus {
@@ -256,7 +256,7 @@ impl From<&str> for LogStatus {
             "a" => LogStatus::FixRestApiBlock,
             "e" => LogStatus::FixRestApiEnd,
             "X" => LogStatus::ExpireControlForce,
-            "x" => LogStatus::ExpireControl,            
+            "x" => LogStatus::ExpireControl,
             _ => {
                 log::error!("Unknown log status: {:?}", status);
                 LogStatus::Unknown
@@ -275,7 +275,7 @@ impl LogStatus {
             LogStatus::FixRestApiStart => "s".to_string(),
             LogStatus::FixRestApiBlock => "a".to_string(),
             LogStatus::FixRestApiEnd => "e".to_string(),
-            LogStatus::ExpireControlForce => "X".to_string(),            
+            LogStatus::ExpireControlForce => "X".to_string(),
             LogStatus::ExpireControl => "x".to_string(),
             LogStatus::Unknown => "?".to_string(),
         }
@@ -329,7 +329,6 @@ impl Trade {
         )
     }
 
-
     pub fn __str__(&self) -> String {
         format!(
             "{{timestamp:{}({:?}), order_side:{:?}, price:{:?}, size:{:?}, id:{:?}, status{:?}}}",
@@ -376,7 +375,6 @@ impl Default for Trade {
     }
 }
 
-
 #[pyclass]
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct AccountChange {
@@ -411,7 +409,6 @@ impl Default for AccountChange {
     }
 }
 
-
 #[pyclass]
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct Coin {
@@ -471,7 +468,7 @@ impl Coin {
 
     pub fn __str__(&self) -> String {
         self.__repr__()
-    }    
+    }
 }
 
 #[pyclass]
@@ -483,7 +480,7 @@ pub struct AccountCoins {
 impl Default for AccountCoins {
     fn default() -> Self {
         AccountCoins::new()
-    }    
+    }
 }
 
 #[pymethods]
@@ -554,7 +551,7 @@ impl AccountCoins {
 
     pub fn apply_order(&mut self, config: &MarketConfig, order: &Order) {
         self.diff_update(
-            &config.home_currency, 
+            &config.home_currency,
             order.home_change,
             order.free_home_change,
             order.lock_home_change,
@@ -585,10 +582,8 @@ impl AccountCoins {
 
         return Ok(Coin::new(key));
     }
-
 }
 
-
 #[pyclass]
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct AccountPair {
@@ -602,15 +597,15 @@ impl Default for AccountPair {
             home: Coin::default(),
             foreign: Coin::default(),
         }
-            /*
-            home: dec![0.0],
-            home_free: dec![0.0],
-            home_locked: dec![0.0],
-
-            foreign: dec![0.0],
-            foreign_free: dec![0.0],
-            foreign_locked: dec![0.0],
-            */
+        /*
+        home: dec![0.0],
+        home_free: dec![0.0],
+        home_locked: dec![0.0],
+
+        foreign: dec![0.0],
+        foreign_free: dec![0.0],
+        foreign_locked: dec![0.0],
+        */
     }
 }
 
@@ -739,35 +734,35 @@ impl Order {
         size: Decimal,
     ) -> Self {
         Order {
-            category:category.to_string(),
-            symbol:symbol.to_string(),
+            category: category.to_string(),
+            symbol: symbol.to_string(),
             create_time,
-            status:order_status,
-            order_id:order_id.to_string(),
-            client_order_id:client_order_id.to_string(),
+            status: order_status,
+            order_id: order_id.to_string(),
+            client_order_id: client_order_id.to_string(),
             order_side,
             order_type,
-            order_price:price.clone(),
-            order_size:size.clone(),
-            remain_size:size.clone(),
-            transaction_id:"".to_string(),
-            update_time:0,
-            execute_price:dec![0.0],
-            execute_size:dec![0.0],
-            quote_vol:dec![0.0],
-            commission:dec![0.0],
-            commission_asset:"".to_string(),
-            is_maker:false,
-            message:"".to_string(),
-            commission_home:dec![0.0],
-            commission_foreign:dec![0.0],
-            home_change:dec![0.0],
-            foreign_change:dec![0.0],
-            free_home_change:dec![0.0],
-            free_foreign_change:dec![0.0],
-            lock_home_change:dec![0.0],
-            lock_foreign_change:dec![0.0],
-            log_id:0, 
+            order_price: price.clone(),
+            order_size: size.clone(),
+            remain_size: size.clone(),
+            transaction_id: "".to_string(),
+            update_time: 0,
+            execute_price: dec![0.0],
+            execute_size: dec![0.0],
+            quote_vol: dec![0.0],
+            commission: dec![0.0],
+            commission_asset: "".to_string(),
+            is_maker: false,
+            message: "".to_string(),
+            commission_home: dec![0.0],
+            commission_foreign: dec![0.0],
+            home_change: dec![0.0],
+            foreign_change: dec![0.0],
+            free_home_change: dec![0.0],
+            free_foreign_change: dec![0.0],
+            lock_home_change: dec![0.0],
+            lock_foreign_change: dec![0.0],
+            log_id: 0,
             open_position: dec![0.0],
             close_position: dec![0.0],
             position: dec![0.0],
@@ -1110,26 +1105,23 @@ impl Order {
                     self.commission_foreign = commission;
                     self.commission_asset = config.foreign_currency.clone();
                 }
-                FeeType::Both => {
-                    match self.order_side {
-                        OrderSide::Buy => {
-                            self.commission_home = commission;
-                            self.commission_foreign = dec![0.0];
-                            self.commission_asset = config.home_currency.clone();
-                        }
-                        OrderSide::Sell => {
-                            self.commission_home = dec![0.0];
-                            self.commission_foreign = commission;
-                            self.commission_asset = config.foreign_currency.clone();
-                        }
-                        _ => {
-                            log::error!("Unknown order side: {:?}", self.order_side);
-                        }
+                FeeType::Both => match self.order_side {
+                    OrderSide::Buy => {
+                        self.commission_home = commission;
+                        self.commission_foreign = dec![0.0];
+                        self.commission_asset = config.home_currency.clone();
                     }
-                }
+                    OrderSide::Sell => {
+                        self.commission_home = dec![0.0];
+                        self.commission_foreign = commission;
+                        self.commission_asset = config.foreign_currency.clone();
+                    }
+                    _ => {
+                        log::error!("Unknown order side: {:?}", self.order_side);
+                    }
+                },
             }
-        }
-        else if commission_asset == config.home_currency {
+        } else if commission_asset == config.home_currency {
             self.commission_home = commission;
             self.commission_foreign = dec![0.0];
         } else {
@@ -1330,8 +1322,7 @@ impl Kline {
     }
 }
 
-
-pub fn convert_klines_to_trades(klines: Vec<Kline>, ) -> Vec<Trade> {
+pub fn convert_klines_to_trades(klines: Vec<Kline>) -> Vec<Trade> {
     let mut trades = Vec::new();
     for kline in klines {
         let mut kline_trades = kline.extract_to_trades(KLINE_TIME_UNIT_SEC);
@@ -1340,20 +1331,30 @@ pub fn convert_klines_to_trades(klines: Vec<Kline>, ) -> Vec<Trade> {
     trades
 }
 
-
 ///----------------------------- TEST ----------------------------------------------------------
 #[cfg(test)]
 mod order_tests {
+    use crate::common::PriceType;
+
     use super::*;
 
     fn create_config() -> MarketConfig {
-        let mut config = MarketConfig::new("SPOT", "BTC", "USDT", 2, 4, 1000);
-
-        config.taker_fee = dec![0.0001];
-        config.maker_fee = dec![0.0001];
+        let config = MarketConfig::new(
+            "BINANCE",
+            "SPOT",
+            "BTC",
+            "USDT",
+            2,
+            PriceType::Home,
+            4,
+            1000,
+            0.0001,
+            0.001,
+            0.001,
+            FeeType::Home,
+            vec!["".to_string()],
+        );
 
-        config.trade_category = "SPOT".to_string();
-        config.trade_symbol = "BTCUSDT".to_string();
         config
     }
 
diff --git a/rbot_lib/src/common/orderbook.rs b/rbot_lib/src/common/orderbook.rs
index 08b9bdaa..a99d3a4c 100644
--- a/rbot_lib/src/common/orderbook.rs
+++ b/rbot_lib/src/common/orderbook.rs
@@ -647,8 +647,6 @@ mod board_test {
 
     #[test]
     fn test_board_set() {
-        let mut config = MarketConfig::new("SPOT", "USD", "JPY", 2, 2, 1000);
-        config.price_unit = dec!(0.5);
 
         let mut b = Board::new(0, true);
 
@@ -687,8 +685,6 @@ mod board_test {
 
     #[test]
     fn serialize_board_transfer() {
-        let mut config = MarketConfig::new("SPOT", "USD", "JPY", 2, 2, 1000);
-        config.price_unit = dec!(0.5);
 
         let mut b = OrderBookRaw::new(0);
 
diff --git a/rbot_lib/src/db/df.rs b/rbot_lib/src/db/df.rs
index 6c96f606..513de6f9 100644
--- a/rbot_lib/src/db/df.rs
+++ b/rbot_lib/src/db/df.rs
@@ -175,7 +175,7 @@ pub fn ohlcv_df(
         include_boundaries: false, // データの下限と上限を結果に含めるかどうか？(falseでOK)
         closed_window: ClosedWindow::Left, // t <=  x  < t+1       開始時間はWindowに含まれる。終了は含まれない(CloseWindow::Left)。
         start_by: StartBy::DataPoint,
-        check_sorted: false,
+        // check_sorted: false,
         ..Default::default()
     };
 
@@ -186,7 +186,7 @@ pub fn ohlcv_df(
             vec![(KEY::time_stamp).to_string()],
             SortMultipleOptions {
                 descending: vec![false],
-                nulls_last: false,
+                nulls_last: vec![false],
                 maintain_order: true,
                 multithreaded: true,
             },
@@ -273,7 +273,7 @@ pub fn ohlcvv_df(
             vec![KEY::time_stamp.to_string()],
             SortMultipleOptions {
                 descending: vec![false],
-                nulls_last: false,
+                nulls_last: vec![false],
                 maintain_order: true,
                 multithreaded: true,
             },
@@ -336,7 +336,7 @@ pub fn ohlcv_from_ohlcvv_df(
             vec![(KEY::time_stamp).to_string()],
             SortMultipleOptions {
                 descending: vec![false],
-                nulls_last: false,
+                nulls_last: vec![false],
                 maintain_order: true,
                 multithreaded: true,
             },
@@ -359,7 +359,7 @@ pub fn ohlcv_from_ohlcvv_df(
                     vec![col(KEY::start_time)],
                     SortMultipleOptions{
                         descending: vec![false],
-                        nulls_last: false,
+                        nulls_last: vec![false],
                         multithreaded: true,
                         maintain_order: true
                     }
@@ -373,7 +373,7 @@ pub fn ohlcv_from_ohlcvv_df(
                     vec![col(KEY::end_time)],
                     SortMultipleOptions{
                         descending: vec![false],
-                        nulls_last: false,
+                        nulls_last: vec![false],
                         multithreaded: true,
                         maintain_order: true
                     }
@@ -425,7 +425,7 @@ pub fn ohlcvv_from_ohlcvv_df(
             vec![(KEY::time_stamp).to_string()],
             SortMultipleOptions {
                 descending: vec![false],
-                nulls_last: false,
+                nulls_last: vec![false],
                 maintain_order: true,
                 multithreaded: true,
             },
@@ -521,7 +521,7 @@ pub fn vap_df(df: &DataFrame, start_time: MicroSec, end_time: MicroSec, size: i6
     let vap = pivot(
         &vap,
         vec![KEY::volume],
-        [KEY::price],
+        Some([KEY::price]),
         Some([KEY::order_side]),
         false,
         None,
@@ -533,7 +533,7 @@ pub fn vap_df(df: &DataFrame, start_time: MicroSec, end_time: MicroSec, size: i6
         vec![KEY::price.to_string()],
          SortMultipleOptions { 
             descending: vec![false], 
-            nulls_last: false, 
+            nulls_last: vec![false], 
             multithreaded: true, 
             maintain_order: true}
     ).unwrap();
@@ -553,7 +553,8 @@ pub fn vap_df(df: &DataFrame, start_time: MicroSec, end_time: MicroSec, size: i6
         .unwrap()
         .rename(KEY::sell_volume)
         .clone();
-    let total_vol = (&buy_vol + &sell_vol).rename(KEY::volume).clone();
+
+        let total_vol = (&buy_vol + &sell_vol).unwrap().rename(KEY::volume).clone();
 
     let df = DataFrame::new(vec![price, buy_vol, sell_vol, total_vol]).unwrap();
 
@@ -676,7 +677,7 @@ mod test_df {
         let option = DynamicGroupOptions {
             every: Duration::new(DAYS(1)),
             index_column: "date".into(),
-            check_sorted: true,
+            // check_sorted: true,
             start_by: StartBy::DataPoint,
             period: Duration::new(DAYS(1)), // データ取得の幅（グループ間隔と同じでOK)
             offset: Duration::parse("0m"),
@@ -703,7 +704,7 @@ mod test_df {
                 vec!["date".to_string()],
                 SortMultipleOptions {
                     descending: vec![false],
-                    nulls_last: false,
+                    nulls_last: vec![false],
                     multithreaded: true,
                     maintain_order: false,
                 },
@@ -744,7 +745,7 @@ mod test_df {
         return df.unwrap().sort([KEY::time_stamp], 
             SortMultipleOptions{
                 descending: vec![false],
-                nulls_last: false,
+                nulls_last: vec![false],
                 multithreaded: true,
                 maintain_order: true
             }).unwrap();
diff --git a/rbot_lib/src/net/ws.rs b/rbot_lib/src/net/ws.rs
index 19c805a4..bb973bae 100644
--- a/rbot_lib/src/net/ws.rs
+++ b/rbot_lib/src/net/ws.rs
@@ -95,10 +95,16 @@ where
         } else {
             self.url.clone()
         };
-
+/*
         log::debug!("start connect: {}", url);
-        let url = Url::parse(&url).unwrap();
+        let url = reqwest::Url::parse(&url);
+
+        if url.is_err() {
+            log::error!("Invalid URL: {}", self.url);
+        }
 
+        let url = url.unwrap();
+*/
         let client = connect_async(url).await;
         if client.is_err() {
             log::error!("Can't connect to {}", self.url);
diff --git a/rbot_market/Cargo.toml b/rbot_market/Cargo.toml
index 9d359055..5f5704c5 100644
--- a/rbot_market/Cargo.toml
+++ b/rbot_market/Cargo.toml
@@ -34,7 +34,7 @@ log = {workspace = true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.2"
+version = "0.21.0"
 features = ["rust_decimal", "auto-initialize", "abi3-py38"]
 
 [features]
diff --git a/rbot_session/Cargo.toml b/rbot_session/Cargo.toml
index 38baaeb7..bc3aa6e9 100644
--- a/rbot_session/Cargo.toml
+++ b/rbot_session/Cargo.toml
@@ -38,7 +38,7 @@ tokio={workspace = true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.2"
+version = "0.21.1"
 features = ["rust_decimal", "auto-initialize", "abi3-py38"]
 
 [features]


Test patch:
diff --git a/rbot/test/bybit_test/market_symbol.ipynb b/rbot/test/bybit_test/market_symbol.ipynb
new file mode 100644
index 00000000..890369f1
--- /dev/null
+++ b/rbot/test/bybit_test/market_symbol.ipynb
@@ -0,0 +1,369 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "! pip install rbot\n",
+    "! pip install json2html\n",
+    "! pip install IPython\n",
+    "! pip install polars"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from rbot import Bybit\n",
+    "from rbot import BybitConfig\n",
+    "from json2html import json2html\n",
+    "from IPython.display import HTML"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "BTCUSDT = BybitConfig.BTCUSDT\n",
+    "BTCUSDC = BybitConfig.BTCUSDC\n",
+    "ETHUSDT = BybitConfig.ETHUSDT\n",
+    "ETHUSDC = BybitConfig.ETHUSDC\n",
+    "MNTUSDT = BybitConfig.MNTUSDT\n",
+    "SOLUSDT = BybitConfig.SOLUSDT\n",
+    "USDCUSDT = BybitConfig.USDCUSDT"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "HTML(json2html.convert(json = BTCUSDT.__str__()))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "HTML(json2html.convert(json = BTCUSDC.__str__()))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "HTML(json2html.convert(json = ETHUSDT.__str__()))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "HTML(json2html.convert(json = ETHUSDC.__str__()))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "HTML(json2html.convert(json = MNTUSDT.__str__()))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "HTML(json2html.convert(json = SOLUSDT.__str__()))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "HTML(json2html.convert(json = USDCUSDT.__str__()))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange = Bybit(production=False) # production = false"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def test_download(config):\n",
+    "    market = exchange.open_market(config)\n",
+    "    market.download_archive(2)\n",
+    "    print(market.info())\n",
+    "    market.start_market_stream()\n",
+    "    return market"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "btcusdt = test_download(BTCUSDT)\n",
+    "btcusdt.ohlcv(0, 0, 60*60)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "btcusdc = test_download(BTCUSDC)\n",
+    "btcusdc.ohlcv(0, 0, 60*60)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ethusdt = test_download(ETHUSDT)\n",
+    "ethusdt.ohlcv(0, 0, 60*60)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ethusdc = test_download(ETHUSDC)\n",
+    "ethusdc.ohlcv(0, 0, 60*60)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mntusdt = test_download(MNTUSDT)\n",
+    "mntusdt.ohlcv(0, 0, 60*60)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "usdcusdt = test_download(SOLUSDT)\n",
+    "usdcusdt.ohlcv(0, 0, 60*60)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "usdcusdt = test_download(USDCUSDT)\n",
+    "usdcusdt.ohlcv(0, 0, 60*60)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "bit, ask = btcusdt.board\n",
+    "print(bit, ask)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "bit, ask = btcusdc.board\n",
+    "print(bit, ask)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "bit, ask = ethusdt.board\n",
+    "print(bit, ask)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "bit, ask = ethusdc.board\n",
+    "print(bit, ask)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "bit, ask = mntusdt.board\n",
+    "print(bit, ask)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "bit, ask = usdcusdt.board\n",
+    "print(bit, ask)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange.start_user_stream()\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange.enable_order_with_my_own_risk = True"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange.market_order(BTCUSDT, 'buy', 0.001)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\n",
+    "exchange.market_order(BTCUSDC, 'buy', 0.001)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange.market_order(ETHUSDT, 'buy', 0.01)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange.market_order(ETHUSDC, 'buy', 0.01)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange.market_order(MNTUSDT, 'buy', 10)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange.market_order(SOLUSDT, 'buy', 0.1)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exchange.market_order(USDCUSDT, 'buy', 10)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": ".venv",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.12.3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}