Skip to content
This repository has been archived by the owner on Nov 25, 2024. It is now read-only.

Commit

Permalink
upd
Browse files Browse the repository at this point in the history
  • Loading branch information
m5l14i11 committed Dec 6, 2023
1 parent e580961 commit 547ffe7
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 54 deletions.
18 changes: 11 additions & 7 deletions config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ buf_size = 1000
piority_groups = 2

[backtest]
batch_size = 2584
in_sample = 1M
batch_size = 1597
in_sample = 3M
out_sample = 1M

[position]
risk_reward_ratio = 4.0
risk_reward_ratio = 1.5
risk_buffer = 0.0001
twap_duration = 300
max_order_slice = 8
entry_timeout = 60
stop_loss_threshold = 0.5

[portfolio]
risk_per_trade = 0.0005
Expand All @@ -20,17 +24,17 @@ account_size = 1000
[system]
parallel_num = 2
active_strategy_num = 2
mode = 0
mode = 1
leverage = 1

[generator]
n_samples = 3

[optimization]
max_generations = 1
elite_count = 2
max_generations = 2
elite_count = 3
mutation_rate = 0.02
crossover_rate = 0.8
tournament_size = 2
tournament_size = 3
reset_percentage = 0.2
stability_percentage = 0.3
4 changes: 4 additions & 0 deletions core/interfaces/abstract_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def fetch_ohlcv(
def fetch_position(self, symbol: Symbol):
pass

@abstractmethod
def fetch_trade(self, symbol: Symbol):
pass

@abstractmethod
def create_market_order(self, symbol: Symbol, side: PositionSide, size: float):
pass
Expand Down
6 changes: 3 additions & 3 deletions core/queries/position.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from dataclasses import dataclass
from core.models.order import Order

from core.models.position import Position
from core.queries.base import Query


@dataclass(frozen=True)
class GetOpenPosition(Query[Position]):
class GetOpenPosition(Query[Order]):
position: Position


@dataclass(frozen=True)
class GetClosePosition(Query[Position]):
class GetClosePosition(Query[Order]):
position: Position
exit_price: float
10 changes: 8 additions & 2 deletions exchange/bybit.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,12 @@ def update_symbol_settings(
def fetch_order(self, order_id: str):
return self.connector.fetch_order(order_id)

def fetch_trade(self, symbol: Symbol):
return next(iter(self.connector.fetch_my_trades(symbol.name, limit=1)), None)

def fetch_order_book(self, symbol: Symbol):
return self.connector.fetch_order_book(symbol.name)
book = self.connector.fetch_order_book(symbol.name, limit=150)
return book["bids"], book["asks"]

def create_market_order(self, symbol: Symbol, side: PositionSide, size: float):
res = self._create_order(
Expand All @@ -77,7 +81,9 @@ def create_limit_order(
symbol.name,
size,
price,
self._create_order_extra_params(),
self._create_order_extra_params(
stop_loss_price=None, take_profit_price=None
),
)

return res["info"]["orderId"]
Expand Down
10 changes: 7 additions & 3 deletions executor/market_order_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ async def _execute_order(self, event: PositionInitialized):

await self.ask(OpenPosition(current_position))

current_position = await self.ask(GetOpenPosition(current_position))
order = await self.ask(GetOpenPosition(current_position))

current_position = current_position.add_order(order)

logger.info(f"Opened Position: {current_position}")

Expand All @@ -61,10 +63,12 @@ async def _close_position(self, event: PositionCloseRequested):

await self.ask(ClosePosition(current_position))

current_position = await self.ask(
GetClosePosition(current_position, event.exit_price)
order = await self.ask(
GetClosePosition(current_position)
)

current_position = current_position.add_order(order)

logger.info(f"Closed Position: {current_position}")

await self.tell(BrokerPositionClosed(current_position))
Empty file added portfolio/__init__.py
Empty file.
46 changes: 24 additions & 22 deletions portfolio/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,10 @@ def __init__(self, config_service: AbstractConfig):
self.config = config_service.get("portfolio")
self.account_size = self.config["account_size"]

@command_handler(UpdateAccountSize)
async def update_account_size(self, command: UpdateAccountSize):
self.account_size = command.amount

await self.dispatch(PortfolioAccountUpdated(self.account_size))

@event_handler(BacktestStarted)
async def handle_backtest_started(self, event: BacktestStarted):
await self.state.reset(event.symbol, event.timeframe, event.strategy)

@command_handler(PortfolioReset)
async def portfolio_reset(self, _event: PortfolioReset):
await self.state.reset_all()

@event_handler(PositionClosed)
async def handle_close_positon(self, event: PositionClosed):
await self.state.next(
Expand All @@ -57,28 +47,30 @@ async def handle_close_positon(self, event: PositionClosed):

performance = await self.state.get(event.position)
logger.info(
f"Performance: strategy={symbol}_{timeframe}{strategy}, trades={performance.total_trades}, cagr={round(performance.cagr * 100, 2)}%, pnl={round(performance.total_pnl, 2)}"
f"Performance: strategy={symbol}_{timeframe}{strategy}, trades={performance.total_trades}, cagr={round(performance.cagr * 100, 2)}%, pnl={round(performance.total_pnl, 3)}"
)

await self.dispatch(
PortfolioPerformanceUpdated(symbol, timeframe, strategy, performance)
)

performance_metrics = [
performance.calmar_ratio,
performance.cvar,
performance.ulcer_index,
performance.max_drawdown,
performance.annualized_return,
performance.sterling_ratio,
performance.sortino_ratio,
performance.burke_ratio,
performance.average_pnl,
]

await self.strategy.next(
symbol,
timeframe,
strategy,
[
performance.calmar_ratio,
performance.cvar,
performance.ulcer_index,
performance.max_drawdown,
performance.annualized_return,
performance.sterling_ratio,
performance.sortino_ratio,
performance.burke_ratio,
performance.average_pnl,
],
performance_metrics,
)

@query_handler(GetTopStrategy)
Expand Down Expand Up @@ -114,3 +106,13 @@ async def fitness(self, query: GetFitness):
return await self.state.get_fitness(
query.symbol, query.timeframe, query.strategy
)

@command_handler(UpdateAccountSize)
async def update_account_size(self, command: UpdateAccountSize):
self.account_size = command.amount

await self.dispatch(PortfolioAccountUpdated(self.account_size))

@command_handler(PortfolioReset)
async def portfolio_reset(self, _event: PortfolioReset):
await self.state.reset_all()
63 changes: 46 additions & 17 deletions sor/_router.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,61 @@
import asyncio
import logging

from core.commands.broker import ClosePosition, OpenPosition, UpdateSettings
from core.event_decorators import command_handler, query_handler
from core.interfaces.abstract_config import AbstractConfig
from core.interfaces.abstract_event_manager import AbstractEventManager
from core.interfaces.abstract_exhange_factory import AbstractExchangeFactory
from core.models.exchange import ExchangeType
from core.models.order import Order, OrderStatus
from core.models.position import PositionSide
from core.queries.account import GetBalance
from core.queries.broker import GetSymbol, GetSymbols
from core.queries.position import GetClosePosition, GetOpenPosition

from ._twap import TWAP

logger = logging.getLogger(__name__)


class SmartRouter(AbstractEventManager):
def __init__(self, exchange_factory: AbstractExchangeFactory):
def __init__(
self, exchange_factory: AbstractExchangeFactory, config_service: AbstractConfig
):
super().__init__()
self.exchange_factory = exchange_factory
self.exchange = self.exchange_factory.create(ExchangeType.BYBIT)
self.entry_price = TWAP(config_service)
self.config = config_service.get("position")

@query_handler(GetOpenPosition)
def get_open_position(self, query: GetOpenPosition):
position = query.position

if position.closed:
return position

broker_position = self.exchange.fetch_position(position.signal.symbol)

if not broker_position:
order = Order(status=OrderStatus.FAILED, price=0, size=0)
return Order(status=OrderStatus.FAILED, price=0, size=0)
else:
order = Order(
return Order(
status=OrderStatus.EXECUTED,
size=broker_position["position_size"],
price=broker_position["entry_price"],
)

return position.add_order(order)

@query_handler(GetClosePosition)
def get_close_position(self, query: GetClosePosition):
position = query.position

symbol = position.signal.symbol

order = Order(
status=OrderStatus.CLOSED, size=position.size, price=query.exit_price
)
trade = self.exchange.fetch_trade(symbol)

return position.add_order(order)
logging.info(f"Trade: {trade}")

return Order(
status=OrderStatus.CLOSED, size=trade["amount"], price=trade["price"]
)

@query_handler(GetSymbols)
def get_symbols(self, _query: GetSymbols):
Expand All @@ -70,16 +78,37 @@ def update_symbol_settings(self, command: UpdateSettings):
)

@command_handler(OpenPosition)
def open_position(self, command: OpenPosition):
async def open_position(self, command: OpenPosition):
position = command.position

if position.closed:
return

symbol = position.signal.symbol
side = position.side
size = position.size
position_side = position.side
position_size = position.size

min_size = symbol.min_position_size
max_order_slice = self.config["max_order_slice"]

num_orders = min(max(1, int(position_size / min_size)), max_order_slice)
size = round(position_size / num_orders, symbol.position_precision)
order_counter = 0

for price in self.entry_price.calculate(symbol, self.exchange):
order_id = self.exchange.create_limit_order(
symbol, position_side, size, price
)

logging.info(f"Order ID: {order_id}")

if order_id:
order_counter += 1

order_id = self.exchange.create_market_order(symbol, side, size)
if order_counter >= num_orders:
break

logging.info(f"Order ID: {order_id}")
await asyncio.sleep(self.config["entry_timeout"])

@command_handler(ClosePosition)
def close_position(self, command: ClosePosition):
Expand Down
44 changes: 44 additions & 0 deletions sor/_twap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import time
import numpy as np

from core.interfaces.abstract_config import AbstractConfig
from core.interfaces.abstract_exchange import AbstractExchange
from core.models.symbol import Symbol


class TWAP:
def __init__(self, config_service: AbstractConfig):
self.config = config_service.get("position")

def calculate(self, symbol: Symbol, exchange: AbstractExchange):
current_time = 0
timepoints = []
twap_duration = self.config["twap_duration"]

while current_time < twap_duration:
order_book = self._fetch_book(symbol, exchange)
timepoints.append(order_book)

time_interval = twap_duration / len(order_book)
current_time += time_interval

time.sleep(time_interval)

twap_value = self._twap(timepoints)

yield round(twap_value, symbol.price_precision)

def _fetch_book(self, symbol: Symbol, exchange: AbstractExchange):
bids, asks = exchange.fetch_order_book(symbol)

bids = np.array([[price, size] for price, size in bids])
asks = np.array([[price, size] for price, size in asks])

return np.vstack([bids, asks])

@staticmethod
def _twap(order_book):
timepoints = np.vstack(order_book)
prices = timepoints[:, 0]
total_timepoints = len(prices)
return np.sum(prices) / total_timepoints

0 comments on commit 547ffe7

Please sign in to comment.