progress
This commit is contained in:
parent
6dd0f97d74
commit
b196863a34
173
.vscode/launch.json
vendored
173
.vscode/launch.json
vendored
@ -35,182 +35,61 @@
|
||||
"--pair=PAIR-ADA-USDT:BNBSPOT,PAIR-SOL-USDT:BNBSPOT",
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "-------- OLS --------",
|
||||
},
|
||||
{
|
||||
"name": "CRYPTO OLS (rolling)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/ols.cfg",
|
||||
"--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=20250605",
|
||||
"--result_db=${workspaceFolder}/research/results/crypto/%T.ols.ADA-SOL.20250605.crypto_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "CRYPTO OLS (optimized)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/ols-opt.cfg",
|
||||
"--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=20250605",
|
||||
"--result_db=${workspaceFolder}/research/results/crypto/%T.ols-opt.ADA-SOL.20250605.crypto_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
// {
|
||||
// "name": "CRYPTO OLS (expanding)",
|
||||
// "type": "debugpy",
|
||||
// "request": "launch",
|
||||
// "python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
// "program": "${workspaceFolder}/research/backtest.py",
|
||||
// "args": [
|
||||
// "--config=${workspaceFolder}/configuration/ols-exp.cfg",
|
||||
// "--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
// "--date_pattern=20250605",
|
||||
// "--result_db=${workspaceFolder}/research/results/crypto/%T.ols-exp.ADA-SOL.20250605.crypto_results.db",
|
||||
// ],
|
||||
// "env": {
|
||||
// "PYTHONPATH": "${workspaceFolder}/lib"
|
||||
// },
|
||||
// "console": "integratedTerminal"
|
||||
// },
|
||||
{
|
||||
"name": "EQUITY OLS (rolling)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/ols.cfg",
|
||||
"--instruments=COIN:EQUITY:ALPACA,MSTR:EQUITY:ALPACA",
|
||||
"--date_pattern=20250605",
|
||||
"--result_db=${workspaceFolder}/research/results/equity/%T.ols.COIN-MSTR.20250605.equity_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "EQUITY-CRYPTO OLS (rolling)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/ols.cfg",
|
||||
"--instruments=COIN:EQUITY:ALPACA,BTC-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=20250605",
|
||||
"--result_db=${workspaceFolder}/research/results/intermarket/%T.ols.COIN-BTC.20250605.equity_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "-------- VECM --------",
|
||||
},
|
||||
{
|
||||
"name": "CRYPTO VECM (rolling)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/vecm.cfg",
|
||||
"--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=20250605",
|
||||
"--result_db=${workspaceFolder}/research/results/crypto/%T.vecm.ADA-SOL.20250605.crypto_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "CRYPTO VECM (optimized)",
|
||||
"name": "CRYPTO VECM BACKTEST (optimized)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/vecm-opt.cfg",
|
||||
"--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=20250605",
|
||||
"--instruments=CRYPTO:BNBSPOT:PAIR-ADA-USDT,CRYPTO:BNBSPOT:PAIR-SOL-USDT",
|
||||
"--date_pattern=20250910",
|
||||
"--result_db=${workspaceFolder}/research/results/crypto/%T.vecm-opt.ADA-SOL.20250605.crypto_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
"PYTHONPATH": "${workspaceFolder}/..",
|
||||
"CONFIG_SERVICE": "cloud16.cvtt.vpn:6789"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
// {
|
||||
// "name": "CRYPTO VECM (expanding)",
|
||||
// "name": "EQUITY VECM (rolling)",
|
||||
// "type": "debugpy",
|
||||
// "request": "launch",
|
||||
// "python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
// "program": "${workspaceFolder}/research/backtest.py",
|
||||
// "args": [
|
||||
// "--config=${workspaceFolder}/configuration/vecm-exp.cfg",
|
||||
// "--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
// "--config=${workspaceFolder}/configuration/vecm.cfg",
|
||||
// "--instruments=COIN:EQUITY:ALPACA,MSTR:EQUITY:ALPACA",
|
||||
// "--date_pattern=20250605",
|
||||
// "--result_db=${workspaceFolder}/research/results/crypto/%T.vecm-exp.ADA-SOL.20250605.crypto_results.db",
|
||||
// "--result_db=${workspaceFolder}/research/results/equity/%T.vecm.COIN-MSTR.20250605.equity_results.db",
|
||||
// ],
|
||||
// "env": {
|
||||
// "PYTHONPATH": "${workspaceFolder}/lib"
|
||||
// },
|
||||
// "console": "integratedTerminal"
|
||||
// },
|
||||
// {
|
||||
// "name": "EQUITY-CRYPTO VECM (rolling)",
|
||||
// "type": "debugpy",
|
||||
// "request": "launch",
|
||||
// "python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
// "program": "${workspaceFolder}/research/backtest.py",
|
||||
// "args": [
|
||||
// "--config=${workspaceFolder}/configuration/vecm.cfg",
|
||||
// "--instruments=COIN:EQUITY:ALPACA,BTC-USDT:CRYPTO:BNBSPOT",
|
||||
// "--date_pattern=20250605",
|
||||
// "--result_db=${workspaceFolder}/research/results/intermarket/%T.vecm.COIN-BTC.20250601.equity_results.db",
|
||||
// ],
|
||||
// "env": {
|
||||
// "PYTHONPATH": "${workspaceFolder}/lib"
|
||||
// },
|
||||
// "console": "integratedTerminal"
|
||||
// },
|
||||
{
|
||||
"name": "EQUITY VECM (rolling)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/vecm.cfg",
|
||||
"--instruments=COIN:EQUITY:ALPACA,MSTR:EQUITY:ALPACA",
|
||||
"--date_pattern=20250605",
|
||||
"--result_db=${workspaceFolder}/research/results/equity/%T.vecm.COIN-MSTR.20250605.equity_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "EQUITY-CRYPTO VECM (rolling)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/vecm.cfg",
|
||||
"--instruments=COIN:EQUITY:ALPACA,BTC-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=20250605",
|
||||
"--result_db=${workspaceFolder}/research/results/intermarket/%T.vecm.COIN-BTC.20250601.equity_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "-------- B a t c h e s --------",
|
||||
},
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -6,7 +6,7 @@
|
||||
],
|
||||
"python.testing.cwd": "${workspaceFolder}",
|
||||
"python.testing.autoTestDiscoverOnSaveEnabled": true,
|
||||
"python.defaultInterpreterPath": "/usr/bin/python3",
|
||||
"python.defaultInterpreterPath": "/home/oleg/.pyenv/python3.12-venv/bin/python3",
|
||||
"python.testing.pytestPath": "python3",
|
||||
"python.analysis.extraPaths": [
|
||||
"${workspaceFolder}",
|
||||
|
||||
@ -90,8 +90,7 @@ class PairsTrader(NamedObject):
|
||||
strategy_config = self.config_.get_subconfig("strategy_config", Config({}))
|
||||
self.live_strategy_ = PtLiveStrategy(
|
||||
config=strategy_config,
|
||||
instruments=self.instruments_,
|
||||
pairs_trader=self
|
||||
pairs_trader=self,
|
||||
)
|
||||
Log.info(f"{self.fname()} Strategy created: {self.live_strategy_}")
|
||||
|
||||
@ -133,8 +132,11 @@ class PairsTrader(NamedObject):
|
||||
await self.live_strategy_.on_mkt_data_hist_snapshot(hist_aggr=history)
|
||||
|
||||
async def _on_api_request(self, request: web.Request) -> web.Response:
|
||||
# TODO choose pair
|
||||
# TODO confirm chosen pair (after selection is implemented)
|
||||
return web.Response() # TODO API request handler implementation
|
||||
|
||||
|
||||
async def run(self) -> None:
|
||||
Log.info(f"{self.fname()} ...")
|
||||
pass
|
||||
|
||||
@ -23,8 +23,8 @@
|
||||
"dis-equilibrium_open_trshld": 2.0,
|
||||
"dis-equilibrium_close_trshld": 0.5,
|
||||
"training_size": 120,
|
||||
"model_class": "pt_strategy.models.OLSModel",
|
||||
"model_data_policy_class": "pt_strategy.model_data_policy.ExpandingWindowDataPolicy",
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.OLSModel",
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.ExpandingWindowDataPolicy",
|
||||
|
||||
# ====== Stop Conditions ======
|
||||
"stop_close_conditions": {
|
||||
@ -22,11 +22,11 @@
|
||||
},
|
||||
"dis-equilibrium_open_trshld": 1.75,
|
||||
"dis-equilibrium_close_trshld": 0.9,
|
||||
"model_class": "pt_strategy.models.OLSModel",
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.OLSModel",
|
||||
|
||||
# "model_data_policy_class": "pt_strategy.model_data_policy.EGOptimizedWndDataPolicy",
|
||||
# "model_data_policy_class": "pt_strategy.model_data_policy.ADFOptimizedWndDataPolicy",
|
||||
"model_data_policy_class": "pt_strategy.model_data_policy.JohansenOptdWndDataPolicy",
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.EGOptimizedWndDataPolicy",
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.ADFOptimizedWndDataPolicy",
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.JohansenOptdWndDataPolicy",
|
||||
"min_training_size": 60,
|
||||
"max_training_size": 150,
|
||||
|
||||
@ -22,11 +22,11 @@
|
||||
},
|
||||
"dis-equilibrium_open_trshld": 1.75,
|
||||
"dis-equilibrium_close_trshld": 0.9,
|
||||
"model_class": "pt_strategy.models.OLSModel",
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.OLSModel",
|
||||
|
||||
"training_size": 120,
|
||||
"model_data_policy_class": "pt_strategy.model_data_policy.RollingWindowDataPolicy",
|
||||
# "model_data_policy_class": "pt_strategy.model_data_policy.OptimizedWindowDataPolicy",
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.RollingWindowDataPolicy",
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.OptimizedWindowDataPolicy",
|
||||
# "min_training_size": 60,
|
||||
# "max_training_size": 150,
|
||||
|
||||
@ -23,11 +23,11 @@
|
||||
},
|
||||
"dis-equilibrium_open_trshld": 1.75,
|
||||
"dis-equilibrium_close_trshld": 1.0,
|
||||
"model_class": "pt_strategy.models.VECMModel",
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.VECMModel",
|
||||
|
||||
"training_size": 120,
|
||||
"model_data_policy_class": "pt_strategy.model_data_policy.RollingWindowDataPolicy",
|
||||
# "model_data_policy_class": "pt_strategy.model_data_policy.OptimizedWindowDataPolicy",
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.RollingWindowDataPolicy",
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.OptimizedWindowDataPolicy",
|
||||
# "min_training_size": 60,
|
||||
# "max_training_size": 150,
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
{
|
||||
"refdata": {
|
||||
"assets": @inc=http://@env{CONFIG_SERVICE}/refdata/assets
|
||||
, "instruments": @inc=http://@env{CONFIG_SERVICE}/refdata/instruments
|
||||
, "exchange_instruments": @inc=http://@env{CONFIG_SERVICE}/refdata/exchange_instruments
|
||||
, "dynamic_instrument_exchanges": ["ALPACA"]
|
||||
, "exchanges": @inc=http://@env{CONFIG_SERVICE}/refdata/exchanges
|
||||
},
|
||||
"market_data_loading": {
|
||||
"CRYPTO": {
|
||||
"data_directory": "./data/crypto",
|
||||
@ -24,11 +31,11 @@
|
||||
"dis-equilibrium_open_trshld": 1.75,
|
||||
"dis-equilibrium_close_trshld": 1.0,
|
||||
|
||||
"model_class": "pt_strategy.models.VECMModel",
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.VECMModel",
|
||||
|
||||
# "training_size": 120,
|
||||
# "model_data_policy_class": "pt_strategy.model_data_policy.RollingWindowDataPolicy",
|
||||
"model_data_policy_class": "pt_strategy.model_data_policy.ADFOptimizedWndDataPolicy",
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.RollingWindowDataPolicy",
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.ADFOptimizedWndDataPolicy",
|
||||
"min_training_size": 60,
|
||||
"max_training_size": 150,
|
||||
|
||||
|
||||
@ -10,19 +10,23 @@ import pandas as pd
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.app import App
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.settings.cvtt_types import IntervalSecT
|
||||
from cvttpy_tools.timeutils import SecPerHour
|
||||
from cvttpy_tools.settings.cvtt_types import BookIdT, IntervalSecT
|
||||
from cvttpy_tools.timeutils import SecPerHour, current_nanoseconds
|
||||
from cvttpy_tools.logger import Log
|
||||
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
from cvttpy_trading.trading.mkt_data.md_summary import MdTradesAggregate
|
||||
from cvttpy_trading.trading.trading_instructions import TradingInstructions
|
||||
from cvttpy_trading.trading.accounting.cvtt_book import CvttBook
|
||||
from cvttpy_trading.trading.trading_instructions import TargetPositionSignal
|
||||
|
||||
# ---
|
||||
from pairs_trading.lib.pt_strategy.model_data_policy import ModelDataPolicy
|
||||
from pairs_trading.lib.pt_strategy.pt_model import Prediction
|
||||
from pairs_trading.lib.pt_strategy.trading_pair import PairState, TradingPair
|
||||
from pairs_trading.lib.pt_strategy.trading_pair import LiveTradingPair
|
||||
from pairs_trading.apps.pairs_trader import PairsTrader
|
||||
from pairs_trading.lib.pt_strategy.pt_market_data import LiveMarketData
|
||||
|
||||
|
||||
"""
|
||||
@ -51,7 +55,7 @@ class PtLiveStrategy(NamedObject):
|
||||
open_threshold_: float
|
||||
close_threshold_: float
|
||||
|
||||
trading_pair_: TradingPair
|
||||
trading_pair_: LiveTradingPair
|
||||
model_data_policy_: ModelDataPolicy
|
||||
pairs_trader_: PairsTrader
|
||||
|
||||
@ -60,28 +64,29 @@ class PtLiveStrategy(NamedObject):
|
||||
# for presentation: history of prediction values and trading signals
|
||||
predictions_df_: pd.DataFrame
|
||||
trading_signals_df_: pd.DataFrame
|
||||
# book_: CvttBook
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
instruments: List[ExchangeInstrument],
|
||||
pairs_trader: PairsTrader,
|
||||
):
|
||||
|
||||
self.trading_pair_ = TradingPair(
|
||||
config=cast(Dict[str, Any], config.data()),
|
||||
instruments=[{"instrument_id": ei.instrument_id()} for ei in instruments],
|
||||
self.pairs_trader_ = pairs_trader
|
||||
self.trading_pair_ = LiveTradingPair(
|
||||
config=config,
|
||||
instruments=self.pairs_trader_.instruments_,
|
||||
)
|
||||
self.predictions_df_ = pd.DataFrame()
|
||||
self.trading_signals_df_ = pd.DataFrame()
|
||||
self.pairs_trader_ = pairs_trader
|
||||
# self.book_ = book
|
||||
|
||||
import copy
|
||||
|
||||
# modified config must be passed to PtMarketData
|
||||
self.config_ = Config(json_src=copy.deepcopy(config.data()))
|
||||
|
||||
self.instruments_ = instruments
|
||||
self.instruments_ = self.pairs_trader_.instruments_
|
||||
|
||||
App.instance().add_call(
|
||||
stage=App.Stage.Config, func=self._on_config(), can_run_now=True
|
||||
@ -95,9 +100,6 @@ class PtLiveStrategy(NamedObject):
|
||||
|
||||
await self.pairs_trader_.subscribe_md()
|
||||
|
||||
self.model_data_policy_ = ModelDataPolicy.create(
|
||||
self.config_, is_real_time=True, pair=self.trading_pair_
|
||||
)
|
||||
self.open_threshold_ = self.config_.get_value(
|
||||
"dis-equilibrium_open_trshld", 0.0
|
||||
)
|
||||
@ -121,13 +123,22 @@ class PtLiveStrategy(NamedObject):
|
||||
if not self._is_md_actual(hist_aggr=hist_aggr):
|
||||
return
|
||||
|
||||
market_data_df: Optional[pd.DataFrame] = self._create_md_pdf(hist_aggr=hist_aggr)
|
||||
if market_data_df is None:
|
||||
market_data_df: pd.DataFrame = self._create_md_df(hist_aggr=hist_aggr)
|
||||
if len(market_data_df) == 0:
|
||||
Log.warning(f"{self.fname()} Unable to create market data df")
|
||||
return
|
||||
|
||||
self.trading_pair_.market_data_ = market_data_df
|
||||
self.model_data_policy_.advance()
|
||||
self.model_data_policy_ = ModelDataPolicy.create(
|
||||
self.config_,
|
||||
is_real_time=True,
|
||||
pair=self.trading_pair_,
|
||||
mkt_data=market_data_df,
|
||||
)
|
||||
assert (
|
||||
self.model_data_policy_ is not None
|
||||
), f"{self.fname()}: Unable to create ModelDataPolicy"
|
||||
|
||||
prediction = self.trading_pair_.run(
|
||||
market_data_df, self.model_data_policy_.advance()
|
||||
)
|
||||
@ -135,7 +146,7 @@ class PtLiveStrategy(NamedObject):
|
||||
[self.predictions_df_, prediction.to_df()], ignore_index=True
|
||||
)
|
||||
|
||||
trading_instructions: Optional[TradingInstructions] = (
|
||||
trading_instructions: List[TradingInstructions] = (
|
||||
self._create_trading_instructions(
|
||||
prediction=prediction, last_row=market_data_df.iloc[-1]
|
||||
)
|
||||
@ -146,8 +157,72 @@ class PtLiveStrategy(NamedObject):
|
||||
def _is_md_actual(self, hist_aggr: List[MdTradesAggregate]) -> bool:
|
||||
return False # URGENT _is_md_actual
|
||||
|
||||
def _create_md_pdf(self, hist_aggr: List[MdTradesAggregate]) -> Optional[pd.DataFrame]:
|
||||
return None # URGENT _create_md_pdf
|
||||
def _create_md_df(self, hist_aggr: List[MdTradesAggregate]) -> pd.DataFrame:
|
||||
"""
|
||||
tstamp time_ns symbol open high low close volume num_trades vwap
|
||||
0 2025-09-10 11:30:00 1757503800000000000 ADA-USDT 0.8750 0.8750 0.8743 0.8743 50710.500 0 0.874489
|
||||
1 2025-09-10 11:30:00 1757503800000000000 SOL-USDT 219.9700 219.9800 219.6600 219.7000 2648.582 0 219.787847
|
||||
2 2025-09-10 11:31:00 1757503860000000000 SOL-USDT 219.7000 219.7300 219.6200 219.6200 1134.886 0 219.663460
|
||||
3 2025-09-10 11:31:00 1757503860000000000 ADA-USDT 0.8743 0.8745 0.8741 0.8741 10696.400 0 0.874234
|
||||
4 2025-09-10 11:32:00 1757503920000000000 ADA-USDT 0.8742 0.8742 0.8739 0.8740 18546.900 0 0.874037
|
||||
"""
|
||||
|
||||
rows: List[Dict[str, Any]] = []
|
||||
|
||||
for aggr in hist_aggr:
|
||||
exch_inst = aggr.exch_inst_
|
||||
|
||||
rows.append(
|
||||
{
|
||||
# convert nanoseconds → tz-aware pandas timestamp
|
||||
"tstamp": pd.to_datetime(aggr.time_ns_, unit="ns", utc=True),
|
||||
"time_ns": aggr.time_ns_,
|
||||
"symbol": exch_inst.instrument_id().split("-", 1)[1],
|
||||
"exchange_id": exch_inst.exchange_id_,
|
||||
"instrument_id": exch_inst.instrument_id(),
|
||||
"open": exch_inst.get_price(aggr.open_),
|
||||
"high": exch_inst.get_price(aggr.high_),
|
||||
"low": exch_inst.get_price(aggr.low_),
|
||||
"close": exch_inst.get_price(aggr.close_),
|
||||
"volume": exch_inst.get_quantity(aggr.volume_),
|
||||
"num_trades": aggr.num_trades_,
|
||||
"vwap": exch_inst.get_price(aggr.vwap_),
|
||||
}
|
||||
)
|
||||
|
||||
source_md_df = pd.DataFrame(
|
||||
rows,
|
||||
columns=[
|
||||
"tstamp",
|
||||
"time_ns",
|
||||
"symbol",
|
||||
"exchange_id",
|
||||
"instrument_id",
|
||||
"open",
|
||||
"high",
|
||||
"low",
|
||||
"close",
|
||||
"volume",
|
||||
"num_trades",
|
||||
"vwap",
|
||||
],
|
||||
)
|
||||
|
||||
# automatic sorting
|
||||
source_md_df.sort_values(
|
||||
by=["time_ns", "symbol"],
|
||||
ascending=True,
|
||||
inplace=True,
|
||||
kind="mergesort", # stable sort
|
||||
)
|
||||
|
||||
source_md_df.reset_index(drop=True, inplace=True)
|
||||
|
||||
pt_mkt_data = LiveMarketData(config=self.config_, instruments=self.instruments_)
|
||||
pt_mkt_data.origin_mkt_data_df_ = source_md_df
|
||||
pt_mkt_data.set_market_data()
|
||||
|
||||
return pt_mkt_data.market_data_df_
|
||||
|
||||
def interval_sec(self) -> IntervalSecT:
|
||||
return self.interval_sec_
|
||||
@ -156,271 +231,110 @@ class PtLiveStrategy(NamedObject):
|
||||
return self.history_depth_sec_
|
||||
|
||||
async def _send_trading_instructions(
|
||||
self, trading_instructions: TradingInstructions
|
||||
self, trading_instructions: List[TradingInstructions]
|
||||
) -> None:
|
||||
await self.pairs_trader_.ti_sender_.send_trading_instructions(trading_instructions)
|
||||
pass # URGENT _send_trading_instructions
|
||||
for ti in trading_instructions:
|
||||
Log.info(f"{self.fname()} Sending trading instructions {ti}")
|
||||
await self.pairs_trader_.ti_sender_.send_trading_instructions(ti)
|
||||
|
||||
def _create_trading_instructions(
|
||||
self, prediction: Prediction, last_row: pd.Series
|
||||
) -> Optional[TradingInstructions]:
|
||||
) -> List[TradingInstructions]:
|
||||
trd_instructions: List[TradingInstructions] = []
|
||||
pair = self.trading_pair_
|
||||
res: Optional[TradingInstructions]
|
||||
|
||||
scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
abs_scaled_disequilibrium = abs(scaled_disequilibrium)
|
||||
|
||||
if pair.is_closed():
|
||||
if abs_scaled_disequilibrium >= self.open_threshold_:
|
||||
trd_instructions = self._create_open_trade_instructions(
|
||||
pair, row=last_row, prediction=prediction
|
||||
)
|
||||
elif pair.is_open():
|
||||
if abs_scaled_disequilibrium <= self.close_threshold_:
|
||||
|
||||
elif abs_scaled_disequilibrium <= self.close_threshold_ or pair.to_stop_close_conditions(predicted_row=last_row):
|
||||
trd_instructions = self._create_close_trade_instructions(
|
||||
pair, row=last_row # , prediction=prediction
|
||||
)
|
||||
elif pair.to_stop_close_conditions(predicted_row=last_row):
|
||||
trd_instructions = self._create_close_trade_instructions(
|
||||
pair, row=last_row
|
||||
)
|
||||
|
||||
|
||||
return trd_instructions
|
||||
|
||||
def _strength(self, scaled_disequilibrium) -> float:
|
||||
# URGENT PtLiveStrategy._strength()
|
||||
return 1.0
|
||||
|
||||
def _create_open_trade_instructions(
|
||||
self, pair: TradingPair, row: pd.Series, prediction: Prediction
|
||||
) -> Optional[TradingInstructions]:
|
||||
ti: Optional[TradingInstructions] = None
|
||||
self, pair: LiveTradingPair, row: pd.Series, prediction: Prediction
|
||||
) -> List[TradingInstructions]:
|
||||
diseqlbrm = prediction.disequilibrium_
|
||||
scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
# URGENT _create_open_trade_instructions
|
||||
if diseqlbrm > 0:
|
||||
side_a = -1
|
||||
side_b = 1
|
||||
else:
|
||||
side_a = 1
|
||||
side_b = -1
|
||||
|
||||
# if scaled_disequilibrium > 0:
|
||||
# side_a = "SELL"
|
||||
# trd_inst_a = TradingInstruction(
|
||||
# type_=TradingInstructionType.TARGET_POSITION,
|
||||
# exch_instr_=pair.get_instrument_a(),
|
||||
# specifics_={"side": "SELL", "strength": -1},
|
||||
# )
|
||||
# side_b = "BUY"
|
||||
# else:
|
||||
# side_a = "BUY"
|
||||
# side_b = "SELL"
|
||||
ti_a: Optional[TradingInstructions] = TradingInstructions(
|
||||
book=self.pairs_trader_.book_id_,
|
||||
strategy_id=self.__class__.__name__,
|
||||
ti_type=TradingInstructions.Type.TARGET_POSITION,
|
||||
issued_ts_ns=current_nanoseconds(),
|
||||
data=TargetPositionSignal(
|
||||
strength=side_a * self._strength(scaled_disequilibrium),
|
||||
base_asset=pair.get_instrument_a().base_asset_id_,
|
||||
quote_asset=pair.get_instrument_a().quote_asset_id_,
|
||||
user_data={}
|
||||
),
|
||||
)
|
||||
if not ti_a:
|
||||
return []
|
||||
ti_b: Optional[TradingInstructions] = TradingInstructions(
|
||||
book=self.pairs_trader_.book_id_,
|
||||
strategy_id=self.__class__.__name__,
|
||||
ti_type=TradingInstructions.Type.TARGET_POSITION,
|
||||
issued_ts_ns=current_nanoseconds(),
|
||||
data=TargetPositionSignal(
|
||||
strength=side_b * self._strength(scaled_disequilibrium),
|
||||
base_asset=pair.get_instrument_b().base_asset_id_,
|
||||
quote_asset=pair.get_instrument_b().quote_asset_id_,
|
||||
user_data={}
|
||||
),
|
||||
)
|
||||
if not ti_b:
|
||||
return []
|
||||
return [ti_a, ti_b]
|
||||
|
||||
# colname_a, colname_b = pair.exec_prices_colnames()
|
||||
# px_a = row[f"{colname_a}"]
|
||||
# px_b = row[f"{colname_b}"]
|
||||
|
||||
# tstamp = row["tstamp"]
|
||||
# diseqlbrm = prediction.disequilibrium_
|
||||
# scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
|
||||
# df = self._trades_df()
|
||||
|
||||
# # save closing sides
|
||||
# pair.user_data_["open_side_a"] = side_a # used in oustanding positions
|
||||
# pair.user_data_["open_side_b"] = side_b
|
||||
# pair.user_data_["open_px_a"] = px_a
|
||||
# pair.user_data_["open_px_b"] = px_b
|
||||
# pair.user_data_["open_tstamp"] = tstamp
|
||||
|
||||
# pair.user_data_["close_side_a"] = side_b # used for closing trades
|
||||
# pair.user_data_["close_side_b"] = side_a
|
||||
|
||||
# # create opening trades
|
||||
# df.loc[len(df)] = {
|
||||
# "time": tstamp,
|
||||
# "symbol": pair.symbol_a_,
|
||||
# "side": side_a,
|
||||
# "action": "OPEN",
|
||||
# "price": px_a,
|
||||
# "disequilibrium": diseqlbrm,
|
||||
# "signed_scaled_disequilibrium": scaled_disequilibrium,
|
||||
# "scaled_disequilibrium": abs(scaled_disequilibrium),
|
||||
# # "pair": pair,
|
||||
# }
|
||||
# df.loc[len(df)] = {
|
||||
# "time": tstamp,
|
||||
# "symbol": pair.symbol_b_,
|
||||
# "side": side_b,
|
||||
# "action": "OPEN",
|
||||
# "price": px_b,
|
||||
# "disequilibrium": diseqlbrm,
|
||||
# "scaled_disequilibrium": abs(scaled_disequilibrium),
|
||||
# "signed_scaled_disequilibrium": scaled_disequilibrium,
|
||||
# # "pair": pair,
|
||||
# }
|
||||
# ti: List[TradingInstruction] = self._create_trading_instructions(
|
||||
# prediction=prediction, last_row=row
|
||||
# )
|
||||
return ti
|
||||
|
||||
def _create_close_trade_instructions(
|
||||
self, pair: TradingPair, row: pd.Series # , prediction: Prediction
|
||||
) -> Optional[TradingInstructions]:
|
||||
ti: Optional[TradingInstructions] = None
|
||||
# URGENT _create_close_trade_instructions
|
||||
return ti
|
||||
|
||||
def _handle_outstanding_positions(self) -> Optional[pd.DataFrame]:
|
||||
trades = None
|
||||
pair = self.trading_pair_
|
||||
|
||||
# Outstanding positions
|
||||
if pair.user_data_["state"] == PairState.OPEN:
|
||||
print(f"{pair}: *** Position is NOT CLOSED. ***")
|
||||
# outstanding positions
|
||||
if self.config_.key_exists("close_outstanding_positions"):
|
||||
close_position_row = pd.Series(pair.market_data_.iloc[-2])
|
||||
# close_position_row["disequilibrium"] = 0.0
|
||||
# close_position_row["scaled_disequilibrium"] = 0.0
|
||||
# close_position_row["signed_scaled_disequilibrium"] = 0.0
|
||||
|
||||
trades = self._create_close_trades(
|
||||
pair=pair, row=close_position_row, prediction=None
|
||||
self, pair: LiveTradingPair, row: pd.Series
|
||||
) -> List[TradingInstructions]:
|
||||
ti_a: Optional[TradingInstructions] = TradingInstructions(
|
||||
book=self.pairs_trader_.book_id_,
|
||||
strategy_id=self.__class__.__name__,
|
||||
ti_type=TradingInstructions.Type.TARGET_POSITION,
|
||||
issued_ts_ns=current_nanoseconds(),
|
||||
data=TargetPositionSignal(
|
||||
strength=0,
|
||||
base_asset=pair.get_instrument_a().base_asset_id_,
|
||||
quote_asset=pair.get_instrument_a().quote_asset_id_,
|
||||
user_data={}
|
||||
),
|
||||
)
|
||||
if trades is not None:
|
||||
trades["status"] = PairState.CLOSE_POSITION.name
|
||||
print(f"CLOSE_POSITION TRADES:\n{trades}")
|
||||
pair.user_data_["state"] = PairState.CLOSE_POSITION
|
||||
pair.on_close_trades(trades)
|
||||
else:
|
||||
pair.add_outstanding_position(
|
||||
symbol=pair.symbol_a_,
|
||||
open_side=pair.user_data_["open_side_a"],
|
||||
open_px=pair.user_data_["open_px_a"],
|
||||
open_tstamp=pair.user_data_["open_tstamp"],
|
||||
last_mkt_data_row=pair.market_data_.iloc[-1],
|
||||
if not ti_a:
|
||||
return []
|
||||
ti_b: Optional[TradingInstructions] = TradingInstructions(
|
||||
book=self.pairs_trader_.book_id_,
|
||||
strategy_id=self.__class__.__name__,
|
||||
ti_type=TradingInstructions.Type.TARGET_POSITION,
|
||||
issued_ts_ns=current_nanoseconds(),
|
||||
data=TargetPositionSignal(
|
||||
strength=0,
|
||||
base_asset=pair.get_instrument_b().base_asset_id_,
|
||||
quote_asset=pair.get_instrument_b().quote_asset_id_,
|
||||
user_data={}
|
||||
),
|
||||
)
|
||||
pair.add_outstanding_position(
|
||||
symbol=pair.symbol_b_,
|
||||
open_side=pair.user_data_["open_side_b"],
|
||||
open_px=pair.user_data_["open_px_b"],
|
||||
open_tstamp=pair.user_data_["open_tstamp"],
|
||||
last_mkt_data_row=pair.market_data_.iloc[-1],
|
||||
)
|
||||
return trades
|
||||
|
||||
def _trades_df(self) -> pd.DataFrame:
|
||||
types = {
|
||||
"time": "datetime64[ns]",
|
||||
"action": "string",
|
||||
"symbol": "string",
|
||||
"side": "string",
|
||||
"price": "float64",
|
||||
"disequilibrium": "float64",
|
||||
"scaled_disequilibrium": "float64",
|
||||
"signed_scaled_disequilibrium": "float64",
|
||||
# "pair": "object",
|
||||
}
|
||||
columns = list(types.keys())
|
||||
return pd.DataFrame(columns=columns).astype(types)
|
||||
|
||||
def _create_open_trades(
|
||||
self, pair: TradingPair, row: pd.Series, prediction: Prediction
|
||||
) -> Optional[pd.DataFrame]:
|
||||
colname_a, colname_b = pair.exec_prices_colnames()
|
||||
|
||||
tstamp = row["tstamp"]
|
||||
diseqlbrm = prediction.disequilibrium_
|
||||
scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
px_a = row[f"{colname_a}"]
|
||||
px_b = row[f"{colname_b}"]
|
||||
|
||||
# creating the trades
|
||||
df = self._trades_df()
|
||||
|
||||
print(f"OPEN_TRADES: {row["tstamp"]} {scaled_disequilibrium=}")
|
||||
if diseqlbrm > 0:
|
||||
side_a = "SELL"
|
||||
side_b = "BUY"
|
||||
else:
|
||||
side_a = "BUY"
|
||||
side_b = "SELL"
|
||||
|
||||
# save closing sides
|
||||
pair.user_data_["open_side_a"] = side_a # used in oustanding positions
|
||||
pair.user_data_["open_side_b"] = side_b
|
||||
pair.user_data_["open_px_a"] = px_a
|
||||
pair.user_data_["open_px_b"] = px_b
|
||||
pair.user_data_["open_tstamp"] = tstamp
|
||||
|
||||
pair.user_data_["close_side_a"] = side_b # used for closing trades
|
||||
pair.user_data_["close_side_b"] = side_a
|
||||
|
||||
# create opening trades
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_a_,
|
||||
"side": side_a,
|
||||
"action": "OPEN",
|
||||
"price": px_a,
|
||||
"disequilibrium": diseqlbrm,
|
||||
"signed_scaled_disequilibrium": scaled_disequilibrium,
|
||||
"scaled_disequilibrium": abs(scaled_disequilibrium),
|
||||
# "pair": pair,
|
||||
}
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_b_,
|
||||
"side": side_b,
|
||||
"action": "OPEN",
|
||||
"price": px_b,
|
||||
"disequilibrium": diseqlbrm,
|
||||
"scaled_disequilibrium": abs(scaled_disequilibrium),
|
||||
"signed_scaled_disequilibrium": scaled_disequilibrium,
|
||||
# "pair": pair,
|
||||
}
|
||||
return df
|
||||
|
||||
def _create_close_trades(
|
||||
self, pair: TradingPair, row: pd.Series, prediction: Optional[Prediction] = None
|
||||
) -> Optional[pd.DataFrame]:
|
||||
colname_a, colname_b = pair.exec_prices_colnames()
|
||||
|
||||
tstamp = row["tstamp"]
|
||||
if prediction is not None:
|
||||
diseqlbrm = prediction.disequilibrium_
|
||||
signed_scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
scaled_disequilibrium = abs(prediction.scaled_disequilibrium_)
|
||||
else:
|
||||
diseqlbrm = 0.0
|
||||
signed_scaled_disequilibrium = 0.0
|
||||
scaled_disequilibrium = 0.0
|
||||
px_a = row[f"{colname_a}"]
|
||||
px_b = row[f"{colname_b}"]
|
||||
|
||||
# creating the trades
|
||||
df = self._trades_df()
|
||||
|
||||
# create opening trades
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_a_,
|
||||
"side": pair.user_data_["close_side_a"],
|
||||
"action": "CLOSE",
|
||||
"price": px_a,
|
||||
"disequilibrium": diseqlbrm,
|
||||
"scaled_disequilibrium": scaled_disequilibrium,
|
||||
"signed_scaled_disequilibrium": signed_scaled_disequilibrium,
|
||||
# "pair": pair,
|
||||
}
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_b_,
|
||||
"side": pair.user_data_["close_side_b"],
|
||||
"action": "CLOSE",
|
||||
"price": px_b,
|
||||
"disequilibrium": diseqlbrm,
|
||||
"scaled_disequilibrium": scaled_disequilibrium,
|
||||
"signed_scaled_disequilibrium": signed_scaled_disequilibrium,
|
||||
# "pair": pair,
|
||||
}
|
||||
del pair.user_data_["close_side_a"]
|
||||
del pair.user_data_["close_side_b"]
|
||||
|
||||
del pair.user_data_["open_tstamp"]
|
||||
del pair.user_data_["open_px_a"]
|
||||
del pair.user_data_["open_px_b"]
|
||||
del pair.user_data_["open_side_a"]
|
||||
del pair.user_data_["open_side_b"]
|
||||
return df
|
||||
if not ti_b:
|
||||
return []
|
||||
return [ti_a, ti_b]
|
||||
|
||||
@ -12,8 +12,8 @@ from cvttpy_tools.config import Config
|
||||
|
||||
@dataclass
|
||||
class DataWindowParams:
|
||||
training_size: int
|
||||
training_start_index: int
|
||||
training_size_: int
|
||||
training_start_index_: int
|
||||
|
||||
|
||||
class ModelDataPolicy(ABC):
|
||||
@ -24,16 +24,9 @@ class ModelDataPolicy(ABC):
|
||||
|
||||
def __init__(self, config: Config, *args: Any, **kwargs: Any):
|
||||
self.config_ = config
|
||||
training_size = config.get_value("training_size", 120)
|
||||
training_start_index = 0
|
||||
if kwargs.get("is_real_time", False):
|
||||
training_size = 120
|
||||
training_start_index = 0
|
||||
else:
|
||||
training_size = config.get_value("training_size", 120)
|
||||
self.current_data_params_ = DataWindowParams(
|
||||
training_size=config.get_value("training_size", 120),
|
||||
training_start_index=0,
|
||||
training_size_=config.get_value("training_size", 120),
|
||||
training_start_index_=0,
|
||||
)
|
||||
self.count_ = 0
|
||||
self.is_real_time_ = kwargs.get("is_real_time", False)
|
||||
@ -66,9 +59,9 @@ class RollingWindowDataPolicy(ModelDataPolicy):
|
||||
def advance(self, mkt_data_df: Optional[pd.DataFrame] = None) -> DataWindowParams:
|
||||
super().advance(mkt_data_df)
|
||||
if self.is_real_time_:
|
||||
self.current_data_params_.training_start_index = -self.current_data_params_.training_size
|
||||
self.current_data_params_.training_start_index_ = -self.current_data_params_.training_size_
|
||||
else:
|
||||
self.current_data_params_.training_start_index += 1
|
||||
self.current_data_params_.training_start_index_ += 1
|
||||
return self.current_data_params_
|
||||
|
||||
|
||||
@ -111,12 +104,12 @@ class OptimizedWndDataPolicy(ModelDataPolicy, ABC):
|
||||
if self.is_real_time_:
|
||||
self.end_index_ = len(self.mkt_data_df_) - 1
|
||||
else:
|
||||
self.end_index_ = self.current_data_params_.training_start_index + self.max_training_size_
|
||||
self.end_index_ = self.current_data_params_.training_start_index_ + self.max_training_size_
|
||||
if self.end_index_ > len(self.mkt_data_df_) - 1:
|
||||
self.end_index_ = len(self.mkt_data_df_) - 1
|
||||
self.current_data_params_.training_start_index = self.end_index_ - self.max_training_size_
|
||||
if self.current_data_params_.training_start_index < 0:
|
||||
self.current_data_params_.training_start_index = 0
|
||||
self.current_data_params_.training_start_index_ = self.end_index_ - self.max_training_size_
|
||||
if self.current_data_params_.training_start_index_ < 0:
|
||||
self.current_data_params_.training_start_index_ = 0
|
||||
|
||||
col_a, col_b = self.pair_.colnames()
|
||||
self.prices_a_ = np.array(self.mkt_data_df_[col_a])
|
||||
@ -153,8 +146,8 @@ class EGOptimizedWndDataPolicy(OptimizedWndDataPolicy):
|
||||
eg_pvalue = float(coint(series_a, series_b)[1])
|
||||
if eg_pvalue < last_pvalue:
|
||||
last_pvalue = eg_pvalue
|
||||
result.training_size = trn_size
|
||||
result.training_start_index = start_index
|
||||
result.training_size_ = trn_size
|
||||
result.training_start_index_ = start_index
|
||||
|
||||
# print(
|
||||
# f"*** DEBUG *** end_index={self.end_index_}, best_trn_size={self.current_data_params_.training_size}, {last_pvalue=}"
|
||||
@ -197,8 +190,8 @@ class ADFOptimizedWndDataPolicy(OptimizedWndDataPolicy):
|
||||
|
||||
if adf_pvalue < last_pvalue:
|
||||
last_pvalue = adf_pvalue
|
||||
result.training_size = trn_size
|
||||
result.training_start_index = start_index
|
||||
result.training_size_ = trn_size
|
||||
result.training_start_index_ = start_index
|
||||
|
||||
# print(
|
||||
# f"*** DEBUG *** end_index={self.end_index_},"
|
||||
@ -247,8 +240,8 @@ class JohansenOptdWndDataPolicy(OptimizedWndDataPolicy):
|
||||
continue
|
||||
|
||||
if best_trn_size > 0:
|
||||
result.training_size = best_trn_size
|
||||
result.training_start_index = best_start_index
|
||||
result.training_size_ = best_trn_size
|
||||
result.training_start_index_ = best_start_index
|
||||
else:
|
||||
print("*** WARNING: No valid cointegration window found.")
|
||||
|
||||
|
||||
@ -1,39 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.settings.cvtt_types import JsonDictT
|
||||
|
||||
# ---
|
||||
from cvttpy_trading.trading.mkt_data.md_summary import MdTradesAggregate
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
# ---
|
||||
from pairs_trading.lib.tools.data_loader import load_market_data
|
||||
|
||||
|
||||
class PtMarketData():
|
||||
config_: Dict[str, Any]
|
||||
class PtMarketData(NamedObject, ABC):
|
||||
config_: Config
|
||||
origin_mkt_data_df_: pd.DataFrame
|
||||
market_data_df_: pd.DataFrame
|
||||
stat_model_price_: str
|
||||
instruments_: List[ExchangeInstrument]
|
||||
symbol_a_: str
|
||||
symbol_b_: str
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
def __init__(self, config: Config, instruments: List[ExchangeInstrument]):
|
||||
self.config_ = config
|
||||
self.origin_mkt_data_df_ = pd.DataFrame()
|
||||
self.market_data_df_ = pd.DataFrame()
|
||||
self.stat_model_price_ = self.config_.get_value("stat_model_price")
|
||||
|
||||
self.instruments_ = instruments
|
||||
assert len(self.instruments_) > 0, "No instruments found in config"
|
||||
self.symbol_a_ = self.instruments_[0].instrument_id().split("-", 1)[1]
|
||||
self.symbol_b_ = self.instruments_[1].instrument_id().split("-", 1)[1]
|
||||
|
||||
@abstractmethod
|
||||
def md_columns(self) -> List[str]: ...
|
||||
|
||||
@abstractmethod
|
||||
def rename_columns(self, symbol_df: pd.DataFrame) -> pd.DataFrame: ...
|
||||
|
||||
@abstractmethod
|
||||
def tranform_df_target_colnames(self) -> List[str]: ...
|
||||
|
||||
def set_market_data(self) -> None:
|
||||
self.market_data_df_ = pd.DataFrame(
|
||||
self._transform_dataframe(self.origin_mkt_data_df_)[
|
||||
["tstamp"] + self.tranform_df_target_colnames()
|
||||
]
|
||||
)
|
||||
|
||||
self.market_data_df_ = self.market_data_df_.dropna().reset_index(drop=True)
|
||||
self.market_data_df_["tstamp"] = pd.to_datetime(self.market_data_df_["tstamp"])
|
||||
self.market_data_df_ = self.market_data_df_.sort_values("tstamp")
|
||||
|
||||
def colnames(self) -> List[str]:
|
||||
return [
|
||||
f"{self.stat_model_price_}_{self.symbol_a_}",
|
||||
f"{self.stat_model_price_}_{self.symbol_b_}",
|
||||
]
|
||||
|
||||
def _transform_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
df_selected: pd.DataFrame = pd.DataFrame(df[self.md_columns()])
|
||||
result_df = (
|
||||
pd.DataFrame(df_selected["tstamp"]).drop_duplicates().reset_index(drop=True)
|
||||
)
|
||||
|
||||
# For each unique symbol, add a corresponding stat_model_price column
|
||||
symbols = df_selected["symbol"].unique()
|
||||
|
||||
for symbol in symbols:
|
||||
# Filter rows for this symbol
|
||||
df_symbol = df_selected[df_selected["symbol"] == symbol].reset_index(
|
||||
drop=True
|
||||
)
|
||||
# Create column name like "close-COIN"
|
||||
temp_df: pd.DataFrame = self.rename_columns(df_symbol)
|
||||
# Join with our result dataframe
|
||||
result_df = pd.merge(result_df, temp_df, on="tstamp", how="left")
|
||||
result_df = result_df.reset_index(
|
||||
drop=True
|
||||
) # do not dropna() since irrelevant symbol would affect dataset
|
||||
|
||||
return result_df.dropna()
|
||||
|
||||
class ResearchMarketData(PtMarketData):
|
||||
current_index_: int
|
||||
|
||||
is_execution_price_: bool
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
super().__init__(config)
|
||||
def __init__(self, config: Config, instruments: List[ExchangeInstrument]):
|
||||
super().__init__(config, instruments)
|
||||
self.current_index_ = 0
|
||||
self.is_execution_price_ = "execution_price" in self.config_
|
||||
self.is_execution_price_ = self.config_.key_exists("execution_price")
|
||||
if self.is_execution_price_:
|
||||
self.execution_price_column_ = self.config_["execution_price"]["column"]
|
||||
self.execution_price_shift_ = self.config_["execution_price"]["shift"]
|
||||
self.execution_price_column_ = self.config_.get_value("execution_price")["column"]
|
||||
self.execution_price_shift_ = self.config_.get_value("execution_price")["shift"]
|
||||
else:
|
||||
self.execution_price_column_ = None
|
||||
self.execution_price_shift_ = 0
|
||||
@ -47,73 +111,61 @@ class ResearchMarketData(PtMarketData):
|
||||
return result
|
||||
|
||||
def load(self) -> None:
|
||||
datafiles: List[str] = self.config_.get("datafiles", [])
|
||||
instruments: List[Dict[str, str]] = self.config_.get("instruments", [])
|
||||
assert len(instruments) > 0, "No instruments found in config"
|
||||
datafiles: List[str] = self.config_.get_value("datafiles", [])
|
||||
assert len(datafiles) > 0, "No datafiles found in config"
|
||||
self.symbol_a_ = instruments[0]["symbol"]
|
||||
self.symbol_b_ = instruments[1]["symbol"]
|
||||
self.stat_model_price_ = self.config_["stat_model_price"]
|
||||
|
||||
extra_minutes: int
|
||||
extra_minutes = self.execution_price_shift_
|
||||
extra_minutes: int = self.execution_price_shift_
|
||||
|
||||
for datafile in datafiles:
|
||||
md_df = load_market_data(
|
||||
datafile=datafile,
|
||||
instruments=instruments,
|
||||
db_table_name=self.config_["market_data_loading"][instruments[0]["instrument_type"]]["db_table_name"],
|
||||
trading_hours=self.config_["trading_hours"],
|
||||
instruments=self.instruments_,
|
||||
db_table_name=self.config_.get_value("market_data_loading")[
|
||||
self.instruments_[0].user_data_.get("instrument_type", "?instrument_type?")
|
||||
]["db_table_name"],
|
||||
trading_hours=self.config_.get_value("trading_hours"),
|
||||
extra_minutes=extra_minutes,
|
||||
)
|
||||
self.origin_mkt_data_df_ = pd.concat([self.origin_mkt_data_df_, md_df])
|
||||
|
||||
self.origin_mkt_data_df_ = self.origin_mkt_data_df_.sort_values(by="tstamp")
|
||||
self.origin_mkt_data_df_ = self.origin_mkt_data_df_.dropna().reset_index(drop=True)
|
||||
self._set_market_data()
|
||||
|
||||
def _set_market_data(self, ) -> None:
|
||||
if self.is_execution_price_:
|
||||
self.market_data_df_ = pd.DataFrame(
|
||||
self._transform_dataframe(self.origin_mkt_data_df_)[["tstamp"] + self.colnames() + self.orig_exec_prices_colnames()]
|
||||
)
|
||||
else:
|
||||
self.market_data_df_ = pd.DataFrame(
|
||||
self._transform_dataframe(self.origin_mkt_data_df_)[["tstamp"] + self.colnames()]
|
||||
)
|
||||
|
||||
self.market_data_df_ = self.market_data_df_.dropna().reset_index(drop=True)
|
||||
self.market_data_df_["tstamp"] = pd.to_datetime(self.market_data_df_["tstamp"])
|
||||
self.market_data_df_ = self.market_data_df_.sort_values("tstamp")
|
||||
self._set_execution_price_data()
|
||||
|
||||
def _transform_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
df_selected: pd.DataFrame
|
||||
if self.is_execution_price_:
|
||||
execution_price_column = self.config_["execution_price"]["column"]
|
||||
|
||||
df_selected = pd.DataFrame(
|
||||
df[["tstamp", "symbol", self.stat_model_price_, execution_price_column]]
|
||||
)
|
||||
else:
|
||||
df_selected = pd.DataFrame(
|
||||
df[["tstamp", "symbol", self.stat_model_price_]]
|
||||
)
|
||||
|
||||
result_df = pd.DataFrame(df_selected["tstamp"]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
# For each unique symbol, add a corresponding stat_model_price column
|
||||
symbols = df_selected["symbol"].unique()
|
||||
|
||||
|
||||
|
||||
for symbol in symbols:
|
||||
# Filter rows for this symbol
|
||||
df_symbol = df_selected[df_selected["symbol"] == symbol].reset_index(
|
||||
self.origin_mkt_data_df_ = self.origin_mkt_data_df_.dropna().reset_index(
|
||||
drop=True
|
||||
)
|
||||
self.set_market_data()
|
||||
self._set_execution_price_data()
|
||||
|
||||
# Create column name like "close-COIN"
|
||||
def _set_execution_price_data(self) -> None:
|
||||
if not self.is_execution_price_:
|
||||
return
|
||||
if not self.config_.key_exists("execution_price"):
|
||||
self.market_data_df_[f"exec_price_{self.symbol_a_}"] = self.market_data_df_[
|
||||
f"{self.stat_model_price_}_{self.symbol_a_}"
|
||||
]
|
||||
self.market_data_df_[f"exec_price_{self.symbol_b_}"] = self.market_data_df_[
|
||||
f"{self.stat_model_price_}_{self.symbol_b_}"
|
||||
]
|
||||
return
|
||||
execution_price_column = self.config_.get_value("execution_price")["column"]
|
||||
execution_price_shift = self.config_.get_value("execution_price")["shift"]
|
||||
self.market_data_df_[f"exec_price_{self.symbol_a_}"] = self.market_data_df_[
|
||||
f"{execution_price_column}_{self.symbol_a_}"
|
||||
].shift(-execution_price_shift)
|
||||
self.market_data_df_[f"exec_price_{self.symbol_b_}"] = self.market_data_df_[
|
||||
f"{execution_price_column}_{self.symbol_b_}"
|
||||
].shift(-execution_price_shift)
|
||||
self.market_data_df_ = self.market_data_df_.dropna().reset_index(drop=True)
|
||||
|
||||
def md_columns(self) -> List[str]:
|
||||
# @abstractmethod
|
||||
if self.is_execution_price_:
|
||||
return ["tstamp", "symbol", self.stat_model_price_, self.execution_price_column_]
|
||||
else:
|
||||
return ["tstamp", "symbol", self.stat_model_price_]
|
||||
|
||||
def rename_columns(self, selected_symbol_df: pd.DataFrame) -> pd.DataFrame:
|
||||
# @abstractmethod
|
||||
symbol = selected_symbol_df.iloc[0]["symbol"]
|
||||
new_price_column = f"{self.stat_model_price_}_{symbol}"
|
||||
if self.is_execution_price_:
|
||||
new_execution_price_column = f"{self.execution_price_column_}_{symbol}"
|
||||
@ -121,53 +173,51 @@ class ResearchMarketData(PtMarketData):
|
||||
# Create temporary dataframe with timestamp and price
|
||||
temp_df = pd.DataFrame(
|
||||
{
|
||||
"tstamp": df_symbol["tstamp"],
|
||||
new_price_column: df_symbol[self.stat_model_price_],
|
||||
new_execution_price_column: df_symbol[execution_price_column],
|
||||
"tstamp": selected_symbol_df["tstamp"],
|
||||
new_price_column: selected_symbol_df[self.stat_model_price_],
|
||||
new_execution_price_column: selected_symbol_df[self.execution_price_column_],
|
||||
}
|
||||
)
|
||||
else:
|
||||
temp_df = pd.DataFrame(
|
||||
{
|
||||
"tstamp": df_symbol["tstamp"],
|
||||
new_price_column: df_symbol[self.stat_model_price_],
|
||||
"tstamp": selected_symbol_df["tstamp"],
|
||||
new_price_column: selected_symbol_df[self.stat_model_price_],
|
||||
}
|
||||
)
|
||||
return temp_df
|
||||
|
||||
# Join with our result dataframe
|
||||
result_df = pd.merge(result_df, temp_df, on="tstamp", how="left")
|
||||
result_df = result_df.reset_index(
|
||||
drop=True
|
||||
) # do not dropna() since irrelevant symbol would affect dataset
|
||||
|
||||
return result_df.dropna()
|
||||
|
||||
def _set_execution_price_data(self) -> None:
|
||||
if "execution_price" not in self.config_:
|
||||
self.market_data_df_[f"exec_price_{self.symbol_a_}"] = self.market_data_df_[f"{self.stat_model_price_}_{self.symbol_a_}"]
|
||||
self.market_data_df_[f"exec_price_{self.symbol_b_}"] = self.market_data_df_[f"{self.stat_model_price_}_{self.symbol_b_}"]
|
||||
return
|
||||
execution_price_column = self.config_["execution_price"]["column"]
|
||||
execution_price_shift = self.config_["execution_price"]["shift"]
|
||||
self.market_data_df_[f"exec_price_{self.symbol_a_}"] = self.market_data_df_[f"{execution_price_column}_{self.symbol_a_}"].shift(-execution_price_shift)
|
||||
self.market_data_df_[f"exec_price_{self.symbol_b_}"] = self.market_data_df_[f"{execution_price_column}_{self.symbol_b_}"].shift(-execution_price_shift)
|
||||
self.market_data_df_ = self.market_data_df_.dropna().reset_index(drop=True)
|
||||
|
||||
def colnames(self) -> List[str]:
|
||||
return [
|
||||
f"{self.stat_model_price_}_{self.symbol_a_}",
|
||||
f"{self.stat_model_price_}_{self.symbol_b_}",
|
||||
]
|
||||
def tranform_df_target_colnames(self):
|
||||
# @abstractmethod
|
||||
return self.colnames() + self.orig_exec_prices_colnames()
|
||||
|
||||
def orig_exec_prices_colnames(self) -> List[str]:
|
||||
return [
|
||||
f"{self.execution_price_column_}_{self.symbol_a_}",
|
||||
f"{self.execution_price_column_}_{self.symbol_b_}",
|
||||
]
|
||||
] if self.is_execution_price_ else []
|
||||
|
||||
def exec_prices_colnames(self) -> List[str]:
|
||||
return [
|
||||
f"exec_price_{self.symbol_a_}",
|
||||
f"exec_price_{self.symbol_b_}",
|
||||
]
|
||||
class LiveMarketData(PtMarketData):
|
||||
|
||||
def __init__(self, config: Config, instruments: List[ExchangeInstrument]):
|
||||
super().__init__(config, instruments)
|
||||
|
||||
def md_columns(self) -> List[str]:
|
||||
# @abstractmethod
|
||||
return ["tstamp", "symbol", self.stat_model_price_]
|
||||
|
||||
def rename_columns(self, selected_symbol_df: pd.DataFrame) -> pd.DataFrame:
|
||||
# @abstractmethod
|
||||
symbol = selected_symbol_df.iloc[0]["symbol"]
|
||||
new_price_column = f"{self.stat_model_price_}_{symbol}"
|
||||
temp_df = pd.DataFrame(
|
||||
{
|
||||
"tstamp": selected_symbol_df["tstamp"],
|
||||
new_price_column: selected_symbol_df[self.stat_model_price_],
|
||||
}
|
||||
)
|
||||
return temp_df
|
||||
|
||||
def tranform_df_target_colnames(self):
|
||||
# @abstractmethod
|
||||
return self.colnames()
|
||||
|
||||
@ -3,6 +3,9 @@ from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, cast
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.config import Config
|
||||
# ---
|
||||
from pairs_trading.lib.pt_strategy.prediction import Prediction
|
||||
from pairs_trading.lib.pt_strategy.trading_pair import TradingPair
|
||||
|
||||
@ -13,10 +16,10 @@ class PairsTradingModel(ABC):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def create(config: Dict[str, Any]) -> PairsTradingModel:
|
||||
def create(config: Config) -> PairsTradingModel:
|
||||
import importlib
|
||||
|
||||
model_class_name = config.get("model_class", None)
|
||||
model_class_name = config.get_value("model_class", None)
|
||||
assert model_class_name is not None
|
||||
module_name, class_name = model_class_name.rsplit(".", 1)
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
@ -1,56 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
# ---
|
||||
from cvttpy_tools.config import Config
|
||||
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
# ---
|
||||
from pairs_trading.lib.pt_strategy.model_data_policy import ModelDataPolicy
|
||||
from pairs_trading.lib.pt_strategy.pt_market_data import ResearchMarketData
|
||||
from pairs_trading.lib.pt_strategy.pt_model import Prediction
|
||||
from pairs_trading.lib.pt_strategy.trading_pair import PairState, TradingPair
|
||||
|
||||
from pairs_trading.lib.pt_strategy.trading_pair import PairState, TradingPair, ResearchTradingPair
|
||||
|
||||
class PtResearchStrategy:
|
||||
config_: Dict[str, Any]
|
||||
trading_pair_: TradingPair
|
||||
config_: Config
|
||||
trading_pair_: ResearchTradingPair
|
||||
model_data_policy_: ModelDataPolicy
|
||||
pt_mkt_data_: ResearchMarketData
|
||||
|
||||
trades_: List[pd.DataFrame]
|
||||
predictions_: pd.DataFrame
|
||||
predictions_df_: pd.DataFrame
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Dict[str, Any],
|
||||
datafiles: List[str],
|
||||
instruments: List[Dict[str, str]],
|
||||
config: Config,
|
||||
instruments: List[ExchangeInstrument]
|
||||
):
|
||||
from pairs_trading.lib.pt_strategy.model_data_policy import ModelDataPolicy
|
||||
from pairs_trading.lib.pt_strategy.trading_pair import TradingPair
|
||||
|
||||
self.config_ = config
|
||||
self.trades_ = []
|
||||
self.trading_pair_ = TradingPair(config=config, instruments=instruments)
|
||||
self.predictions_ = pd.DataFrame()
|
||||
self.trading_pair_ = ResearchTradingPair(config=config, instruments=instruments)
|
||||
self.predictions_df_ = pd.DataFrame()
|
||||
|
||||
import copy
|
||||
|
||||
# modified config must be passed to PtMarketData
|
||||
config_copy = copy.deepcopy(config)
|
||||
config_copy["instruments"] = instruments
|
||||
config_copy["datafiles"] = datafiles
|
||||
self.pt_mkt_data_ = ResearchMarketData(config=config_copy)
|
||||
config_copy.set_value("instruments", instruments)
|
||||
self.pt_mkt_data_ = ResearchMarketData(config=config_copy, instruments=instruments)
|
||||
self.pt_mkt_data_.load()
|
||||
self.model_data_policy_ = ModelDataPolicy.create(
|
||||
Config(config_copy), mkt_data=self.pt_mkt_data_.market_data_df_, pair=self.trading_pair_
|
||||
config_copy, mkt_data=self.pt_mkt_data_.market_data_df_, pair=self.trading_pair_
|
||||
)
|
||||
|
||||
def outstanding_positions(self) -> List[Dict[str, Any]]:
|
||||
return list(self.trading_pair_.user_data_.get("outstanding_positions", []))
|
||||
|
||||
def run(self) -> None:
|
||||
training_minutes = self.config_.get("training_minutes", 120)
|
||||
training_minutes = self.config_.get_value("training_minutes", 120)
|
||||
market_data_series: pd.Series
|
||||
market_data_df = pd.DataFrame()
|
||||
|
||||
@ -74,8 +74,8 @@ class PtResearchStrategy:
|
||||
prediction = self.trading_pair_.run(
|
||||
market_data_df, self.model_data_policy_.advance(mkt_data_df=market_data_df)
|
||||
)
|
||||
self.predictions_ = pd.concat(
|
||||
[self.predictions_, prediction.to_df()], ignore_index=True
|
||||
self.predictions_df_ = pd.concat(
|
||||
[self.predictions_df_, prediction.to_df()], ignore_index=True
|
||||
)
|
||||
assert prediction is not None
|
||||
|
||||
@ -95,8 +95,8 @@ class PtResearchStrategy:
|
||||
pair = self.trading_pair_
|
||||
trades = None
|
||||
|
||||
open_threshold = self.config_["dis-equilibrium_open_trshld"]
|
||||
close_threshold = self.config_["dis-equilibrium_close_trshld"]
|
||||
open_threshold = self.config_.get_value("dis-equilibrium_open_trshld")
|
||||
close_threshold = self.config_.get_value("dis-equilibrium_close_trshld")
|
||||
scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
abs_scaled_disequilibrium = abs(scaled_disequilibrium)
|
||||
|
||||
@ -145,7 +145,7 @@ class PtResearchStrategy:
|
||||
if pair.user_data_["state"] == PairState.OPEN:
|
||||
print(f"{pair}: *** Position is NOT CLOSED. ***")
|
||||
# outstanding positions
|
||||
if self.config_["close_outstanding_positions"]:
|
||||
if self.config_.get_value("close_outstanding_positions", False):
|
||||
close_position_row = pd.Series(pair.market_data_.iloc[-2])
|
||||
# close_position_row["disequilibrium"] = 0.0
|
||||
# close_position_row["scaled_disequilibrium"] = 0.0
|
||||
@ -161,14 +161,14 @@ class PtResearchStrategy:
|
||||
pair.on_close_trades(trades)
|
||||
else:
|
||||
pair.add_outstanding_position(
|
||||
symbol=pair.symbol_a_,
|
||||
symbol=pair.symbol_a(),
|
||||
open_side=pair.user_data_["open_side_a"],
|
||||
open_px=pair.user_data_["open_px_a"],
|
||||
open_tstamp=pair.user_data_["open_tstamp"],
|
||||
last_mkt_data_row=pair.market_data_.iloc[-1],
|
||||
)
|
||||
pair.add_outstanding_position(
|
||||
symbol=pair.symbol_b_,
|
||||
symbol=pair.symbol_b(),
|
||||
open_side=pair.user_data_["open_side_b"],
|
||||
open_px=pair.user_data_["open_px_b"],
|
||||
open_tstamp=pair.user_data_["open_tstamp"],
|
||||
@ -192,7 +192,7 @@ class PtResearchStrategy:
|
||||
return pd.DataFrame(columns=columns).astype(types)
|
||||
|
||||
def _create_open_trades(
|
||||
self, pair: TradingPair, row: pd.Series, prediction: Prediction
|
||||
self, pair: ResearchTradingPair, row: pd.Series, prediction: Prediction
|
||||
) -> Optional[pd.DataFrame]:
|
||||
colname_a, colname_b = pair.exec_prices_colnames()
|
||||
|
||||
@ -226,7 +226,7 @@ class PtResearchStrategy:
|
||||
# create opening trades
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_a_,
|
||||
"symbol": pair.symbol_a(),
|
||||
"side": side_a,
|
||||
"action": "OPEN",
|
||||
"price": px_a,
|
||||
@ -237,7 +237,7 @@ class PtResearchStrategy:
|
||||
}
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_b_,
|
||||
"symbol": pair.symbol_b(),
|
||||
"side": side_b,
|
||||
"action": "OPEN",
|
||||
"price": px_b,
|
||||
@ -249,7 +249,7 @@ class PtResearchStrategy:
|
||||
return df
|
||||
|
||||
def _create_close_trades(
|
||||
self, pair: TradingPair, row: pd.Series, prediction: Optional[Prediction] = None
|
||||
self, pair: ResearchTradingPair, row: pd.Series, prediction: Optional[Prediction] = None
|
||||
) -> Optional[pd.DataFrame]:
|
||||
colname_a, colname_b = pair.exec_prices_colnames()
|
||||
|
||||
@ -271,7 +271,7 @@ class PtResearchStrategy:
|
||||
# create opening trades
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_a_,
|
||||
"symbol": pair.symbol_a(),
|
||||
"side": pair.user_data_["close_side_a"],
|
||||
"action": "CLOSE",
|
||||
"price": px_a,
|
||||
@ -282,7 +282,7 @@ class PtResearchStrategy:
|
||||
}
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_b_,
|
||||
"symbol": pair.symbol_b(),
|
||||
"side": pair.user_data_["close_side_b"],
|
||||
"action": "CLOSE",
|
||||
"price": px_b,
|
||||
|
||||
@ -4,6 +4,8 @@ from datetime import date, datetime
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
|
||||
from pairs_trading.lib.pt_strategy.trading_pair import TradingPair
|
||||
|
||||
|
||||
@ -120,7 +122,7 @@ def store_config_in_database(
|
||||
config_file_path: str,
|
||||
config: Dict,
|
||||
datafiles: List[Tuple[str, str]],
|
||||
instruments: List[Dict[str, str]],
|
||||
instruments: List[ExchangeInstrument],
|
||||
) -> None:
|
||||
"""
|
||||
Store configuration information in the database for reference.
|
||||
@ -141,7 +143,7 @@ def store_config_in_database(
|
||||
datafiles_str = ", ".join([f"{datafile}" for _, datafile in datafiles])
|
||||
instruments_str = ", ".join(
|
||||
[
|
||||
f"{inst['symbol']}:{inst['instrument_type']}:{inst['exchange_id']}"
|
||||
inst.details_short()
|
||||
for inst in instruments
|
||||
]
|
||||
)
|
||||
@ -292,7 +294,7 @@ class PairResearchResult:
|
||||
pair_return = symbol_a_return + symbol_b_return
|
||||
|
||||
# Create round-trip records for both symbols
|
||||
funding_per_position = self.config_.get("funding_per_pair", 10000) / 2
|
||||
funding_per_position = self.config_.get_value("funding_per_pair", 10000) / 2
|
||||
|
||||
# Symbol A round-trip
|
||||
day_roundtrips.append({
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import pandas as pd
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import Config
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
# ---
|
||||
from pairs_trading.lib.pt_strategy.model_data_policy import DataWindowParams
|
||||
from pairs_trading.lib.pt_strategy.prediction import Prediction
|
||||
from pairs_trading.lib.pt_strategy.models import PairsTradingModel
|
||||
|
||||
|
||||
|
||||
class PairState(Enum):
|
||||
@ -23,62 +27,90 @@ class PairState(Enum):
|
||||
CLOSE_STOP_PROFIT = 6
|
||||
|
||||
|
||||
def get_symbol(instrument: Dict[str, str]) -> str:
|
||||
if "symbol" in instrument:
|
||||
return instrument["symbol"]
|
||||
elif "instrument_id" in instrument:
|
||||
instrument_id = instrument["instrument_id"]
|
||||
instrument_pfx = instrument_id[:instrument_id.find("-") + 1]
|
||||
symbol = instrument_id[len(instrument_pfx):]
|
||||
instrument["symbol"] = symbol
|
||||
instrument["instrument_id_pfx"] = instrument_pfx
|
||||
return symbol
|
||||
else:
|
||||
raise ValueError(f"Invalid instrument: {instrument}, missing symbol or instrument_id")
|
||||
# def get_symbol(instrument: Dict[str, str]) -> str:
|
||||
# if "symbol" in instrument:
|
||||
# return instrument["symbol"]
|
||||
# elif "instrument_id" in instrument:
|
||||
# instrument_id = instrument["instrument_id"]
|
||||
# instrument_pfx = instrument_id[: instrument_id.find("-") + 1]
|
||||
# symbol = instrument_id[len(instrument_pfx) :]
|
||||
# instrument["symbol"] = symbol
|
||||
# instrument["instrument_id_pfx"] = instrument_pfx
|
||||
# return symbol
|
||||
# else:
|
||||
# raise ValueError(
|
||||
# f"Invalid instrument: {instrument}, missing symbol or instrument_id"
|
||||
# )
|
||||
|
||||
class TradingPair:
|
||||
config_: Dict[str, Any]
|
||||
|
||||
class TradingPair(NamedObject, ABC):
|
||||
config_: Config
|
||||
model_: Any # "PairsTradingModel"
|
||||
market_data_: pd.DataFrame
|
||||
instruments_: List[Dict[str, str]]
|
||||
symbol_a_: str
|
||||
symbol_b_: str
|
||||
|
||||
stat_model_price_: str
|
||||
model_: PairsTradingModel # type: ignore[assignment]
|
||||
|
||||
user_data_: Dict[str, Any]
|
||||
stat_model_price_: str
|
||||
|
||||
exch_inst_a_: ExchangeInstrument
|
||||
exch_inst_b_: ExchangeInstrument
|
||||
instruments_: List[ExchangeInstrument]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Dict[str, Any],
|
||||
instruments: List[Dict[str, str]],
|
||||
config: Config,
|
||||
instruments: List[ExchangeInstrument],
|
||||
):
|
||||
|
||||
from pairs_trading.lib.pt_strategy.pt_model import PairsTradingModel
|
||||
|
||||
assert len(instruments) == 2, "Trading pair must have exactly 2 instruments"
|
||||
|
||||
self.config_ = config
|
||||
self.instruments_ = instruments
|
||||
self.symbol_a_ = get_symbol(instruments[0])
|
||||
self.symbol_b_ = get_symbol(instruments[1])
|
||||
self.model_ = PairsTradingModel.create(config)
|
||||
self.stat_model_price_ = config["stat_model_price"]
|
||||
self.user_data_ = {
|
||||
"state": PairState.INITIAL,
|
||||
}
|
||||
self.user_data_ = {}
|
||||
self.instruments_ = instruments
|
||||
self.instruments_[0].user_data_["symbol"] = instruments[0].instrument_id().split("-", 1)[1]
|
||||
self.instruments_[1].user_data_["symbol"] = instruments[1].instrument_id().split("-", 1)[1]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}:"
|
||||
f" symbol_a={self.symbol_a_},"
|
||||
f" symbol_b={self.symbol_b_},"
|
||||
f" symbol_a={self.symbol_a()},"
|
||||
f" symbol_b={self.symbol_b()},"
|
||||
f" model={self.model_.__class__.__name__}"
|
||||
)
|
||||
|
||||
def colnames(self) -> List[str]:
|
||||
return [
|
||||
f"{self.stat_model_price_}_{self.symbol_a()}",
|
||||
f"{self.stat_model_price_}_{self.symbol_b()}",
|
||||
]
|
||||
def symbol_a(self) -> str:
|
||||
return self.get_instrument_a().user_data_["symbol"]
|
||||
|
||||
def symbol_b(self) -> str:
|
||||
return self.get_instrument_b().user_data_["symbol"]
|
||||
|
||||
def get_instrument_a(self) -> ExchangeInstrument:
|
||||
return self.instruments_[0]
|
||||
|
||||
def get_instrument_b(self) -> ExchangeInstrument:
|
||||
return self.instruments_[1]
|
||||
|
||||
|
||||
|
||||
class ResearchTradingPair(TradingPair):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
instruments: List[ExchangeInstrument],
|
||||
):
|
||||
assert len(instruments) == 2, "Trading pair must have exactly 2 instruments"
|
||||
super().__init__(config=config, instruments=instruments)
|
||||
|
||||
self.stat_model_price_ = config.get_value("stat_model_price")
|
||||
self.user_data_ = {
|
||||
"state": PairState.INITIAL,
|
||||
}
|
||||
|
||||
# URGENT set exchange instruments for the pair
|
||||
|
||||
def is_closed(self) -> bool:
|
||||
return self.user_data_["state"] in [
|
||||
PairState.CLOSE,
|
||||
@ -86,39 +118,34 @@ class TradingPair:
|
||||
PairState.CLOSE_STOP_LOSS,
|
||||
PairState.CLOSE_STOP_PROFIT,
|
||||
]
|
||||
def is_open(self) -> bool:
|
||||
return self.user_data_["state"] == PairState.OPEN
|
||||
|
||||
def colnames(self) -> List[str]:
|
||||
return [
|
||||
f"{self.stat_model_price_}_{self.symbol_a_}",
|
||||
f"{self.stat_model_price_}_{self.symbol_b_}",
|
||||
]
|
||||
def is_open(self) -> bool:
|
||||
return not self.is_closed()
|
||||
|
||||
def exec_prices_colnames(self) -> List[str]:
|
||||
return [
|
||||
f"exec_price_{self.symbol_a_}",
|
||||
f"exec_price_{self.symbol_b_}",
|
||||
f"exec_price_{self.symbol_a()}",
|
||||
f"exec_price_{self.symbol_b()}",
|
||||
]
|
||||
|
||||
def to_stop_close_conditions(self, predicted_row: pd.Series) -> bool:
|
||||
config = self.config_
|
||||
if (
|
||||
"stop_close_conditions" not in config
|
||||
or config["stop_close_conditions"] is None
|
||||
not config.key_exists("stop_close_conditions")
|
||||
or config.get_value("stop_close_conditions") is None
|
||||
):
|
||||
return False
|
||||
if "profit" in config["stop_close_conditions"]:
|
||||
if "profit" in config.get_value("stop_close_conditions"):
|
||||
current_return = self._current_return(predicted_row)
|
||||
#
|
||||
# print(f"time={predicted_row['tstamp']} current_return={current_return}")
|
||||
#
|
||||
if current_return >= config["stop_close_conditions"]["profit"]:
|
||||
if current_return >= config.get_value("stop_close_conditions")["profit"]:
|
||||
print(f"STOP PROFIT: {current_return}")
|
||||
self.user_data_["stop_close_state"] = PairState.CLOSE_STOP_PROFIT
|
||||
return True
|
||||
if "loss" in config["stop_close_conditions"]:
|
||||
if current_return <= config["stop_close_conditions"]["loss"]:
|
||||
if "loss" in config.get_value("stop_close_conditions"):
|
||||
if current_return <= config.get_value("stop_close_conditions")["loss"]:
|
||||
print(f"STOP LOSS: {current_return}")
|
||||
self.user_data_["stop_close_state"] = PairState.CLOSE_STOP_LOSS
|
||||
return True
|
||||
@ -143,8 +170,8 @@ class TradingPair:
|
||||
)
|
||||
return float(instrument_return) * 100.0
|
||||
|
||||
instrument_a_return = _single_instrument_return(self.symbol_a_)
|
||||
instrument_b_return = _single_instrument_return(self.symbol_b_)
|
||||
instrument_a_return = _single_instrument_return(self.symbol_a())
|
||||
instrument_b_return = _single_instrument_return(self.symbol_b())
|
||||
return instrument_a_return + instrument_b_return
|
||||
return 0.0
|
||||
|
||||
@ -165,20 +192,22 @@ class TradingPair:
|
||||
open_tstamp: datetime,
|
||||
last_mkt_data_row: pd.Series,
|
||||
) -> None:
|
||||
assert symbol in [self.symbol_a_, self.symbol_b_], "Symbol must be one of the pair's symbols"
|
||||
assert symbol in [
|
||||
self.symbol_a(),
|
||||
self.symbol_b(),
|
||||
], "Symbol must be one of the pair's symbols"
|
||||
assert open_side in ["BUY", "SELL"], "Open side must be either BUY or SELL"
|
||||
assert open_px > 0, "Open price must be greater than 0"
|
||||
assert open_tstamp is not None, "Open timestamp must be provided"
|
||||
assert last_mkt_data_row is not None, "Last market data row must be provided"
|
||||
|
||||
exec_prices_col_a, exec_prices_col_b = self.exec_prices_colnames()
|
||||
if symbol == self.symbol_a_:
|
||||
if symbol == self.symbol_a():
|
||||
last_px = last_mkt_data_row[exec_prices_col_a]
|
||||
else:
|
||||
last_px = last_mkt_data_row[exec_prices_col_b]
|
||||
|
||||
|
||||
funding_per_position = self.config_["funding_per_pair"] / 2
|
||||
funding_per_position = self.config_.get_value("funding_per_pair") / 2
|
||||
shares = funding_per_position / open_px
|
||||
if open_side == "SELL":
|
||||
shares = -shares
|
||||
@ -186,7 +215,8 @@ class TradingPair:
|
||||
if "outstanding_positions" not in self.user_data_:
|
||||
self.user_data_["outstanding_positions"] = []
|
||||
|
||||
self.user_data_["outstanding_positions"].append({
|
||||
self.user_data_["outstanding_positions"].append(
|
||||
{
|
||||
"symbol": symbol,
|
||||
"open_side": open_side,
|
||||
"open_px": open_px,
|
||||
@ -195,16 +225,23 @@ class TradingPair:
|
||||
"last_px": last_px,
|
||||
"last_tstamp": last_mkt_data_row["tstamp"],
|
||||
"last_value": last_px * shares,
|
||||
})
|
||||
|
||||
def get_instrument_a(self) -> ExchangeInstrument:
|
||||
return self.exch_inst_a_
|
||||
def get_instrument_b(self) -> ExchangeInstrument:
|
||||
return self.exch_inst_b_
|
||||
}
|
||||
)
|
||||
|
||||
def run(self, market_data: pd.DataFrame, data_params: DataWindowParams) -> Prediction: # type: ignore[assignment]
|
||||
self.market_data_ = market_data[data_params.training_start_index:data_params.training_start_index + data_params.training_size]
|
||||
self.market_data_ = market_data[
|
||||
data_params.training_start_index_ : data_params.training_start_index_
|
||||
+ data_params.training_size_
|
||||
]
|
||||
return self.model_.predict(pair=self)
|
||||
|
||||
class LiveTradingPair(TradingPair):
|
||||
|
||||
def __init__(self, config: Config, instruments: List[ExchangeInstrument]):
|
||||
super().__init__(config, instruments)
|
||||
|
||||
def to_stop_close_conditions(self, predicted_row: pd.Series) -> bool:
|
||||
# TODO LiveTradingPair.to_stop_close_conditions()
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import hjson
|
||||
from typing import Dict
|
||||
from datetime import datetime
|
||||
# ---
|
||||
from cvttpy_tools.config import Config
|
||||
|
||||
|
||||
def load_config(config_path: str) -> Dict:
|
||||
with open(config_path, "r") as f:
|
||||
config = hjson.load(f)
|
||||
return dict(config)
|
||||
def load_config(config_path: str) -> Config:
|
||||
return Config(json_src=f"file://{config_path}")
|
||||
|
||||
|
||||
def expand_filename(filename: str) -> str:
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from typing import Dict, List, cast
|
||||
from typing import Any, Dict, List, Tuple, cast
|
||||
import pandas as pd
|
||||
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
|
||||
def load_sqlite_to_dataframe(db_path:str, query:str) -> pd.DataFrame:
|
||||
df: pd.DataFrame = pd.DataFrame()
|
||||
@ -45,19 +46,17 @@ def convert_time_to_UTC(value: str, timezone: str, extra_minutes: int = 0) -> st
|
||||
|
||||
def load_market_data(
|
||||
datafile: str,
|
||||
instruments: List[Dict[str, str]],
|
||||
instruments: List[ExchangeInstrument],
|
||||
db_table_name: str,
|
||||
trading_hours: Dict = {},
|
||||
extra_minutes: int = 0,
|
||||
) -> pd.DataFrame:
|
||||
|
||||
insts = [
|
||||
'"' + instrument["instrument_id_pfx"] + instrument["symbol"] + '"'
|
||||
for instrument in instruments
|
||||
]
|
||||
instrument_ids = list(set(insts))
|
||||
|
||||
inst_ids = ['"' + exch_inst.instrument_id() + '"' for exch_inst in instruments]
|
||||
instrument_ids = list(set(inst_ids))
|
||||
exchange_ids = list(
|
||||
set(['"' + instrument["exchange_id"] + '"' for instrument in instruments])
|
||||
set(['"' + instrument.exchange_id() + '"' for instrument in instruments])
|
||||
)
|
||||
|
||||
query = "select"
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
import os
|
||||
import glob
|
||||
from typing import Dict, List, Tuple
|
||||
# ---
|
||||
from cvttpy_tools.config import CvttAppConfig
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
|
||||
DayT = str
|
||||
DataFileNameT = str
|
||||
|
||||
def resolve_datafiles(
|
||||
config: Dict, date_pattern: str, instruments: List[Dict[str, str]]
|
||||
config: Dict, date_pattern: str, instruments: List[ExchangeInstrument]
|
||||
) -> List[Tuple[DayT, DataFileNameT]]:
|
||||
resolved_files: List[Tuple[DayT, DataFileNameT]] = []
|
||||
for inst in instruments:
|
||||
for exch_inst in instruments:
|
||||
pattern = date_pattern
|
||||
inst_type = inst["instrument_type"]
|
||||
inst_type = exch_inst.user_data_.get("instrument_type", "?instrument_type?")
|
||||
data_dir = config["market_data_loading"][inst_type]["data_directory"]
|
||||
if "*" in pattern or "?" in pattern:
|
||||
# Handle wildcards
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import argparse
|
||||
from typing import Dict, List
|
||||
|
||||
def get_instruments(args: argparse.Namespace, config: Dict) -> List[Dict[str, str]]:
|
||||
|
||||
instruments = [
|
||||
{
|
||||
"symbol": inst.split(":")[0],
|
||||
"instrument_type": inst.split(":")[1],
|
||||
"exchange_id": inst.split(":")[2],
|
||||
"instrument_id_pfx": config["market_data_loading"][inst.split(":")[1]][
|
||||
"instrument_id_pfx"
|
||||
],
|
||||
"db_table_name": config["market_data_loading"][inst.split(":")[1]][
|
||||
"db_table_name"
|
||||
],
|
||||
}
|
||||
for inst in args.instruments.split(",")
|
||||
]
|
||||
return instruments
|
||||
|
||||
@ -8,8 +8,8 @@ def visualize_prices(strategy: PtResearchStrategy, trading_date: str) -> None:
|
||||
import seaborn as sns
|
||||
|
||||
pair = strategy.trading_pair_
|
||||
SYMBOL_A = pair.symbol_a_
|
||||
SYMBOL_B = pair.symbol_b_
|
||||
SYMBOL_A = pair.symbol_a()
|
||||
SYMBOL_B = pair.symbol_b()
|
||||
TRD_DATE = f"{trading_date[0:4]}-{trading_date[4:6]}-{trading_date[6:8]}"
|
||||
|
||||
plt.style.use('seaborn-v0_8')
|
||||
|
||||
@ -1,13 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
from pairs_trading.lib.pairs_trading.lib.tegy.results import (PairResearchResult, create_result_database,
|
||||
store_config_in_database)
|
||||
from pairs_trading.lib.pairs_trading.lib.t_strategy.research_strategy import PtResearchStrategy
|
||||
from pairs_trading.lib.tools.filetools import resolve_datafiles
|
||||
from pairs_trading.lib.tools.instruments import get_instruments
|
||||
from pairs_trading.lib.pt_strategy.results import (PairResearchResult)
|
||||
from pairs_trading.lib.pt_strategy.research_strategy import PtResearchStrategy
|
||||
|
||||
|
||||
def visualize_trades(strategy: PtResearchStrategy, results: PairResearchResult, trading_date: str) -> None:
|
||||
@ -25,8 +20,8 @@ def visualize_trades(strategy: PtResearchStrategy, results: PairResearchResult,
|
||||
origin_mkt_data_df = strategy.pt_mkt_data_.origin_mkt_data_df_
|
||||
mkt_data_df = strategy.pt_mkt_data_.market_data_df_
|
||||
TRD_DATE = f"{trading_date[0:4]}-{trading_date[4:6]}-{trading_date[6:8]}"
|
||||
SYMBOL_A = pair.symbol_a_
|
||||
SYMBOL_B = pair.symbol_b_
|
||||
SYMBOL_A = pair.symbol_a()
|
||||
SYMBOL_B = pair.symbol_b()
|
||||
|
||||
|
||||
print(f"\nCreated trading pair: {pair}")
|
||||
@ -51,7 +46,7 @@ def visualize_trades(strategy: PtResearchStrategy, results: PairResearchResult,
|
||||
timeline_df = pd.DataFrame({'tstamp': all_timestamps})
|
||||
|
||||
# Merge with predicted data to get dis-equilibrium values
|
||||
timeline_df = timeline_df.merge(strategy.predictions_[['tstamp', 'disequilibrium', 'scaled_disequilibrium', 'signed_scaled_disequilibrium']],
|
||||
timeline_df = timeline_df.merge(strategy.predictions_df_[['tstamp', 'disequilibrium', 'scaled_disequilibrium', 'signed_scaled_disequilibrium']],
|
||||
on='tstamp', how='left')
|
||||
|
||||
# Get Symbol_A and Symbol_B market data
|
||||
@ -110,8 +105,8 @@ def visualize_trades(strategy: PtResearchStrategy, results: PairResearchResult,
|
||||
type="line",
|
||||
x0=timeline_df['tstamp'].min(),
|
||||
x1=timeline_df['tstamp'].max(),
|
||||
y0=strategy.config_['dis-equilibrium_open_trshld'],
|
||||
y1=strategy.config_['dis-equilibrium_open_trshld'],
|
||||
y0=strategy.config_.get_value('dis-equilibrium_open_trshld'),
|
||||
y1=strategy.config_.get_value('dis-equilibrium_open_trshld'),
|
||||
line=dict(color="purple", width=2, dash="dot"),
|
||||
opacity=0.7,
|
||||
row=1, col=1
|
||||
@ -121,8 +116,8 @@ def visualize_trades(strategy: PtResearchStrategy, results: PairResearchResult,
|
||||
type="line",
|
||||
x0=timeline_df['tstamp'].min(),
|
||||
x1=timeline_df['tstamp'].max(),
|
||||
y0=-strategy.config_['dis-equilibrium_open_trshld'],
|
||||
y1=-strategy.config_['dis-equilibrium_open_trshld'],
|
||||
y0=-strategy.config_.get_value('dis-equilibrium_open_trshld'),
|
||||
y1=-strategy.config_.get_value('dis-equilibrium_open_trshld'),
|
||||
line=dict(color="purple", width=2, dash="dot"),
|
||||
opacity=0.7,
|
||||
row=1, col=1
|
||||
@ -132,8 +127,8 @@ def visualize_trades(strategy: PtResearchStrategy, results: PairResearchResult,
|
||||
type="line",
|
||||
x0=timeline_df['tstamp'].min(),
|
||||
x1=timeline_df['tstamp'].max(),
|
||||
y0=strategy.config_['dis-equilibrium_close_trshld'],
|
||||
y1=strategy.config_['dis-equilibrium_close_trshld'],
|
||||
y0=strategy.config_.get_value('dis-equilibrium_close_trshld'),
|
||||
y1=strategy.config_.get_value('dis-equilibrium_close_trshld'),
|
||||
line=dict(color="brown", width=2, dash="dot"),
|
||||
opacity=0.7,
|
||||
row=1, col=1
|
||||
@ -143,8 +138,8 @@ def visualize_trades(strategy: PtResearchStrategy, results: PairResearchResult,
|
||||
type="line",
|
||||
x0=timeline_df['tstamp'].min(),
|
||||
x1=timeline_df['tstamp'].max(),
|
||||
y0=-strategy.config_['dis-equilibrium_close_trshld'],
|
||||
y1=-strategy.config_['dis-equilibrium_close_trshld'],
|
||||
y0=-strategy.config_.get_value('dis-equilibrium_close_trshld'),
|
||||
y1=-strategy.config_.get_value('dis-equilibrium_close_trshld'),
|
||||
line=dict(color="brown", width=2, dash="dot"),
|
||||
opacity=0.7,
|
||||
row=1, col=1
|
||||
|
||||
@ -1,8 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.app import App
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import CvttAppConfig
|
||||
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
from cvttpy_trading.settings.instruments import Instruments
|
||||
|
||||
# ---
|
||||
from pairs_trading.lib.pt_strategy.results import (
|
||||
PairResearchResult,
|
||||
create_result_database,
|
||||
@ -10,44 +20,48 @@ from pairs_trading.lib.pt_strategy.results import (
|
||||
)
|
||||
from pairs_trading.lib.pt_strategy.research_strategy import PtResearchStrategy
|
||||
from pairs_trading.lib.tools.filetools import resolve_datafiles
|
||||
from pairs_trading.lib.tools.instruments import get_instruments
|
||||
|
||||
InstrumentTypeT = str
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import argparse
|
||||
class Runner(NamedObject):
|
||||
def __init__(self):
|
||||
App()
|
||||
CvttAppConfig()
|
||||
|
||||
from pairs_trading.lib.tools.config import expand_filename, load_config
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run pairs trading backtest.")
|
||||
parser.add_argument(
|
||||
"--config", type=str, required=True, help="Path to the configuration file."
|
||||
)
|
||||
parser.add_argument(
|
||||
# App.instance().add_cmdline_arg(
|
||||
# "--config", type=str, required=True, help="Path to the configuration file."
|
||||
# )
|
||||
App.instance().add_cmdline_arg(
|
||||
"--date_pattern",
|
||||
type=str,
|
||||
required=True,
|
||||
help="Date YYYYMMDD, allows * and ? wildcards",
|
||||
)
|
||||
parser.add_argument(
|
||||
App.instance().add_cmdline_arg(
|
||||
"--instruments",
|
||||
type=str,
|
||||
required=True,
|
||||
help="Comma-separated list of instrument symbols (e.g., COIN:EQUITY,GBTC:CRYPTO)",
|
||||
)
|
||||
parser.add_argument(
|
||||
App.instance().add_cmdline_arg(
|
||||
"--result_db",
|
||||
type=str,
|
||||
required=True,
|
||||
help="Path to SQLite database for storing results. Use 'NONE' to disable database output.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
config: Dict = load_config(args.config)
|
||||
App.instance().add_call(stage=App.Stage.Config, func=self._on_config())
|
||||
App.instance().add_call(stage=App.Stage.Run, func=self.run())
|
||||
|
||||
async def _on_config(self) -> None:
|
||||
# Resolve data files (CLI takes priority over config)
|
||||
instruments = get_instruments(args, config)
|
||||
datafiles = resolve_datafiles(config, args.date_pattern, instruments)
|
||||
instruments: List[ExchangeInstrument] = self._get_instruments()
|
||||
datafiles = resolve_datafiles(
|
||||
config=CvttAppConfig.instance().to_dict(),
|
||||
date_pattern=App.instance().get_argument("date_pattern"),
|
||||
instruments=instruments,
|
||||
)
|
||||
|
||||
days = list(set([day for day, _ in datafiles]))
|
||||
print(f"Found {len(datafiles)} data files to process:")
|
||||
@ -55,35 +69,36 @@ def main() -> None:
|
||||
print(f" - {df}")
|
||||
|
||||
# Create result database if needed
|
||||
if args.result_db.upper() != "NONE":
|
||||
args.result_db = expand_filename(args.result_db)
|
||||
create_result_database(args.result_db)
|
||||
if App.instance().get_argument("result_db").upper() != "NONE":
|
||||
create_result_database(App.instance().get_argument("result_db"))
|
||||
|
||||
# Initialize a dictionary to store all trade results
|
||||
all_results: Dict[str, Dict[str, Any]] = {}
|
||||
is_config_stored = False
|
||||
# Process each data file
|
||||
|
||||
results = PairResearchResult(config=config)
|
||||
results = PairResearchResult(config=CvttAppConfig.instance().to_dict())
|
||||
for day in sorted(days):
|
||||
md_datafiles = [datafile for md_day, datafile in datafiles if md_day == day]
|
||||
if not all([os.path.exists(datafile) for datafile in md_datafiles]):
|
||||
print(f"WARNING: insufficient data files: {md_datafiles}")
|
||||
continue
|
||||
exit(1)
|
||||
print(f"\n====== Processing {day} ======")
|
||||
|
||||
if not is_config_stored:
|
||||
store_config_in_database(
|
||||
db_path=args.result_db,
|
||||
config_file_path=args.config,
|
||||
config=config,
|
||||
db_path=App.instance().get_argument("result_db"),
|
||||
config_file_path=App.instance().get_argument("config"),
|
||||
config=CvttAppConfig.instance().to_dict(),
|
||||
datafiles=datafiles,
|
||||
instruments=instruments,
|
||||
)
|
||||
is_config_stored = True
|
||||
|
||||
CvttAppConfig.instance().set_value("datafiles", md_datafiles)
|
||||
pt_strategy = PtResearchStrategy(
|
||||
config=config, datafiles=md_datafiles, instruments=instruments
|
||||
config=CvttAppConfig.instance(),
|
||||
instruments=instruments,
|
||||
)
|
||||
pt_strategy.run()
|
||||
results.add_day_results(
|
||||
@ -92,15 +107,33 @@ def main() -> None:
|
||||
outstanding_positions=pt_strategy.outstanding_positions(),
|
||||
)
|
||||
|
||||
|
||||
results.analyze_pair_performance()
|
||||
|
||||
def _get_instruments(self) -> List[ExchangeInstrument]:
|
||||
res: List[ExchangeInstrument] = []
|
||||
|
||||
if args.result_db.upper() != "NONE":
|
||||
print(f"\nResults stored in database: {args.result_db}")
|
||||
for inst in App.instance().get_argument("instruments").split(","):
|
||||
instrument_type = inst.split(":")[0]
|
||||
exchange_id = inst.split(":")[1]
|
||||
instrument_id = inst.split(":")[2]
|
||||
exch_inst: ExchangeInstrument = Instruments.instance().get_exch_inst(
|
||||
exch_id=exchange_id, inst_id=instrument_id, src=f"{self.fname()}"
|
||||
)
|
||||
exch_inst.user_data_["instrument_type"] = instrument_type
|
||||
res.append(exch_inst)
|
||||
|
||||
return res
|
||||
|
||||
async def run(self) -> None:
|
||||
|
||||
if App.instance().get_argument("result_db").upper() != "NONE":
|
||||
print(
|
||||
f'\nResults stored in database: {App.instance().get_argument("result_db")}'
|
||||
)
|
||||
else:
|
||||
print("No results to display.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Runner()
|
||||
App.instance().run()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,94 +0,0 @@
|
||||
import glob
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import pandas as pd
|
||||
from pairs_trading.lib.pt_trading.fit_method import PairsTradingFitMethod
|
||||
|
||||
|
||||
def resolve_datafiles(config: Dict, cli_datafiles: Optional[str] = None) -> List[str]:
|
||||
"""
|
||||
Resolve the list of data files to process.
|
||||
CLI datafiles take priority over config datafiles.
|
||||
Supports wildcards in config but not in CLI.
|
||||
"""
|
||||
if cli_datafiles:
|
||||
# CLI override - comma-separated list, no wildcards
|
||||
datafiles = [f.strip() for f in cli_datafiles.split(",")]
|
||||
# Make paths absolute relative to data directory
|
||||
data_dir = config.get("data_directory", "./data")
|
||||
resolved_files = []
|
||||
for df in datafiles:
|
||||
if not os.path.isabs(df):
|
||||
df = os.path.join(data_dir, df)
|
||||
resolved_files.append(df)
|
||||
return resolved_files
|
||||
|
||||
# Use config datafiles with wildcard support
|
||||
config_datafiles = config.get("datafiles", [])
|
||||
data_dir = config.get("data_directory", "./data")
|
||||
resolved_files = []
|
||||
|
||||
for pattern in config_datafiles:
|
||||
if "*" in pattern or "?" in pattern:
|
||||
# Handle wildcards
|
||||
if not os.path.isabs(pattern):
|
||||
pattern = os.path.join(data_dir, pattern)
|
||||
matched_files = glob.glob(pattern)
|
||||
resolved_files.extend(matched_files)
|
||||
else:
|
||||
# Handle explicit file path
|
||||
if not os.path.isabs(pattern):
|
||||
pattern = os.path.join(data_dir, pattern)
|
||||
resolved_files.append(pattern)
|
||||
|
||||
return sorted(list(set(resolved_files))) # Remove duplicates and sort
|
||||
|
||||
|
||||
def create_pairs(
|
||||
datafiles: List[str],
|
||||
fit_method: PairsTradingFitMethod,
|
||||
config: Dict,
|
||||
instruments: List[Dict[str, str]],
|
||||
) -> List:
|
||||
from pt_trading.trading_pair import TradingPair
|
||||
from tools.data_loader import load_market_data
|
||||
|
||||
all_indexes = range(len(instruments))
|
||||
unique_index_pairs = [(i, j) for i in all_indexes for j in all_indexes if i < j]
|
||||
pairs = []
|
||||
|
||||
# Update config to use the specified instruments
|
||||
config_copy = config.copy()
|
||||
config_copy["instruments"] = instruments
|
||||
|
||||
market_data_df = pd.DataFrame()
|
||||
extra_minutes = 0
|
||||
if "execution_price" in config_copy:
|
||||
extra_minutes = config_copy["execution_price"]["shift"]
|
||||
|
||||
for datafile in datafiles:
|
||||
md_df = load_market_data(
|
||||
datafile=datafile,
|
||||
instruments=instruments,
|
||||
db_table_name=config_copy["market_data_loading"][instruments[0]["instrument_type"]]["db_table_name"],
|
||||
trading_hours=config_copy["trading_hours"],
|
||||
extra_minutes=extra_minutes,
|
||||
)
|
||||
market_data_df = pd.concat([market_data_df, md_df])
|
||||
|
||||
if len(set(market_data_df["symbol"])) != 2: # both symbols must be present for a pair
|
||||
print(f"WARNING: insufficient data in files: {datafiles}")
|
||||
return []
|
||||
|
||||
for a_index, b_index in unique_index_pairs:
|
||||
symbol_a=instruments[a_index]["symbol"]
|
||||
symbol_b=instruments[b_index]["symbol"]
|
||||
pair = fit_method.create_trading_pair(
|
||||
config=config_copy,
|
||||
market_data=market_data_df,
|
||||
symbol_a=symbol_a,
|
||||
symbol_b=symbol_b,
|
||||
)
|
||||
pairs.append(pair)
|
||||
return pairs
|
||||
@ -1,111 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
from pairs_trading.lib.pt_strategy.results import (PairResearchResult, create_result_database,
|
||||
store_config_in_database)
|
||||
from pairs_trading.lib.pt_strategy.research_strategy import PtResearchStrategy
|
||||
from pairs_trading.lib.tools.filetools import resolve_datafiles
|
||||
from pairs_trading.lib.tools.instruments import get_instruments
|
||||
from pairs_trading.lib.tools.viz.viz_trades import visualize_trades
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import argparse
|
||||
|
||||
from pairs_trading.lib.tools.config import expand_filename, load_config
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run pairs trading backtest.")
|
||||
parser.add_argument(
|
||||
"--config", type=str, required=True, help="Path to the configuration file."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--date_pattern",
|
||||
type=str,
|
||||
required=True,
|
||||
help="Date YYYYMMDD, allows * and ? wildcards",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--instruments",
|
||||
type=str,
|
||||
required=True,
|
||||
help="Comma-separated list of instrument symbols (e.g., COIN:EQUITY,GBTC:CRYPTO)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--result_db",
|
||||
type=str,
|
||||
required=False,
|
||||
default="NONE",
|
||||
help="Path to SQLite database for storing results. Use 'NONE' to disable database output.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
config: Dict = load_config(args.config)
|
||||
|
||||
# Resolve data files (CLI takes priority over config)
|
||||
instruments = get_instruments(args, config)
|
||||
datafiles = resolve_datafiles(config, args.date_pattern, instruments)
|
||||
|
||||
days = list(set([day for day, _ in datafiles]))
|
||||
print(f"Found {len(datafiles)} data files to process:")
|
||||
for df in datafiles:
|
||||
print(f" - {df}")
|
||||
|
||||
# Create result database if needed
|
||||
if args.result_db.upper() != "NONE":
|
||||
args.result_db = expand_filename(args.result_db)
|
||||
create_result_database(args.result_db)
|
||||
|
||||
# Initialize a dictionary to store all trade results
|
||||
all_results: Dict[str, Dict[str, Any]] = {}
|
||||
is_config_stored = False
|
||||
# Process each data file
|
||||
|
||||
results = PairResearchResult(config=config)
|
||||
for day in sorted(days):
|
||||
md_datafiles = [datafile for md_day, datafile in datafiles if md_day == day]
|
||||
if not all([os.path.exists(datafile) for datafile in md_datafiles]):
|
||||
print(f"WARNING: insufficient data files: {md_datafiles}")
|
||||
continue
|
||||
print(f"\n====== Processing {day} ======")
|
||||
|
||||
if not is_config_stored:
|
||||
store_config_in_database(
|
||||
db_path=args.result_db,
|
||||
config_file_path=args.config,
|
||||
config=config,
|
||||
datafiles=datafiles,
|
||||
instruments=instruments,
|
||||
)
|
||||
is_config_stored = True
|
||||
|
||||
pt_strategy = PtResearchStrategy(
|
||||
config=config, datafiles=md_datafiles, instruments=instruments
|
||||
)
|
||||
pt_strategy.run()
|
||||
results.add_day_results(
|
||||
day=day,
|
||||
trades=pt_strategy.day_trades(),
|
||||
outstanding_positions=pt_strategy.outstanding_positions(),
|
||||
)
|
||||
|
||||
|
||||
results.analyze_pair_performance()
|
||||
|
||||
|
||||
visualize_trades(pt_strategy, results, day)
|
||||
|
||||
|
||||
if args.result_db.upper() != "NONE":
|
||||
print(f"\nResults stored in database: {args.result_db}")
|
||||
else:
|
||||
print("No results to display.")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user