def _convert_to_numpy_array(self, arr, name) -> np.ndarray: if type(arr) is np.ndarray: return arr try: # create numpy array from list arr = np.array(arr, dtype=float) if jh.is_live(): # in livetrade mode, we'll need them rounded price = arr[0][1] price_precision = selectors.get_exchange( self.exchange).vars['precisions'][ self.symbol]['price_precision'] qty_precision = selectors.get_exchange( self.exchange).vars['precisions'][ self.symbol]['qty_precision'] prices = jh.round_price_for_live_mode(price, arr[:, 1], price_precision) qtys = jh.round_qty_for_live_mode(price, arr[:, 0], qty_precision) arr[:, 0] = qtys arr[:, 1] = prices return arr except ValueError: raise exceptions.InvalidShape( f'The format of {name} is invalid. \n' f'It must be (qty, price) or [(qty, price), (qty, price)] for multiple points; but {arr} was given' )
def execute(self, silent=False) -> None: if self.is_canceled or self.is_executed: return self.executed_at = jh.now_to_timestamp() self.status = order_statuses.EXECUTED if jh.is_live(): self.save() if not silent: txt = f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}' if self.price: txt += f', ${round(self.price, 2)}' # log if jh.is_debuggable('order_execution'): logger.info(txt) # notify if jh.is_live(): self.broadcast() if config['env']['notifications']['events']['executed_orders']: notify(txt) p = selectors.get_position(self.exchange, self.symbol) if p: p._on_executed_order(self) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_execution(self)
def cancel(self, silent=False) -> None: if self.is_canceled or self.is_executed: return self.canceled_at = jh.now_to_timestamp() self.status = order_statuses.CANCELED if jh.is_live(): self.save() if not silent: txt = f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}' if self.price: txt += f', ${round(self.price, 2)}' if jh.is_debuggable('order_cancellation'): logger.info(txt) if jh.is_live(): self.broadcast() if config['env']['notifications']['events'][ 'cancelled_orders']: notify(txt) # handle exchange balance e = selectors.get_exchange(self.exchange) e.on_order_cancellation(self)
def __init__(self, attributes: dict = None, **kwargs) -> None: Model.__init__(self, attributes=attributes, **kwargs) if attributes is None: attributes = {} for a, value in attributes.items(): setattr(self, a, value) if self.created_at is None: self.created_at = jh.now_to_timestamp() if jh.is_live(): from jesse.store import store self.session_id = store.app.session_id self.save(force_insert=True) if jh.is_live(): self.notify_submission() if jh.is_debuggable('order_submission'): txt = f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, {self.qty}' if self.price: txt += f', ${round(self.price, 2)}' logger.info(txt) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_submission(self)
def execute(self): if self.is_canceled or self.is_executed: return self.executed_at = jh.now_to_timestamp() self.status = order_statuses.EXECUTED # log if jh.is_debuggable('order_execution'): logger.info('EXECUTED order: {}, {}, {}, {}, ${}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2))) # notify if jh.is_live( ) and config['env']['notifications']['events']['executed_orders']: notify('EXECUTED order: {}, {}, {}, {}, {}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2))) p = selectors.get_position(self.exchange, self.symbol) if p: p._on_executed_order(self) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_execution(self)
def __init__(self, attributes=None, **kwargs)-> None: Model.__init__(self, attributes=attributes, **kwargs) if attributes is None: attributes = {} for a in attributes: setattr(self, a, attributes[a]) if self.created_at is None: self.created_at = jh.now_to_timestamp() if jh.is_live() and config['env']['notifications']['events']['submitted_orders']: self.notify_submission() if jh.is_debuggable('order_submission'): logger.info( '{} order: {}, {}, {}, {}, ${}'.format( 'QUEUED' if self.is_queued else 'SUBMITTED', self.symbol, self.type, self.side, self.qty, round(self.price, 2) ) ) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_submission(self)
def set_up_without_fee(is_margin_trading=False): reset_config() config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'margin' config['env']['exchanges'][exchanges.SANDBOX]['fee'] = 0 config['env']['exchanges'][exchanges.SANDBOX]['assets'] = [ { 'asset': 'USDT', 'balance': 1000 }, { 'asset': 'BTC', 'balance': 0 }, ] if is_margin_trading: # used only in margin trading config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'margin' config['env']['exchanges'][ exchanges.SANDBOX]['settlement_currency'] = 'USDT' config['app']['trading_mode'] = 'backtest' config['app']['considering_exchanges'] = ['Sandbox'] router.set_routes([(exchanges.SANDBOX, 'BTC-USDT', '5m', 'Test19')]) store.reset(True) global position global exchange position = selectors.get_position(exchanges.SANDBOX, 'BTC-USDT') position.current_price = 50 exchange = selectors.get_exchange(exchanges.SANDBOX)
def paper(debug, dev, fee): """ trades in real-time on exchange with PAPER money """ validate_cwd() # set trading mode from jesse.config import config config['app']['trading_mode'] = 'papertrade' register_custom_exception_handler() # debug flag config['app']['debug_mode'] = debug from plugins.live import init from jesse.services.selectors import get_exchange # fee flag if not fee: for e in config['app']['trading_exchanges']: config['env']['exchanges'][e]['fee'] = 0 get_exchange(e).fee = 0 # inject live config init(config) # execute live session from plugins.live.live_mode import run run(dev)
def test_decrease_balance(): set_up() e = selectors.get_exchange(exchanges.SANDBOX) assert e.balance == 2000 e.decrease_balance(None, 100) assert e.balance == 1900
def backtest(start_date: str, finish_date: str, debug: bool, csv: bool, json: bool, fee: bool, chart: bool, tradingview: bool, full_reports: bool) -> None: """ backtest mode. Enter in "YYYY-MM-DD" "YYYY-MM-DD" """ validate_cwd() from jesse.config import config config['app']['trading_mode'] = 'backtest' register_custom_exception_handler() from jesse.services import db from jesse.modes import backtest_mode from jesse.services.selectors import get_exchange # debug flag config['app']['debug_mode'] = debug # fee flag if not fee: for e in config['app']['trading_exchanges']: config['env']['exchanges'][e]['fee'] = 0 get_exchange(e).fee = 0 backtest_mode.run(start_date, finish_date, chart=chart, tradingview=tradingview, csv=csv, json=json, full_reports=full_reports) db.close_connection()
def __init__(self, exchange_name: str, symbol: str, attributes=None) -> None: self.id = jh.generate_unique_id() self.entry_price = None self.exit_price = None self.current_price = None self.qty = 0 self.opened_at = None self.closed_at = None # TODO: self._mark_price = None if attributes is None: attributes = {} self.exchange_name = exchange_name self.exchange: Exchange = selectors.get_exchange(self.exchange_name) self.symbol = symbol self.strategy = None for a in attributes: setattr(self, a, attributes[a])
def test_negative_balance_validation(): with pytest.raises(NegativeBalance): set_up() e = selectors.get_exchange(exchanges.SANDBOX) assert e.balance == 2000 e.decrease_balance(None, 3000)
def live(testdrive: bool, debug: bool, dev: bool, fee: bool) -> None: """ trades in real-time on exchange with REAL money """ validate_cwd() # set trading mode from jesse.config import config config['app']['trading_mode'] = 'livetrade' config['app']['is_test_driving'] = testdrive register_custom_exception_handler() # debug flag config['app']['debug_mode'] = debug from plugins.live import init from jesse.services.selectors import get_exchange # fee flag if not fee: for e in config['app']['trading_exchanges']: config['env']['exchanges'][e]['fee'] = 0 get_exchange(e).fee = 0 # inject live config init(config) # execute live session from plugins.live.live_mode import run run(dev)
def set_up_with_fee(is_futures_trading=False): reset_config() config['env']['exchanges'][exchanges.SANDBOX]['fee'] = 0.002 config['env']['exchanges'][exchanges.SANDBOX]['assets'] = [ { 'asset': 'USDT', 'balance': 1000 }, { 'asset': 'BTC', 'balance': 0 }, ] if is_futures_trading: # used only in futures trading config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'futures' else: config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'spot' config['env']['exchanges'][ exchanges.SANDBOX]['settlement_currency'] = 'USDT' config['app']['trading_mode'] = 'backtest' config['app']['considering_exchanges'] = ['Sandbox'] router.set_routes([(exchanges.SANDBOX, 'BTC-USDT', '5m', 'Test19')]) store.reset(True) global position global exchange global broker position = selectors.get_position(exchanges.SANDBOX, 'BTC-USDT') position.current_price = 50 exchange = selectors.get_exchange(exchanges.SANDBOX) broker = Broker(position, exchanges.SANDBOX, 'BTC-USDT', timeframes.MINUTE_5)
def generate_signal(start_date, finish_date, reports_folder, debug, csv, json, fee, chart, tradingview): """ backtest mode. Enter in "YYYY-MM-DD" "YYYY-MM-DD" """ validate_cwd() from jesse.config import config config['app']['trading_mode'] = 'backtest' register_custom_exception_handler() from jesse.services import db from jesse.modes import backtest_mode from jesse.services.selectors import get_exchange # debug flag config['app']['debug_mode'] = debug # fee flag if not fee: for e in config['app']['trading_exchanges']: config['env']['exchanges'][e]['fee'] = 0 get_exchange(e).fee = 0 print(f"Generating report for {finish_date}") todays_signal_report = backtest_mode.generate_signal( start_date, finish_date, chart=chart, tradingview=tradingview, csv=csv, json=json) previous_report_file = Path( reports_folder) / f"{jh.get_previous_reports_file_name()}.json" print(f"Loading {previous_report_file} for comparison") previous_signal_report = jh.load_report( previous_report_file) if os.path.exists(previous_report_file) else None is_report_different = jh.are_reports_different(previous_signal_report, todays_signal_report) if is_report_different: print("Posting to slack") slack_report = jh.construct_slack_report(previous_signal_report, todays_signal_report) print(slack_report) jh.post_slack_notification(slack_report) current_report_file_name = Path( reports_folder) / f"{jh.get_current_report_file_name()}.json" print(f"Dumping to {current_report_file_name}") jh.dump_report(todays_signal_report, current_report_file_name) db.close_connection()
def _init_objects(self) -> None: """ This method gets called after right creating the Strategy object. It is just a workaround as a part of not being able to set them inside self.__init__() for the purpose of removing __init__() methods from strategies. """ self.position = selectors.get_position(self.exchange, self.symbol) self.broker = Broker(self.position, self.exchange, self.symbol, self.timeframe) if jh.is_live(): self.price_precision = selectors.get_exchange( self.exchange).vars['precisions'][ self.symbol]['price_precision'] self.qty_precision = selectors.get_exchange( self.exchange).vars['precisions'][self.symbol]['qty_precision'] if self.hp is None: if len(self.hyperparameters()) > 0: self.hp = {} for dna in self.hyperparameters(): self.hp[dna['name']] = dna['default']
def test_reduce_a_short_position(): set_up() p = Position(exchanges.SANDBOX, 'BTCUSD', { 'entry_price': 50, 'current_price': 50, 'qty': -2, }) e = selectors.get_exchange('Sandbox') p._reduce(1, 50) assert p.qty == -1 assert e.balance == 1050
def update_position(exchange: str, symbol: str, candle: np.ndarray) -> None: # get position object p = selectors.get_position(exchange, symbol) # for extra_route candles, p == None, hence no further action is required if p is None: return price_precision = 0 if jh.is_live(): price_precision = selectors.get_exchange(exchange).vars['precisions'][symbol]['price_precision'] # update position.current_price p.current_price = jh.round_price_for_live_mode(candle[2], candle[2], price_precision)
def test_is_able_to_close_via_reduce_postion_too(): set_up() p = Position(exchanges.SANDBOX, 'BTCUSD', { 'entry_price': 50, 'current_price': 50, 'qty': 2, }) e = selectors.get_exchange('Sandbox') p._reduce(2, 50) assert p.qty == 0 assert e.balance == 1100
def __init__(self, attributes=None): # id generated by Jesse for database usage self.id = '' # id generated by market, used in live-trade mode self.exchange_id = '' # some exchanges might require even further info self.vars = {} self.symbol = '' self.exchange = '' self.side = '' self.type = '' self.flag = '' self.qty = 0 self.price = 0 self.status = order_statuses.ACTIVE self.created_at = None self.executed_at = None self.canceled_at = None self.role = None if attributes is None: attributes = {} for a in attributes: setattr(self, a, attributes[a]) if self.created_at is None: self.created_at = jh.now_to_timestamp() p = selectors.get_position(self.exchange, self.symbol) if p: if jh.is_live() and config['env']['notifications']['events']['submitted_orders']: self.notify_submission() if jh.is_debuggable('order_submission'): logger.info( '{} order: {}, {}, {}, {}, ${}'.format( 'QUEUED' if self.is_queued else 'SUBMITTED', self.symbol, self.type, self.side, self.qty, round(self.price, 2) ) ) p._on_opened_order(self) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_submission(self)
def test_increase_a_short_position(): set_up() p = Position(exchanges.SANDBOX, 'BTCUSD', { 'entry_price': 50, 'current_price': 50, 'qty': -2, }) e = selectors.get_exchange('Sandbox') p._increase(2, 40) assert p.qty == -4 assert p.entry_price == 45 assert e.balance == 920
def set_up_with_fee(): reset_config() config['env']['exchanges'][exchanges.SANDBOX]['fee'] = 0.002 config['env']['exchanges'][exchanges.SANDBOX]['starting_balance'] = 1000 config['app']['trading_mode'] = 'backtest' config['app']['considering_exchanges'] = ['Sandbox'] router.set_routes([(exchanges.SANDBOX, 'BTCUSD', '5m', 'Test19')]) store.reset(True) global position global exchange global broker position = selectors.get_position(exchanges.SANDBOX, 'BTCUSD') position.current_price = 50 exchange = selectors.get_exchange(exchanges.SANDBOX) broker = Broker(position, exchanges.SANDBOX, 'BTCUSD', timeframes.MINUTE_5)
def test_close_position(): set_up() p = Position(exchanges.SANDBOX, 'BTCUSD', { 'entry_price': 50, 'current_price': 50, 'qty': 2, }) e = selectors.get_exchange('Sandbox') assert p.exit_price is None p._close(50) assert p.qty == 0 assert e.balance == 1100 assert p.entry_price is None assert p.exit_price == 50
def test_open_position(): set_up() p = Position(exchanges.SANDBOX, 'BTCUSD') e = selectors.get_exchange('Sandbox') assert p.qty == 0 assert p.entry_price is None assert p.exit_price is None assert p.current_price is None assert e.balance == 1000 p._open(1, 50) assert p.qty == 1 assert p.entry_price == 50 assert p.exit_price is None assert e.balance == 950
def cancel(self): if self.is_canceled or self.is_executed: return self.canceled_at = jh.now_to_timestamp() self.status = order_statuses.CANCELED if jh.is_debuggable('order_cancellation'): logger.info('CANCELED order: {}, {}, {}, {}, ${}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2))) if jh.is_live( ) and config['env']['notifications']['events']['cancelled_orders']: notify('CANCELED order: {}, {}, {}, {}, {}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2))) # handle exchange balance e = selectors.get_exchange(self.exchange) e.on_order_cancellation(self)
def __init__(self, attributes: dict = None, **kwargs) -> None: Model.__init__(self, attributes=attributes, **kwargs) if attributes is None: attributes = {} for a, value in attributes.items(): setattr(self, a, value) if self.created_at is None: self.created_at = jh.now_to_timestamp() if jh.is_live( ) and config['env']['notifications']['events']['submitted_orders']: self.notify_submission() if jh.is_debuggable('order_submission'): logger.info( f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}' ) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_submission(self)
def livetrade() -> List[Union[List[Union[str, Any]], List[str], List[Union[ str, int]], List[Union[str, Dict[str, Union[str, int]], Dict[ str, str], Dict[str, bool], Dict[str, Union[Dict[str, Union[ int, str, List[Dict[str, Union[str, int]]]]], Dict[str, Union[ float, str, int, List[Dict[str, Union[str, int]]]]]]], Dict[ str, int]]]]]: # sum up balance of all trading exchanges starting_balance = 0 current_balance = 0 for e in store.exchanges.storage: starting_balance += store.exchanges.storage[e].starting_assets[ jh.app_currency()] current_balance += store.exchanges.storage[e].assets[jh.app_currency()] starting_balance = round(starting_balance, 2) current_balance = round(current_balance, 2) arr = [[ 'started at', jh.timestamp_to_arrow(store.app.starting_time).humanize() ], ['current time', jh.timestamp_to_time(jh.now_to_timestamp())[:19]], ['errors/info', f'{len(store.logs.errors)}/{len(store.logs.info)}'], ['active orders', store.orders.count_all_active_orders()], ['open positions', store.positions.count_open_positions()]] # TODO: for now, we assume that we trade on one exchange only. Later, we need to support for more than one exchange at a time first_exchange = selectors.get_exchange(router.routes[0].exchange) if first_exchange.type == 'futures': arr.append([ 'started/current balance', f'{starting_balance}/{current_balance}' ]) else: # loop all trading exchanges for exchange in selectors.get_all_exchanges(): # loop all assets for asset_name, asset_balance in exchange.assets.items(): if asset_name == jh.base_asset(router.routes[0].symbol): current_price = selectors.get_current_price( router.routes[0].exchange, router.routes[0].symbol) arr.append([ f'{asset_name}', f'{round(exchange.available_assets[asset_name], 5)}/{round(asset_balance, 5)} ({jh.format_currency(round(asset_balance * current_price, 2))} { jh.quote_asset(router.routes[0].symbol)})' ]) else: arr.append([ f'{asset_name}', f'{round(exchange.available_assets[asset_name], 5)}/{round(asset_balance, 5)}' ]) # short trades summary if len(store.completed_trades.trades): df = pd.DataFrame.from_records( [t.to_dict() for t in store.completed_trades.trades]) total = len(df) winning_trades = df.loc[df['PNL'] > 0] losing_trades = df.loc[df['PNL'] < 0] pnl = round(df['PNL'].sum(), 2) pnl_percentage = round((pnl / starting_balance) * 100, 2) arr.append([ 'total/winning/losing trades', f'{total}/{len(winning_trades)}/{len(losing_trades)}' ]) arr.append(['PNL (%)', f'${pnl} ({pnl_percentage}%)']) if config['app']['debug_mode']: arr.append(['debug mode', config['app']['debug_mode']]) if config['app']['is_test_driving']: arr.append(['Test Drive', config['app']['is_test_driving']]) return arr
def fee_rate(self) -> float: return selectors.get_exchange(self.exchange).fee_rate
def capital(self): """the current capital in the trading exchange""" return selectors.get_exchange(self.exchange).balance
def capital(self): """the current capital in the trading exchange""" e = selectors.get_exchange(self.exchange) return e.tradable_balance(self.symbol)