Repository: yasstake/rbot (language: python)

Issue:
Sessionオブジェクトのメモリーリーク確認
ロングラン時にメモリーリークが発生していないか確認する。
Tickがレコードの半分しか呼ばれていない
ログを見る限りRecの半分になっている。カウントのミス、実際に呼ばれていないの両面から調査する。
<img width="1036" alt="image" src="https://github.com/user-attachments/assets/e606c28f-e039-49f5-bca7-a1f42e769797">



Discussion / hints:
1年分のロングランでskelton agentを動かすとメモリ利用料６４GBを超えて落ちるという報告があった。確認し修正する。
memrayで調査したところ、メモリリークがみられた。
おそらくSessionオブジェクトのあたりだろう。
pyo３の新しいメモリ管理へ変更することで対処する。

<img width="589" alt="image" src="https://github.com/user-attachments/assets/3b609a42-b155-4e20-883f-2688d42687c8">



Fix patch:
diff --git a/exchanges/binance/Cargo.toml b/exchanges/binance/Cargo.toml
index b3ef43c1..7810d99c 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.0"
+version = "0.21.2"
 features = ["rust_decimal", "auto-initialize", "abi3-py38"]
 
 [features]
diff --git a/exchanges/bybit/Cargo.toml b/exchanges/bybit/Cargo.toml
index beacea45..83a21dfc 100644
--- a/exchanges/bybit/Cargo.toml
+++ b/exchanges/bybit/Cargo.toml
@@ -39,8 +39,9 @@ async-stream = {workspace = true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.0"
-features = ["rust_decimal", "auto-initialize", "abi3-py38"]
+version = "0.21.2"
+features = ["rust_decimal", "auto-initialize", "abi3-py39", "anyhow"]
+
 
 [features]
 extension-module = ["pyo3/extension-module"]
diff --git a/rbot/Cargo.toml b/rbot/Cargo.toml
index 00890c5f..70bcc2e7 100644
--- a/rbot/Cargo.toml
+++ b/rbot/Cargo.toml
@@ -25,8 +25,8 @@ tracing-subscriber = {workspace= true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.0"
-features = ["rust_decimal", "auto-initialize", "abi3-py38"]
+version = "0.21.2"
+features = ["rust_decimal", "auto-initialize", "abi3-py39", "anyhow"]
 
 [features]
 extension-module = ["pyo3/extension-module"]
diff --git a/rbot/python/rbot/__init__.py b/rbot/python/rbot/__init__.py
index 0aa03e66..1cb987d2 100644
--- a/rbot/python/rbot/__init__.py
+++ b/rbot/python/rbot/__init__.py
@@ -13,7 +13,7 @@ def terminate(_a, _b):
 
 print("rbot version: ", rbot.__version__)
 print("!!! ABSOLUTELY NO WARRANTY. USE AT YOUR OWN RISK !!!")
-print("For some exchange, affliate or referer link may be included.")
+print("For some exchange, an affliate or referer link may be included.")
 print("Distributed under LGPL license. https://www.gnu.org/licenses/lgpl-3.0.txt")
 print("See the document at https://github.com/yasstake/rusty-bot")
-print("All rights reserved. (c) 2022-2024 rbot developers / yasstake")
+print("All rights reserved. (c) 2022-2024 rbot(rusty-bot) developers / yasstake")
diff --git a/rbot_lib/Cargo.toml b/rbot_lib/Cargo.toml
index 77aa269f..0a8217ee 100644
--- a/rbot_lib/Cargo.toml
+++ b/rbot_lib/Cargo.toml
@@ -69,8 +69,9 @@ tempfile={workspace=true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.0"
-features = ["rust_decimal", "auto-initialize", "abi3-py38", "anyhow"]
+version = "0.21.2"
+features = ["rust_decimal", "auto-initialize", "abi3-py39", "anyhow"]
+
 
 [features]
 extension-module = ["pyo3/extension-module"]
diff --git a/rbot_market/Cargo.toml b/rbot_market/Cargo.toml
index 5f5704c5..c62f8aa8 100644
--- a/rbot_market/Cargo.toml
+++ b/rbot_market/Cargo.toml
@@ -34,8 +34,8 @@ log = {workspace = true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.0"
-features = ["rust_decimal", "auto-initialize", "abi3-py38"]
+version = "0.21.2"
+features = ["rust_decimal", "auto-initialize", "abi3-py39", "anyhow"]
 
 [features]
 extension-module = ["pyo3/extension-module"]
diff --git a/rbot_session/Cargo.toml b/rbot_session/Cargo.toml
index bc3aa6e9..fc0046ab 100644
--- a/rbot_session/Cargo.toml
+++ b/rbot_session/Cargo.toml
@@ -38,8 +38,8 @@ tokio={workspace = true}
 
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
-version = "0.21.1"
-features = ["rust_decimal", "auto-initialize", "abi3-py38"]
+version = "0.21.2"
+features = ["rust_decimal", "auto-initialize", "abi3-py39", "anyhow"]
 
 [features]
 extension-module = ["pyo3/extension-module"]
diff --git a/rbot_session/src/python_if.rs b/rbot_session/src/python_if.rs
index d31d3d94..21a59e2d 100644
--- a/rbot_session/src/python_if.rs
+++ b/rbot_session/src/python_if.rs
@@ -1,10 +1,12 @@
-// Copyright(c) 2022-2023. yasstake. All rights reserved.
+// Copyright(c) 2022-2024. yasstake. All rights reserved.
 
-use pyo3::PyAny;
+use pyo3::{types::PyAnyMethods, Bound, PyAny};
 
 /// Checks if a given Python object has a method with the specified name.
-pub fn has_method(agent: &PyAny, method_name: &str) -> bool {
-    if agent.dir().contains(method_name).unwrap_or(false) {
+pub fn has_method(agent: &Bound<PyAny>, method_name: &str) -> bool {
+    let dir  = agent.dir();
+
+    if dir.contains(method_name).unwrap_or(false) {
         return true;
     } else {
         return false;
diff --git a/rbot_session/src/runner.rs b/rbot_session/src/runner.rs
index a9976f5a..ca0a26f2 100644
--- a/rbot_session/src/runner.rs
+++ b/rbot_session/src/runner.rs
@@ -1,7 +1,11 @@
-// Copyright(c) 2022-2023. yasstake. All rights reserved.
+// Copyright(c) 2022-2024. yasstake. All rights reserved.
 
 use crossbeam_channel::Receiver;
-use pyo3::{pyclass, pymethods, types::IntoPyDict, Py, PyAny, PyErr, PyObject, Python};
+use pyo3::{
+    pyclass, pymethods,
+    types::{IntoPyDict, PyAnyMethods},
+    Bound, Py, PyAny, PyErr, PyObject, Python,
+};
 use rust_decimal::prelude::ToPrimitive;
 
 use super::{has_method, ExecuteMode, Session};
@@ -53,8 +57,6 @@ pub struct Runner {
     exchange_name: String,
     category: String,
     symbol: String,
-
-    warmup_count: i64,
 }
 
 #[pymethods]
@@ -88,7 +90,6 @@ impl Runner {
             exchange_name: "".to_string(),
             category: "".to_string(),
             symbol: "".to_string(),
-            warmup_count: 0,
         }
     }
 
@@ -111,16 +112,16 @@ impl Runner {
     #[pyo3(signature = (*, exchange, market, agent, start_time=0, end_time=0, execute_time=0, verbose=false, log_file=None))]
     pub fn back_test(
         &mut self,
-        exchange: PyObject,
-        market: PyObject,
-        agent: &PyAny,
+        exchange: &Bound<PyAny>,
+        market: &Bound<PyAny>,
+        agent: &Bound<PyAny>,
         start_time: MicroSec,
         end_time: MicroSec,
         execute_time: i64,
         verbose: bool,
         log_file: Option<String>,
     ) -> anyhow::Result<Py<Session>> {
-        self.update_market_info(&market)?;
+        self.update_market_info(market)?;
         self.update_agent_info(agent)?;
 
         self.execute_time = execute_time;
@@ -128,16 +129,16 @@ impl Runner {
         self.verbose = verbose;
         self.execute_mode = ExecuteMode::BackTest;
 
-        let receiver = Self::open_backtest_receiver(&market, start_time, end_time)?;
+        let receiver = Self::open_backtest_receiver(market, start_time, end_time)?;
         self.run(exchange, market, &receiver, agent, false, true, log_file)
     }
 
     #[pyo3(signature = (*, exchange, market, agent, log_memory=false, execute_time=0, verbose=false, log_file=None, client=false, no_download=false))]
     pub fn dry_run(
         &mut self,
-        exchange: PyObject,
-        market: PyObject,
-        agent: &PyAny,
+        exchange: &Bound<PyAny>,
+        market: &Bound<PyAny>,
+        agent: &Bound<PyAny>,
         log_memory: bool,
         execute_time: i64,
         verbose: bool,
@@ -165,7 +166,7 @@ impl Runner {
                 exchange, market, &receiver, agent, client, log_memory, log_file,
             )
         } else {
-            self.prepare_data(&exchange, &market, no_download)?;
+            self.prepare_data(exchange, &market, no_download)?;
 
             let receiver = MARKET_HUB.subscribe(&exchange_name, &category, &symbol, &agent_id)?;
 
@@ -178,15 +179,15 @@ impl Runner {
     #[pyo3(signature = (*,exchange,  market, agent, log_memory=false, execute_time=0, verbose=false, log_file=None, client=false, no_download=false))]
     pub fn real_run(
         &mut self,
-        exchange: PyObject,
-        market: PyObject,
-        agent: &PyAny,
+        exchange: &Bound<PyAny>,
+        market: &Bound<PyAny>,
+        agent: &Bound<PyAny>,
         log_memory: bool,
         execute_time: i64,
         verbose: bool,
         log_file: Option<String>,
         client: bool,
-        no_download: bool
+        no_download: bool,
     ) -> anyhow::Result<Py<Session>> {
         self.update_market_info(&market)?;
         self.update_agent_info(agent)?;
@@ -208,7 +209,7 @@ impl Runner {
                 exchange, market, &receiver, agent, client, log_memory, log_file,
             )
         } else {
-            self.prepare_data(&exchange, &market, no_download)?;
+            self.prepare_data(exchange, market, no_download)?;
             let receiver = MARKET_HUB.subscribe(&exchange_name, &category, &symbol, &agent_id)?;
 
             self.run(
@@ -242,13 +243,18 @@ impl Runner {
         "".to_string()
     }
 
-    pub fn prepare_data(&self, exchange: &PyObject, market: &PyObject, no_download: bool) -> anyhow::Result<()> {
+    pub fn prepare_data(
+        &self,
+        exchange: &Bound<PyAny>,
+        market: &Bound<PyAny>,
+        no_download: bool,
+    ) -> anyhow::Result<()> {
         // prepare market data
         // 1. start market & user stream
         // 2. download market data
         Python::with_gil(|py| {
             if self.execute_mode == ExecuteMode::Real || self.execute_mode == ExecuteMode::Dry {
-                market.call_method0(py, "start_market_stream")?;
+                market.call_method0("start_market_stream")?;
 
                 if self.verbose {
                     println!("--- start market stream ---");
@@ -257,7 +263,7 @@ impl Runner {
             }
 
             if self.execute_mode == ExecuteMode::Real {
-                exchange.call_method0(py, "start_user_stream")?;
+                exchange.call_method0("start_user_stream")?;
 
                 if self.verbose {
                     println!("--- start user stream ---");
@@ -265,7 +271,9 @@ impl Runner {
                 }
             }
 
-            if (! no_download) && self.execute_mode == ExecuteMode::Real || self.execute_mode == ExecuteMode::Dry {
+            if (!no_download) && self.execute_mode == ExecuteMode::Real
+                || self.execute_mode == ExecuteMode::Dry
+            {
                 if self.verbose {
                     println!("--- start download log data ---");
                     flush_log();
@@ -276,15 +284,17 @@ impl Runner {
                 } else {
                     vec![("verbose", false)]
                 };
-                let kwargs = kwargs.into_py_dict(py);
-
-                market.call_method(py, "download_latest", (), Some(kwargs))?;
+                
+                //let kwargs = kwargs.into_py_dict_bound(py);
+                let kwargs = kwargs.into_py_dict_bound(py);
+                market.call_method("download_latest", (), Some(&kwargs))?;
                 log::debug!("download_latest is done");
-                market.call_method(py, "download_gap", (), Some(kwargs))?;
+                market.call_method("download_gap", (), Some(&kwargs))?;
                 log::debug!("download_gap is done");
 
-                let kwargs = vec![("ndays", 1)];
-                market.call_method(py, "download_archive", (), Some(kwargs.into_py_dict(py)))?;
+                let kwargs = vec![("ndays", 10)];
+                let kwargs = kwargs.into_py_dict_bound(py);
+                market.call_method("download_archive", (), Some(&kwargs))?;
                 log::debug!("download_archive is done");
             }
 
@@ -294,22 +304,18 @@ impl Runner {
         Ok(())
     }
 
-    pub fn update_market_info(&mut self, market: &PyObject) -> Result<(), PyErr> {
-        let r = Python::with_gil(|py| {
-            let market_config = market.getattr(py, "config")?;
-            let market_config = market_config.extract::<MarketConfig>(py)?;
+    pub fn update_market_info(&mut self, market: &Bound<PyAny>) -> Result<(), PyErr> {
+        let market_config = market.getattr("config")?;
+        let market_config = market_config.extract::<MarketConfig>()?;
 
-            self.exchange_name = market_config.exchange_name;
-            self.category = market_config.trade_category;
-            self.symbol = market_config.trade_symbol;
+        self.exchange_name = market_config.exchange_name;
+        self.category = market_config.trade_category;
+        self.symbol = market_config.trade_symbol;
 
-            Ok(())
-        });
-
-        r
+        Ok(())
     }
 
-    pub fn update_agent_info(&mut self, agent: &PyAny) -> Result<(), PyErr> {
+    pub fn update_agent_info(&mut self, agent: &Bound<PyAny>) -> Result<(), PyErr> {
         self.has_on_init = has_method(agent, "on_init");
         self.has_on_clock = has_method(agent, "on_clock");
         self.has_on_tick = has_method(agent, "on_tick");
@@ -368,8 +374,8 @@ impl Runner {
 
     fn create_session(
         &self,
-        exchange: PyObject,
-        market: PyObject,
+        exchange: &Bound<PyAny>,
+        market: &Bound<PyAny>,
         client_mode: bool,
         log_memory: bool,
         log_file: Option<String>,
@@ -421,7 +427,7 @@ impl Runner {
     pub fn execute_message(
         &mut self,
         py_session: &Py<Session>,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         message: &MarketMessage,
         interval_sec: i64,
     ) -> anyhow::Result<()> {
@@ -430,121 +436,29 @@ impl Runner {
             if result.is_err() {
                 return Err(result.unwrap_err());
             }
-            self.loop_count += 1;
 
             Ok(())
         })?;
 
         Ok(())
     }
-    /*
-    pub async fn async_run(
-        &mut self,
-        exchange: PyObject,
-        market: PyObject,
-        receiver: impl Stream<Item = anyhow::Result<MarketMessage>>,
-        // receiver: &Receiver<MarketMessage>,
-        agent: &PyAny,
-        client_mode: bool,
-        log_memory: bool,
-        log_file: Option<String>,
-    ) -> anyhow::Result<Py<Session>> {
-        if self.verbose {
-            print!("--- run {:?} mode --- ", self.execute_mode);
-            print!("market: {}, ", self.exchange_name);
-            print!("agent_id: {}, ", self.agent_id);
-            print!("log_memory: {}, ", log_memory);
-            println!("duration: {}[sec], ", self.execute_time);
-
-
-            println!("---- warm up start ----");
-            flush_log();
-        }
-
-        let py_session = self.create_session(exchange, market, client_mode, log_memory, log_file);
-
-        self.call_agent_on_init(&agent, &py_session)?;
-        let interval_sec = self.get_clock_interval(&py_session)?;
-
-        let mut rec = Box::pin(receiver);
-
-        // warm up loop
-        let mut warm_up_loop: i64 = WARMUP_STEPS;
-        while let Some(message) = rec.next().await {
-            if self.verbose {
-                println!("warm up loop {:?}:{:?}", warm_up_loop, message);
-            }
-
-            let mut message = message?;
-            message.update_config(&self.config);
-
-            self.execute_message_update_session(&py_session, &message)?;
-
-            if let MarketMessage::Trade(_trade) = &message {
-                warm_up_loop = warm_up_loop - 1;
-                if warm_up_loop <= 0 {
-                    break;
-                }
-            }
-        }
-
-        if self.verbose {
-            println!("--- warm up loop end ---");
-            flush_log();
-        }
-
-        let mut remain_time: i64 = 0;
-
-        let loop_start_time = NOW();
-        while let Some(message) = rec.next().await {
-            let message = message?;
-            self.execute_message(&py_session, agent, &message, interval_sec)?;
-            self.loop_count += 1;
-
-            // break if the running time exceeceds the loop_duration
-            if self.start_timestamp == 0 {
-                self.start_timestamp = self.last_timestamp;
-                if self.verbose {
-                    println!("start_timestamp: {}", time_string(self.start_timestamp));
-                }
-            } else if 0 < self.execute_time {
-                remain_time = self.start_timestamp + SEC(self.execute_time) - self.last_timestamp;
-
-                if remain_time < 0 {
-                    log::debug!("remain_time out break");
-                    break;
-                }
-            }
-
-            // print progress if verbose flag is set.
-            if self.verbose {
-                self.print_progress(remain_time);
-            }
-        }
-        self.print_run_result(loop_start_time);
-
-        Ok(py_session)
-    }
-    */
 
     pub fn run(
         &mut self,
-        exchange: PyObject,
-        market: PyObject,
+        exchange: &Bound<PyAny>,
+        market: &Bound<PyAny>,
         //        receiver: impl Stream<Item = anyhow::Result<MarketMessage>>,
         receiver: &Receiver<MarketMessage>,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         client_mode: bool,
         log_memory: bool,
         log_file: Option<String>,
     ) -> anyhow::Result<Py<Session>> {
         self.start_timestamp = 0;
 
-        let production = Python::with_gil(|py| {
-            let exchange_status = exchange.getattr(py, "production").unwrap();
-            let status: bool = exchange_status.extract(py).unwrap();
-            status
-        });
+        let object = exchange.as_borrowed();
+        let exchange_status = object.getattr("production").unwrap();
+        let production: bool = exchange_status.extract().unwrap();
 
         if self.verbose {
             if production {
@@ -716,7 +630,7 @@ impl Runner {
     pub fn on_message(
         self: &mut Self,
         py: &Python,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         py_session: &Py<Session>,
         message: &MarketMessage,
         interval_sec: i64,
@@ -732,8 +646,7 @@ impl Runner {
 
                 if self.current_clock == 0 {
                     self.current_clock = new_clock;
-                }
-                else if self.current_clock < new_clock {
+                } else if self.current_clock < new_clock {
                     self.current_clock = new_clock;
 
                     self.call_agent_on_clock(py, agent, py_session, new_clock)?;
@@ -788,7 +701,7 @@ impl Runner {
             }
 
             if account_change && self.has_account_update {
-                self.call_agent_on_account_update_dummy(py, agent, py_session)?;
+                self.call_agent_on_account_update_backtest(py, agent, py_session)?;
             }
         }
 
@@ -806,19 +719,6 @@ impl Runner {
         session.update_psudo_account_by_order(order)
     }
 
-    /*
-    fn get_clock_interval_py(
-        self: &mut Self,
-        py: &Python,
-        py_session: &Py<Session>,
-    ) -> Result<i64, PyErr> {
-        let session = py_session.borrow_mut(*py);
-        let interval_sec = session.get_clock_interval_sec();
-
-        Ok(interval_sec)
-    }
-    */
-
     fn get_clock_interval(self: &mut Self, py_session: &Py<Session>) -> Result<i64, PyErr> {
         Python::with_gil(|py| {
             let session = py_session.borrow_mut(py);
@@ -828,24 +728,9 @@ impl Runner {
         })
     }
 
-    /*
-    fn call_agent_on_init_py(
-        self: &mut Self,
-        py: &Python,
-        agent: &PyAny,
-        py_session: &Py<Session>,
-    ) -> Result<(), PyErr> {
-        let session = py_session.borrow_mut(*py);
-
-        agent.call_method1("on_init", (session,))?;
-
-        Ok(())
-    }
-    */
-
     fn call_agent_on_init(
         self: &mut Self,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         py_session: &Py<Session>,
     ) -> anyhow::Result<()> {
         if !self.has_on_init {
@@ -863,7 +748,7 @@ impl Runner {
     fn call_agent_on_tick(
         self: &mut Self,
         py: &Python,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         py_session: &Py<Session>,
         trade: &Trade,
     ) -> Result<(), PyErr> {
@@ -879,7 +764,7 @@ impl Runner {
     fn call_agent_on_update(
         self: &mut Self,
         py: &Python,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         py_session: &Py<Session>,
         order: &Order,
     ) -> Result<(), PyErr> {
@@ -895,7 +780,7 @@ impl Runner {
     fn call_agent_on_clock(
         self: &mut Self,
         py: &Python,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         py_session: &Py<Session>,
         clock: MicroSec,
     ) -> Result<(), PyErr> {
@@ -912,7 +797,7 @@ impl Runner {
     fn call_agent_on_account_update(
         self: &mut Self,
         py: &Python,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         py_session: &Py<Session>,
         account: &AccountCoins,
     ) -> Result<(), PyErr> {
@@ -934,10 +819,10 @@ impl Runner {
     }
 
     /// When backtest, the account infomation is from session.
-    fn call_agent_on_account_update_dummy(
+    fn call_agent_on_account_update_backtest(
         self: &mut Self,
         py: &Python,
-        agent: &PyAny,
+        agent: &Bound<PyAny>,
         py_session: &Py<Session>,
     ) -> Result<(), PyErr> {
         let mut session = py_session.borrow_mut(*py);
@@ -959,47 +844,13 @@ impl Runner {
     }
 
     pub fn open_backtest_receiver(
-        market: &PyObject,
+        market: &Bound<PyAny>,
         time_from: MicroSec,
         time_to: MicroSec,
     ) -> anyhow::Result<Receiver<MarketMessage>> {
-        let market_stream = Python::with_gil(|py| {
-            let stream = market.call_method1(py, "open_backtest_channel", (time_from, time_to))?;
-            let stream = stream.extract::<MarketStream>(py)?;
-
-            Ok::<MarketStream, anyhow::Error>(stream)
-        })?;
-
-        Ok(market_stream.reciver)
-    }
-
-    pub async fn open_backtest_stream(
-        market: &PyObject,
-        time_from: MicroSec,
-        time_to: MicroSec,
-    ) -> impl Stream<Item = anyhow::Result<MarketMessage>> {
-        let market_stream = Python::with_gil(|py| {
-            let stream = market.call_method1(py, "open_backtest_channel", (time_from, time_to));
-
-            if stream.is_ok() {
-                let stream = stream.unwrap();
-                let stream = stream.extract::<MarketStream>(py).unwrap();
-                stream
-            } else {
-                let err = stream.unwrap_err();
-                log::error!("Error in open_backtest_channel: {:?}", err);
-                panic!("Error in open_backtest_channel: {:?}", err);
-            }
-        });
-
-        let market_stream = market_stream.reciver;
+        let stream = market.call_method1("open_backtest_channel", (time_from, time_to))?;
+        let stream = stream.extract::<MarketStream>()?;
 
-        stream! {
-            loop {
-                let message = market_stream.recv()?;
-
-                yield Ok(message);
-            }
-        }
+        Ok(stream.reciver)
     }
 }
diff --git a/rbot_session/src/session.rs b/rbot_session/src/session.rs
index baed16a5..8ca57f52 100644
--- a/rbot_session/src/session.rs
+++ b/rbot_session/src/session.rs
@@ -3,12 +3,10 @@
 use std::sync::Mutex;
 use std::{collections::VecDeque, sync::Arc};
 
-use polars::lazy::dsl::last;
-use pyo3::{pyclass, pymethods, PyAny, PyObject, Python};
+use pyo3::{pyclass, pymethods, PyAny, Python};
 
 use pyo3_polars::PyDataFrame;
 use rbot_lib::common::FLOOR_SEC;
-use rbot_lib::db::ohlcv_start;
 use rbot_server::get_rest_orderbook;
 use rust_decimal::{prelude::ToPrimitive, Decimal};
 use rust_decimal_macros::dec;
@@ -70,7 +68,7 @@ pub struct Session {
     sell_orders: OrderList,
     real_account: AccountCoins,
     psudo_account: AccountCoins,
-    exchange: PyObject,
+    exchange: Py<PyAny>,
     current_timestamp: MicroSec,
     current_clock_time: MicroSec,
     pub session_name: String,
@@ -112,8 +110,8 @@ impl Session {
     #[new]
     #[pyo3(signature = (exchange, market, execute_mode, client_mode=false, session_name=None, log_memory=true))]
     pub fn new(
-        exchange: PyObject,
-        market: PyObject,
+        exchange: &Bound<PyAny>,
+        market: &Bound<PyAny>,
         execute_mode: ExecuteMode,
         client_mode: bool,
         session_name: Option<&str>,
@@ -135,19 +133,12 @@ impl Session {
             }
         };
 
-        let production = Python::with_gil(|py| {
-            let production = exchange.getattr(py, "production").unwrap();
-            let production: bool = production.extract(py).unwrap();
+        let obj = exchange.as_borrowed();
+            let production = obj.getattr("production").unwrap();
+            let production: bool = production.extract().unwrap();
 
-            production
-        });
-
-        let config = Python::with_gil(|py| {
-            let config = market.getattr(py, "config").unwrap();
-            let config: MarketConfig = config.extract(py).unwrap();
-
-            config
-        });
+        let config = market.getattr("config").unwrap();
+        let config: MarketConfig = config.extract().unwrap();
 
         let category = config.trade_category.clone();
         let now_time = NOW() / 1_000_000;
@@ -160,7 +151,7 @@ impl Session {
             sell_orders: OrderList::new(OrderSide::Sell),
             real_account: AccountCoins::default(),
             psudo_account: AccountCoins::default(),
-            exchange,
+            exchange: exchange.extract().unwrap(),
             current_timestamp: 0,
             current_clock_time: 0,
             session_name,
@@ -257,7 +248,7 @@ impl Session {
             ohlcv
         };
 
-        Ok((df))
+        Ok(df)
     }
 
     pub fn vap(


Test patch:
diff --git a/rbot/test/bybit_test/nullagent.py b/rbot/test/bybit_test/nullagent.py
index 79200c92..b1a7052e 100644
--- a/rbot/test/bybit_test/nullagent.py
+++ b/rbot/test/bybit_test/nullagent.py
@@ -40,7 +40,8 @@ def on_clock(self, session, clock):
         """
         # 現在の時刻をプリントします。
 #        print("on_clock: ", clock, ": ", time_string(clock))
-        session.market_order("Buy", 0.001)
+        #session.market_order("Buy", 0.001)
+        pass
     
     def on_update(self, session, updated_order):
         """自分の注文状態が変化した場合に呼び出される処理です。
@@ -51,6 +52,7 @@ def on_update(self, session, updated_order):
         """
         # 注文状態が変化した注文情報をプリントします。オーダーを発行しない限り呼び出されません。
 #        print("on_update", updated_order)    
+        pass
 
     
 from rbot import Bybit, BybitConfig
diff --git a/rbot/test/bybit_test/runner.py b/rbot/test/bybit_test/runner.py
new file mode 100644
index 00000000..3fcacab2
--- /dev/null
+++ b/rbot/test/bybit_test/runner.py
@@ -0,0 +1,17 @@
+from rbot import Runner, BybitConfig, Bybit
+
+class Agent:
+    def on_init(self):
+        print("call")
+        pass
+
+agent = Agent()
+
+
+runner = Runner()
+
+bybit = Bybit(True)
+market = bybit.open_market(BybitConfig.BTCUSDT)
+
+#runner.runrun()
+runner.runrun(bybit, market, agent)