def dumps_notify_entry(self, timestamp, strategy_trader): """ Dumps to dict for notify/history. """ return { 'version': self.version(), 'trade': self.trade_type_to_str(), 'id': self.id, 'app-name': strategy_trader.strategy.name, 'app-id': strategy_trader.strategy.identifier, 'timestamp': timestamp, 'symbol': strategy_trader.instrument.market_id, 'way': "entry", 'entry-timeout': timeframe_to_str(self._entry_timeout), 'expiry': self._expiry, 'timeframe': timeframe_to_str(self._timeframe), 'is-user-trade': self._user_trade, 'comment': self._comment, 'direction': self.direction_to_str(), 'order-price': strategy_trader.instrument.format_price(self.op), 'order-qty': strategy_trader.instrument.format_quantity(self.oq), 'stop-loss-price': strategy_trader.instrument.format_price(self.sl), 'take-profit-price': strategy_trader.instrument.format_price(self.tp), 'avg-entry-price': strategy_trader.instrument.format_price(self.aep), 'entry-open-time': self.dump_timestamp(self.eot), 'stats': { 'entry-order-type': order_type_to_str(self._stats['entry-order-type']), } }
def __str__(self): mydate = datetime.fromtimestamp(self.ts) date_str = mydate.strftime('%Y-%m-%d %H:%M:%S') return "tf=%s ts=%s signal=%s dir=%s p=%s sl=%s tp=%s %s" % ( timeframe_to_str(self.timeframe), date_str, self.signal_type_str(), self.direction_str(), self.p, self.sl, self.tp, self.comment)
def __repr__(self): return "%s bid %s/%s/%s/%s ofr %s/%s/%s/%s" % ( timeframe_to_str(self._timeframe), self._bid_open, self._bid_high, self._bid_low, self._bid_close, self._ofr_open, self._ofr_high, self._ofr_low, self._ofr_close)
def update_trades(self, timestamp): """ Update managed trades per instruments and delete terminated trades. """ if not self.trades: return trader = self.strategy.trader() # # for each trade check if the TP or SL is reached and trigger if necessary # with self._trade_mutex: for trade in self.trades: # # managed operation # if trade.has_operations(): mutated = False for operation in trade.operations: mutated |= operation.test_and_operate(trade, self.instrument, trader) if mutated: trade.cleanup_operations() # # active trade # if trade.is_active(): # for statistics usage trade.update_stats(self.instrument.close_exec_price(trade.direction), timestamp) # # asset trade # if trade.trade_type == StrategyTrade.TRADE_BUY_SELL: if trade.is_closed(): continue # process only on active trades if not trade.is_active(): # @todo timeout if not filled before condition... continue if trade.is_closing(): continue if not self.instrument.tradeable: continue if trade.is_dirty: # entry quantity changed need to update the exits orders trade.update_dirty(trader, self.instrument) # potential order exec close price (always close a long) close_exec_price = self.instrument.close_exec_price(Order.LONG) if (trade.tp > 0) and (close_exec_price >= trade.tp) and not trade.has_limit_order(): # take profit trigger stop, close at market (taker fee) if trade.close(trader, self.instrument) > 0: trade.exit_reason = trade.REASON_TAKE_PROFIT_MARKET elif (trade.sl > 0) and (close_exec_price <= trade.sl) and not trade.has_stop_order(): # stop loss trigger stop, close at market (taker fee) if trade.close(trader, self.instrument) > 0: trade.exit_reason = trade.REASON_STOP_LOSS_MARKET # # margin trade # elif trade.trade_type in (StrategyTrade.TRADE_MARGIN, StrategyTrade.TRADE_POSITION, StrategyTrade.TRADE_IND_MARGIN): # process only on active trades if not trade.is_active(): # @todo timeout if not filled before condition... continue if trade.is_closed(): continue if trade.is_closing(): continue if not self.instrument.tradeable: continue if trade.is_dirty: # entry quantity changed need to update the exits orders trade.update_dirty(trader, self.instrument) # potential order exec close price close_exec_price = self.instrument.close_exec_price(trade.direction) if (trade.tp > 0) and ((trade.direction > 0 and close_exec_price >= trade.tp) or (trade.direction < 0 and close_exec_price <= trade.tp)) and not trade.has_limit_order(): # close in profit at market (taker fee) if trade.close(trader, self.instrument) > 0: trade.exit_reason = trade.REASON_TAKE_PROFIT_MARKET elif (trade.sl > 0) and ((trade.direction > 0 and close_exec_price <= trade.sl) or (trade.direction < 0 and close_exec_price >= trade.sl)) and not trade.has_stop_order(): # close a long or a short position at stop-loss level at market (taker fee) if trade.close(trader, self.instrument) > 0: trade.exit_reason = trade.REASON_STOP_LOSS_MARKET # # remove terminated, rejected, canceled and empty trades # mutated = False with self._trade_mutex: for trade in self.trades: if trade.can_delete(): mutated = True # cleanup if necessary before deleting the trade related refs trade.remove(trader, self.instrument) # record the trade for analysis and study if not trade.is_canceled(): # last update of stats before logging trade.update_stats(self.instrument.close_exec_price(trade.direction), timestamp) # realized profit/loss profit_loss = trade.profit_loss - trade.entry_fees_rate() - trade.exit_fees_rate() # perf sommed here it means that its not done during partial closing if profit_loss != 0.0: self._stats['perf'] += profit_loss self._stats['best'] = max(self._stats['best'], profit_loss) self._stats['worst'] = min(self._stats['worst'], profit_loss) if profit_loss <= 0.0: self._stats['cont-loss'] += 1 self._stats['cont-win'] = 1 elif profit_loss > 0.0: self._stats['cont-loss'] = 0 self._stats['cont-win'] += 1 record = { 'id': trade.id, 'eot': trade.entry_open_time, 'xot': trade.exit_open_time, 'freot': trade.first_realized_entry_time, 'frxot': trade.first_realized_exit_time, 'lreot': trade.last_realized_entry_time, 'lrxot': trade.last_realized_exit_time, 'd': trade.direction_to_str(), 'l': self.instrument.format_price(trade.order_price), 'q': self.instrument.format_quantity(trade.order_quantity), 'e': self.instrument.format_quantity(trade.exec_entry_qty), 'x': self.instrument.format_quantity(trade.exec_exit_qty), 'tp': self.instrument.format_price(trade.take_profit), 'sl': self.instrument.format_price(trade.stop_loss), 'tf': timeframe_to_str(trade.timeframe), 'aep': self.instrument.format_price(trade.entry_price), 'axp': self.instrument.format_price(trade.exit_price), 's': trade.state_to_str(), 'b': self.instrument.format_price(trade.best_price()), 'w': self.instrument.format_price(trade.worst_price()), 'bt': trade.best_price_timestamp(), 'wt': trade.worst_price_timestamp(), 'pl': profit_loss, 'fees': trade.entry_fees_rate() + trade.exit_fees_rate(), 'c': trade.get_conditions(), 'label': trade.label, 'rpnl': self.instrument.format_price(trade.unrealized_profit_loss), # once close its realized 'pnlcur': trade.profit_loss_currency } if profit_loss < 0: self._stats['failed'].append(record) elif profit_loss > 0: self._stats['success'].append(record) else: self._stats['roe'].append(record) if self._reporting == StrategyTrader.REPORTING_VERBOSE: try: self.report(trade, False) except Exception as e: error_logger.error(str(e)) # notify if not trade.exit_reason: if trade.exit_price >= trade.take_profit and trade.take_profit > 0: trade.exit_reason = trade.REASON_TAKE_PROFIT_LIMIT elif trade.exit_price <= trade.stop_loss and trade.stop_loss > 0: trade.exit_reason = trade.REASON_STOP_LOSS_MARKET trade.pl = profit_loss self.notify_trade_exit(timestamp, trade) else: if not trade.exit_reason: trade.exit_reason = trade.REASON_CANCELED_TIMEOUT self.notify_trade_exit(timestamp, trade) # recreate the list of trades if mutated: trades_list = [] for trade in self.trades: if not trade.can_delete(): # keep only active and pending trades trades_list.append(trade) self.trades = trades_list
def update_trades(self, timestamp): """ Update managed trades per instruments and delete terminated trades. """ if not self.trades: return trader = self.strategy.trader() # # for each trade check if the TP or SL is reached and trigger if necessary # self.lock() for trade in self.trades: # # managed operation # if trade.has_operations(): mutated = False for operation in trade.operations: mutated |= operation.test_and_operate( trade, self.instrument, trader) if mutated: trade.cleanup_operations() # # active trade # if trade.is_active(): # for statistics usage trade.update_stats( self.instrument.close_exec_price(trade.direction), timestamp) # # asset trade # if trade.trade_type == StrategyTrade.TRADE_BUY_SELL: if trade.is_closed(): continue # process only on active trades if not trade.is_active(): # @todo timeout if not filled before condition... continue if trade.is_closing(): continue if not self.instrument.tradeable: continue # potential order exec close price (always close a long) close_exec_price = self.instrument.close_exec_price(Order.LONG) if trade.tp > 0 and (close_exec_price >= trade.tp): # take profit order # trade.modify_take_profit(trader, market, take_profit) # close at market (taker fee) if trade.close(trader, self.instrument.market_id): # only get it at the last moment market = trader.market(self.instrument.market_id) # estimed profit/loss rate profit_loss_rate = ( close_exec_price - trade.entry_price) / trade.entry_price # estimed maker/taker fee rate for entry and exit if trade.get_stats()['entry-maker']: profit_loss_rate -= market.maker_fee else: profit_loss_rate -= market.taker_fee if trade.get_stats()['exit-maker']: profit_loss_rate -= market.maker_fee else: profit_loss_rate -= market.taker_fee # notify self.strategy.notify_order( trade.id, Order.SHORT, self.instrument.market_id, market.format_price(close_exec_price), timestamp, trade.timeframe, 'take-profit', profit_loss_rate) # streaming (but must be done with notify) self._global_streamer.member('buy-exit').update( close_exec_price, timestamp) elif trade.sl > 0 and (close_exec_price <= trade.sl): # stop loss order # close at market (taker fee) if trade.close(trader, self.instrument.market_id): # only get it at the last moment market = trader.market(self.instrument.market_id) # estimed profit/loss rate profit_loss_rate = ( close_exec_price - trade.entry_price) / trade.entry_price # estimed maker/taker fee rate for entry and exit if trade.get_stats()['entry-maker']: profit_loss_rate -= market.maker_fee else: profit_loss_rate -= market.taker_fee if trade.get_stats()['exit-maker']: profit_loss_rate -= market.maker_fee else: profit_loss_rate -= market.taker_fee # notify self.strategy.notify_order( trade.id, Order.SHORT, self.instrument.market_id, market.format_price(close_exec_price), timestamp, trade.timeframe, 'stop-loss', profit_loss_rate) # streaming (but must be done with notify) self._global_streamer.member('buy-exit').update( close_exec_price, timestamp) # # margin trade # elif trade.trade_type == StrategyTrade.TRADE_MARGIN or trade.trade_type == StrategyTrade.TRADE_IND_MARGIN: # process only on active trades if not trade.is_active(): # @todo timeout if not filled before condition... continue if trade.is_closed(): continue if trade.is_closing(): continue if not self.instrument.tradeable: continue # potential order exec close price close_exec_price = self.instrument.close_exec_price( trade.direction) if (trade.tp > 0) and (trade.direction > 0 and close_exec_price >= trade.tp) or ( trade.direction < 0 and close_exec_price <= trade.tp): # close in profit at market (taker fee) if trade.close(trader, self.instrument.market_id): # only get it at the last moment market = trader.market(self.instrument.market_id) # estimed profit/loss rate if trade.direction > 0 and trade.entry_price: profit_loss_rate = ( close_exec_price - trade.entry_price) / trade.entry_price elif trade.direction < 0 and trade.entry_price: profit_loss_rate = ( trade.entry_price - close_exec_price) / trade.entry_price else: profit_loss_rate = 0 # estimed maker/taker fee rate for entry and exit if trade.get_stats()['entry-maker']: profit_loss_rate -= market.maker_fee else: profit_loss_rate -= market.taker_fee if trade.get_stats()['exit-maker']: profit_loss_rate -= market.maker_fee else: profit_loss_rate -= market.taker_fee # and notify self.strategy.notify_order( trade.id, trade.close_direction(), self.instrument.market_id, market.format_price(close_exec_price), timestamp, trade.timeframe, 'take-profit', profit_loss_rate) # and for streaming self._global_streamer.member( 'sell-exit' if trade.direction < 0 else 'buy-exit' ).update(close_exec_price, timestamp) elif (trade.sl > 0) and (trade.direction > 0 and close_exec_price <= trade.sl) or ( trade.direction < 0 and close_exec_price >= trade.sl): # close a long or a short position at stop-loss level at market (taker fee) if trade.close(trader, self.instrument.market_id): # only get it at the last moment market = trader.market(self.instrument.market_id) # estimed profit/loss rate if trade.direction > 0 and trade.entry_price: profit_loss_rate = ( close_exec_price - trade.entry_price) / trade.entry_price elif trade.direction < 0 and trade.entry_price: profit_loss_rate = ( trade.entry_price - close_exec_price) / trade.entry_price else: profit_loss_rate = 0 # estimed maker/taker fee rate for entry and exit if trade.get_stats()['entry-maker']: profit_loss_rate -= market.maker_fee else: profit_loss_rate -= market.taker_fee if trade.get_stats()['exit-maker']: profit_loss_rate -= market.maker_fee else: profit_loss_rate -= market.taker_fee # and notify self.strategy.notify_order( trade.id, trade.close_direction(), self.instrument.market_id, market.format_price(close_exec_price), timestamp, trade.timeframe, 'stop-loss', profit_loss_rate) # and for streaming self._global_streamer.member( 'sell-exit' if trade.direction < 0 else 'buy-exit' ).update(close_exec_price, timestamp) self.unlock() # # remove terminated, rejected, canceled and empty trades # mutated = False self.lock() for trade in self.trades: if trade.can_delete(): mutated = True # cleanup if necessary before deleting the trade related refs, and add them to the deletion list trade.remove(trader) # record the trade for analysis and learning if not trade.is_canceled(): # @todo all this part could be in an async method of another background service, because # it is not part of the trade managemnt neither strategy computing, its purely for reporting # and view then we could add a list of the deleted trade (producer) and having another service (consumer) doing the rest # estimation on mid last price, but might be close market price market = trader.market(self.instrument.market_id) # rate = (trade.best_price() - trade.entry_price) / trade.entry_price # for best missed profit rate = trade.profit_loss # realized profit/loss # fee rate for entry and exit if trade._stats['entry-maker']: rate -= market.maker_fee else: rate -= market.taker_fee if trade._stats['exit-maker']: rate -= market.maker_fee else: rate -= market.taker_fee # estimed commission fee rate (futur, stocks) # @todo # perf sommed here it means that its not done during partial closing if rate != 0.0: self._stats['perf'] += rate self._stats['worst'] = min(self._stats['worst'], rate) self._stats['best'] = max(self._stats['best'], rate) if rate <= 0.0: self._stats['cont-loss'] += 1 self._stats['cont-win'] = 1 elif rate > 0.0: self._stats['cont-loss'] = 0 self._stats['cont-win'] += 1 record = { 'id': trade.id, 'ts': trade.entry_open_time, 'd': trade.direction_to_str(), 'p': market.format_price(trade.entry_price), 'q': market.format_quantity(trade.order_quantity), 'e': market.format_quantity(trade.exec_entry_qty), 'x': market.format_quantity(trade.exec_exit_qty), 'tp': market.format_price(trade.take_profit), 'sl': market.format_price(trade.stop_loss), 'tf': timeframe_to_str(trade.timeframe), 'aep': market.format_price(trade.entry_price), 'axp': market.format_price(trade.exit_price), 's': trade.state_to_str(), 'b': market.format_price(trade.best_price()), 'w': market.format_price(trade.worst_price()), 'bt': trade.best_price_timestamp(), 'wt': trade.worst_price_timestamp(), 'rate': rate, 'c': trade.get_conditions() } if rate < 0: self._stats['failed'].append(record) elif rate > 0: self._stats['success'].append(record) else: self._stats['roe'].append(record) # recreate the list of trades if mutated: trades_list = [] for trade in self.trades: if not trade.can_delete(): # keep only active and pending trades trades_list.append(trade) self.trades = trades_list self.unlock()
def update(self): count = 0 while self._signals: signal = self._signals.popleft() label = "" message = "" icon = "contact-new" now = time.time() audio_alert = None if signal.signal_type == Signal.SIGNAL_SOCIAL_ENTER: # here we only assume that because of what 1broker return to us but should be timestamp in the model entry_date = signal.data.entry_date + timedelta(hours=2) position_timestamp = time.mktime(entry_date.timetuple()) audio_alert = DesktopNotifier.AUDIO_ALERT_SIMPLE if now - position_timestamp > 120 * 60: continue label = "Entry position on %s" % (signal.data.symbol, ) message = "Trader %s enter %s on %s at %s (x%s)" % ( signal.data.author.name if signal.data.author is not None else "???", "long" if signal.data.direction == Position.LONG else "short", signal.data.symbol, signal.data.entry_price, signal.data.leverage) elif signal.signal_type == Signal.SIGNAL_SOCIAL_EXIT: # here we only assume that because of what 1broker return to us but should be timestamp in the model exit_date = signal.data.exit_date + timedelta(hours=2) position_timestamp = time.mktime(exit_date.timetuple()) audio_alert = DesktopNotifier.AUDIO_ALERT_SIMPLE if now - position_timestamp > 120 * 60: continue label = "Exit position on %s" % (signal.data.symbol, ) message = "Trader %s exit %s on %s at %s" % ( signal.data.author.name, "long" if signal.data.direction == Position.LONG else "short", signal.data.symbol, signal.data.exit_price) # # @todo a threshold... or a timelimit # elif signal.signal_type == Signal.SIGNAL_TRADE_ALERT: # icon = "go-down" # label = "Position loss on %s" % (signal.data.symbol,) # audio_alert = DesktopNotifier.AUDIO_ALERT_WARNING # message = "Position %s %s of %s on %s start at %s %s is in regretable loss %s (%s%%) :$" % ( # signal.data.position_id, # "long" if signal.data.direction == Position.LONG else "short", # signal.data.author.name if signal.data.author is not None else "???", # signal.data.trader.name, # signal.data.entry_price, # signal.data.symbol, # signal.data.profit_loss, # signal.data.profit_loss_rate * 100.0) # elif signal.signal_type == Signal.SIGNAL_TRADE_ENJOY: # icon = "go-up" # label = "Position profit on %s" % (signal.data.symbol,) # audio_alert = DesktopNotifier.AUDIO_ALERT_SIMPLE # message = "Position %s %s of %s on %s start at %s %s is in enjoyable profit %s (%s%%) :)" % ( # signal.data.position_id, # "long" if signal.data.direction == Position.LONG else "short", # signal.data.author.name if signal.data.author is not None else "???", # signal.data.trader.name, # signal.data.entry_price, # signal.data.symbol, # signal.data.profit_loss, # signal.data.profit_loss_rate * 100.0) elif signal.signal_type == Signal.SIGNAL_STRATEGY_ENTRY_EXIT: # @todo in addition of entry/exit, modification and a reason of the exit/modification icon = "contact-new" direction = "long" if signal.data[ 'direction'] == Position.LONG else "short" audio_alert = DesktopNotifier.AUDIO_ALERT_SIMPLE if signal.data['action'] == 'stop': audio_alert = DesktopNotifier.AUDIO_ALERT_WARNING ldatetime = datetime.fromtimestamp( signal.data['timestamp']).strftime('%Y-%m-%d %H:%M:%S') label = "Signal %s %s on %s" % ( signal.data['action'], direction, signal.data['symbol'], ) message = "%s@%s (%s) %s %s at %s - #%s in %s" % ( signal.data['symbol'], signal.data['price'], signal.data['trader-name'], signal.data['action'], direction, ldatetime, signal.data['trade-id'], timeframe_to_str(signal.data['timeframe'])) if signal.data['stop-loss']: message += " SL@%s" % (signal.data['stop-loss'], ) if signal.data['take-profit']: message += " TP@%s" % (signal.data['take-profit'], ) if signal.data['rate'] is not None: message += " (%.2f%%)" % ((signal.data['rate'] * 100), ) if self.discord: if signal.data['identifier'] in self._discord_webhook: send_to_discord( self._discord_webhook[signal.data['identifier']], 'CryptoBot', '```' + message + '```') else: send_to_discord(self._discord_webhook['signals'], 'CryptoBot', '```' + message + '```') # log them to the signal view Terminal.inst().notice(message, view="signal") # process sound if not self.backtesting and self.audible and audio_alert: # if repeat for i in range(0, self._alerts[audio_alert[1]]): subprocess.Popen([ 'aplay', '-D', self._audio_device, self._alerts[audio_alert[0]] ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # duration = 1 # second # freq = 440 # Hz # os.system('play --no-show-progress --null --channels 1 synth %s sine %f' % (duration, freq)) if not self.backtesting and self.popups and message: n = notify2.Notification(label, message, icon) n.show() count += 1 if count > 10: # no more than per loop break
def timeframe_to_str(self): return timeframe_to_str(self._timeframe)
def timeframe_to_str(self): if self._timeframe > 0: return timeframe_to_str(self._timeframe) else: return "any"
def process_signal(self, signal): label = "" message = "" icon = "contact-new" now = time.time() alert = None if Signal.SIGNAL_STRATEGY_SIGNAL_ENTRY <= signal.signal_type <= Signal.SIGNAL_STRATEGY_TRADE_UPDATE: icon = "contact-new" direction = "long" if signal.data[ 'direction'] == Position.LONG else "short" alert = DesktopNotifier.AUDIO_ALERT_SIGNAL_NEW if signal.data['way'] == "exit" and 'profit-loss' in signal.data: if signal.data['profit-loss'] < 0.0: alert = DesktopNotifier.AUDIO_ALERT_TRADE_LOST icon = self._alerts[alert][2] else: alert = DesktopNotifier.AUDIO_ALERT_TRADE_WIN icon = self._alerts[alert][2] ldatetime = datetime.fromtimestamp( signal.data['timestamp']).strftime('%Y-%m-%d %H:%M:%S') # generic signal reason action = signal.data['way'] # specified exit reason if action == "exit" and 'stats' in signal.data and 'exit-reason' in signal.data[ 'stats']: action = signal.data['stats']['exit-reason'] label = "Signal %s %s on %s" % ( action, direction, signal.data['symbol'], ) way = '>' if signal.data['way'] == "entry" else '<' exit_reason = signal.data['stats'].get( 'exit-reason', "") if 'stats' in signal.data else "" message = "%s@%s (%s) %s %s at %s - #%s in %s" % ( signal.data['symbol'], signal.data['order-price'], signal.data['app-name'], action, direction, ldatetime, signal.data['id'], timeframe_to_str(signal.data['timeframe'])) if signal.data.get('stop-loss-price'): message += " SL@%s" % (signal.data['stop-loss-price'], ) if signal.data.get('take-profit-price'): message += " TP@%s" % (signal.data['take-profit-price'], ) if signal.data.get('profit-loss') is not None: message += " (%.2f%%)" % ((signal.data['profit-loss'] * 100), ) if signal.data['comment'] is not None: message += " (%s)" % signal.data['comment'] # and in signal logger (@todo to be moved) # signal_logger.info(message) # process sound if not self._backtesting and self._audible and alert is not None: self.play_audio_alert(alert) if not self._backtesting and self._popups and message: if self.notify2: n = self.notify2.Notification(label, message, icon) n.show() elif signal.signal_type == Signal.SIGNAL_MARKET_SIGNAL: return elif signal.signal_type == Signal.SIGNAL_WATCHDOG_TIMEOUT: return elif signal.signal_type == Signal.SIGNAL_WATCHDOG_UNREACHABLE: return
def dumps_notify(self, timestamp, strategy_trader): """ Dumps to dict for notify/history, same format as for StrategyTrade. """ if self.signal == StrategySignal.SIGNAL_EXIT: return { 'version': self.version(), 'trade': "signal", 'id': -1, 'app-name': strategy_trader.strategy.name, 'app-id': strategy_trader.strategy.identifier, 'timestamp': timestamp, 'symbol': strategy_trader.instrument.market_id, 'way': "entry", 'entry-timeout': timeframe_to_str(self.entry_timeout), 'expiry': self.expiry, 'timeframe': timeframe_to_str(self.timeframe), 'is-user-trade': False, 'comment': self._comment, 'direction': self.direction_to_str(), 'order-price': strategy_trader.instrument.format_price(self.p), 'stop-loss-price': strategy_trader.instrument.format_price(self.sl), 'take-profit-price': strategy_trader.instrument.format_price(self.tp), 'entry-open-time': self.dump_timestamp(self.ts), } elif self.signal == StrategySignal.SIGNAL_EXIT: return { 'version': self.version(), 'trade': "signal", 'id': -1, 'app-name': strategy_trader.strategy.name, 'app-id': strategy_trader.strategy.identifier, 'timestamp': timestamp, 'symbol': strategy_trader.instrument.market_id, 'way': "exit", 'entry-timeout': timeframe_to_str(self.entry_timeout), 'expiry': self.expiry, 'timeframe': timeframe_to_str(self.timeframe), 'is-user-trade': False, 'comment': self._comment, 'direction': self.direction_to_str(), 'take-profit-price': strategy_trader.instrument.format_price(self.tp), 'stop-loss-price': strategy_trader.instrument.format_price(self.sl), 'exit-open-time': self.dump_timestamp(self.ts), }
def do_importer(options): tool = Importer() Terminal.inst().info("Starting SIIS importer...") Terminal.inst().flush() # database manager Database.create(options) Database.inst().setup(options) # want speedup the database inserts Database.inst().enable_fetch_mode() filename = options.get('filename') detected_format = FORMAT_UNDEFINED detected_timeframe = None is_mtx_tick = False pathname = pathlib.Path(filename) if not pathname.exists(): error_exit(None, "File %s does not exists" % pathname.name) timeframe = None if not options.get('timeframe'): timeframe = None else: if options['timeframe'] in TIMEFRAME_FROM_STR_MAP: timeframe = TIMEFRAME_FROM_STR_MAP[options['timeframe']] else: try: timeframe = int(options['timeframe']) except: pass src = open(filename, "rt") if filename.endswith(".siis"): detected_format = FORMAT_SIIS elif filename.endswith(".csv"): # detect the format from the first row row = src.readline().rstrip('\n') if row.count('\t') > 0: if row.count( '\t' ) == 5 and row == "<DATE>\t<TIME>\t<BID>\t<ASK>\t<LAST>\t<VOLUME>": detected_format = FORMAT_MT5 detected_timeframe = Instrument.TF_TICK is_mtx_tick = True elif row.count( '\t' ) == 8 and row == "<DATE>\t<TIME>\t<OPEN>\t<HIGH>\t<LOW>\t<CLOSE>\t<TICKVOL>\t<VOL>\t<SPREAD>": detected_format = FORMAT_MT5 is_mtx_tick = False # from filename try to detect the timeframe parts = pathname.name.split('_') if len(parts) >= 2: detected_timeframe = MT5_TIMEFRAMES.get(parts[1]) # ignore the header line elif row.count(',') > 0: if row.count(',') == 4: detected_format = FORMAT_MT4 detected_timeframe = Instrument.TF_TICK is_mtx_tick = True elif row.count(',') == 6: detected_format = FORMAT_MT4 is_mtx_tick = False # from filename try to detect the timeframe parts = pathname.name.split('.') if len(parts) > 0: for mt_tf, tf in MT4_TIMEFRAMES.items(): if parts[0].endswith(mt_tf): detected_timeframe = tf break # reset because first row is data src.seek(0, 0) if detected_format == FORMAT_UNDEFINED: error_exit(src, "Unknown file format") if detected_format in (FORMAT_MT4, FORMAT_MT5): if detected_timeframe is not None and timeframe is None: Terminal.inst().message("Auto-detected timeframe %s" % timeframe_to_str(detected_timeframe)) if detected_timeframe and timeframe and detected_timeframe != timeframe: error_exit( src, "Auto-detected timeframe %s is different of specified timeframe %s" % (timeframe_to_str(detected_timeframe), timeframe_to_str(timeframe))) market_id = "" broker_id = "" # UTC option dates from_date = options.get('from') to_date = options.get('to') if detected_format == FORMAT_SIIS: # first row gives format details header = src.readline() if not header.startswith("format=SIIS\t"): error_exit(src, "Unsupported file format") info = header.split('\t') for nfo in info: k, v = nfo.split('=') if k == "version": if v != "1.0.0": error_exit(src, "Unsupported format version") elif k == "created": pass # informational only elif k == "broker": broker_id = v elif k == "market": market_id = v elif k == "from": pass # informational only elif k == "to": pass # informational only elif k == "timeframe": if v != "any": timeframe = timeframe_from_str(v) else: timeframe = None else: # need broker, market and timeframe broker_id = options.get('broker') market_id = options.get('market') if not broker_id: error_exit(src, "Missing target broker identifier") if not market_id or ',' in market_id: error_exit(src, "Missing or invalid target market identifier") if timeframe is None: if is_mtx_tick: timeframe = Instrument.TF_TICK elif detected_timeframe: timeframe = detected_timeframe else: error_exit(src, "Missing target timeframe") # limited sub-range from_date_str = from_date.strftime( "%Y-%m-%dT%H:%M:%SZ") if from_date else None to_date_str = to_date.strftime("%Y-%m-%dT%H:%M:%SZ") if to_date else None total_count = 0 try: if detected_format == FORMAT_SIIS: cur_timeframe = None cur_from_date = from_date cur_to_date = to_date while 1: row = src.readline() if not row: break row = row.rstrip("\n") if row.startswith("timeframe="): # specify the timeframe of the next rows k, v = row.split('=') cur_timeframe = timeframe_from_str(v) continue if cur_timeframe is None: # need a specified timeframe continue if cur_timeframe == Instrument.TF_TICK: total_count += import_tick_siis_1_0_0( broker_id, market_id, cur_from_date, cur_to_date, row) elif cur_timeframe > 0: total_count += import_ohlc_siis_1_0_0( broker_id, market_id, cur_timeframe, cur_from_date, cur_to_date, row) elif detected_format == FORMAT_MT4: cur_timeframe = timeframe if not is_mtx_tick else Instrument.TF_TICK cur_from_date = from_date cur_to_date = to_date if cur_timeframe == Instrument.TF_TICK: while 1: row = src.readline() if not row: break row = row.rstrip("\n") total_count += import_tick_mt4(tool, broker_id, market_id, cur_from_date, cur_to_date, row) elif cur_timeframe > 0: while 1: row = src.readline() if not row: break row = row.rstrip("\n") total_count += import_ohlc_mt4(broker_id, market_id, cur_timeframe, cur_from_date, cur_to_date, row) elif detected_format == FORMAT_MT5: cur_timeframe = timeframe if not is_mtx_tick else Instrument.TF_TICK cur_from_date = from_date cur_to_date = to_date if cur_timeframe == Instrument.TF_TICK: while 1: row = src.readline() if not row: break row = row.rstrip("\n") total_count += import_tick_mt5(tool, broker_id, market_id, cur_from_date, cur_to_date, row) elif cur_timeframe > 0: while 1: row = src.readline() if not row: break row = row.rstrip("\n") total_count += import_ohlc_mt5(broker_id, market_id, cur_timeframe, cur_from_date, cur_to_date, row) except Exception as e: error_logger.error(str(e)) finally: src.close() src = None Terminal.inst().info("Imported %s samples" % (total_count)) Terminal.inst().info("Flushing database...") Terminal.inst().flush() Database.terminate() Terminal.inst().info("Importation done!") Terminal.inst().flush() Terminal.terminate() sys.exit(0)
def do_rebuilder(options): Terminal.inst().info("Starting SIIS rebuilder using %s identity..." % options['identity']) Terminal.inst().flush() # database manager Database.create(options) Database.inst().setup(options) # want speedup the database inserts Database.inst().enable_fetch_mode() timeframe = -1 cascaded = None if not options.get('timeframe'): timeframe = 60 # default to 1min else: if options['timeframe'] in TIMEFRAME_FROM_STR_MAP: timeframe = TIMEFRAME_FROM_STR_MAP[options['timeframe']] else: try: timeframe = int(options['timeframe']) except: pass if not options.get('cascaded'): cascaded = None else: if options['cascaded'] in TIMEFRAME_FROM_STR_MAP: cascaded = TIMEFRAME_FROM_STR_MAP[options['cascaded']] else: try: cascaded = int(options['cascaded']) except: pass if timeframe < 0: error_logger.error("Invalid timeframe") sys.exit(-1) from_date = options.get('from') to_date = options.get('to') if not to_date: today = datetime.now().astimezone(UTC()) if timeframe == Instrument.TF_MONTH: to_date = today + timedelta(months=1) else: to_date = today + timedelta(seconds=timeframe) to_date = to_date.replace(microsecond=0) if timeframe > 0 and timeframe not in GENERATED_TF: logger.error("Timeframe %s is not allowed !" % timeframe_to_str(timeframe)) sys.exit(-1) for market in options['market'].split(','): if market.startswith('!') or market.startswith('*'): continue timestamp = from_date.timestamp() to_timestamp = to_date.timestamp() progression = 0.0 prev_update = timestamp count = 0 total_count = 0 progression_incr = (to_timestamp - timestamp) * 0.01 tts = 0.0 prev_tts = 0.0 generators = [] from_tf = timeframe last_ticks = [] last_ohlcs = {} if timeframe == Instrument.TF_TICK: tick_streamer = Database.inst().create_tick_streamer( options['broker'], market, from_date=from_date, to_date=to_date) else: ohlc_streamer = Database.inst().create_ohlc_streamer( options['broker'], market, timeframe, from_date=from_date, to_date=to_date) # cascaded generation of candles if cascaded: for tf in GENERATED_TF: if tf > timeframe: # from timeframe greater than initial if tf <= cascaded: # until max cascaded timeframe generators.append(CandleGenerator(from_tf, tf)) from_tf = tf # store for generation last_ohlcs[tf] = [] else: from_tf = tf if options.get('target'): target = TIMEFRAME_FROM_STR_MAP.get(options.get('target')) if target % timeframe != 0: logger.error( "Timeframe %s is not a multiple of %s !" % (timeframe_to_str(target), timeframe_to_str(timeframe))) sys.exit(-1) generators.append(CandleGenerator(timeframe, target)) # store for generation last_ohlcs[target] = [] if timeframe > 0: last_ohlcs[timeframe] = [] if timeframe == 0: while not tick_streamer.finished(): ticks = tick_streamer.next(timestamp + Instrument.TF_1M) count = len(ticks) total_count += len(ticks) for data in ticks: if data[0] > to_timestamp: break if generators: last_ticks.append(data) # generate higher candles for generator in generators: if generator.from_tf == 0: candles = generator.generate_from_ticks(last_ticks) if candles: for c in candles: store_ohlc(options['broker'], market, generator.to_tf, c) last_ohlcs[generator.to_tf] += candles # remove consumed ticks last_ticks = [] else: candles = generator.generate_from_candles( last_ohlcs[generator.from_tf]) if candles: for c in candles: store_ohlc(options['broker'], market, generator.to_tf, c) last_ohlcs[generator.to_tf] += candles # remove consumed candles last_ohlcs[generator.from_tf] = [] if timestamp - prev_update >= progression_incr: progression += 1 Terminal.inst().info( "%i%% on %s, %s ticks/trades for 1 minute, current total of %s..." % (progression, format_datetime(timestamp), count, total_count)) prev_update = timestamp count = 0 if timestamp > to_timestamp: break timestamp += Instrument.TF_1M # by step of 1m # calm down the storage of tick, if parsing is faster while Database.inst().num_pending_ticks_storage( ) > TICK_STORAGE_DELAY: time.sleep( TICK_STORAGE_DELAY) # wait a little before continue if progression < 100: Terminal.inst().info( "100%% on %s, %s ticks/trades for 1 minute, current total of %s..." % (format_datetime(timestamp), count, total_count)) elif timeframe > 0: while not ohlc_streamer.finished(): ohlcs = ohlc_streamer.next(timestamp + timeframe * 100) # per 100 count = len(ohlcs) total_count += len(ohlcs) for data in ohlcs: if data.timestamp > to_timestamp: break if generators: last_ohlcs[timeframe].append(data) tts = data.timestamp if not prev_tts: prev_tts = tts prev_tts = tts timestamp = tts # generate higher candles for generator in generators: candles = generator.generate_from_candles( last_ohlcs[generator.from_tf]) if candles: for c in candles: store_ohlc(options['broker'], market, generator.to_tf, c) last_ohlcs[generator.to_tf].extend(candles) # remove consumed candles last_ohlcs[generator.from_tf] = [] if timestamp - prev_update >= progression_incr: progression += 1 Terminal.inst().info( "%i%% on %s, %s ohlcs for 1 minute, current total of %s..." % (progression, format_datetime(timestamp), count, total_count)) prev_update = timestamp count = 0 if timestamp > to_timestamp: break if count == 0: timestamp += timeframe * 100 if progression < 100: Terminal.inst().info( "100%% on %s, %s ohlcs for 1 minute, current total of %s..." % (format_datetime(timestamp), count, total_count)) Terminal.inst().info("Flushing database...") Terminal.inst().flush() Database.terminate() Terminal.inst().info("Rebuild done!") Terminal.inst().flush() Terminal.terminate() sys.exit(0)
def dumps_notify_exit(self, timestamp, strategy_trader): """ Dumps to dict for notify/history. """ return { 'version': self.version(), 'trade': self.trade_type_to_str(), 'id': self.id, 'app-name': strategy_trader.strategy.name, 'app-id': strategy_trader.strategy.identifier, 'timestamp': timestamp, 'symbol': strategy_trader.instrument.market_id, 'way': "exit", 'entry-timeout': timeframe_to_str(self._entry_timeout), 'expiry': self._expiry, 'timeframe': timeframe_to_str(self._timeframe), 'is-user-trade': self._user_trade, 'comment': self._comment, 'direction': self.direction_to_str(), 'order-price': strategy_trader.instrument.format_price(self.op), 'order-qty': strategy_trader.instrument.format_quantity(self.oq), 'stop-loss-price': strategy_trader.instrument.format_price(self.sl), 'take-profit-price': strategy_trader.instrument.format_price(self.tp), 'avg-entry-price': strategy_trader.instrument.format_price(self.aep), 'avg-exit-price': strategy_trader.instrument.format_price(self.axp), 'entry-open-time': self.dump_timestamp(self.eot), 'exit-open-time': self.dump_timestamp(self.xot), 'filled-entry-qty': strategy_trader.instrument.format_quantity(self.e), 'filled-exit-qty': strategy_trader.instrument.format_quantity(self.x), 'profit-loss-pct': round(self.pl * 100.0, 2), 'num-exit-trades': len(self.exit_trades), 'stats': { 'best-price': strategy_trader.instrument.format_price( self._stats['best-price']), 'best-datetime': self.dump_timestamp(self._stats['best-timestamp']), 'worst-price': strategy_trader.instrument.format_price( self._stats['worst-price']), 'worst-datetime': self.dump_timestamp(self._stats['worst-timestamp']), 'entry-order-type': order_type_to_str(self._stats['entry-order-type']), 'first-realized-entry-datetime': self.dump_timestamp( self._stats['first-realized-entry-timestamp']), 'first-realized-exit-datetime': self.dump_timestamp( self._stats['first-realized-exit-timestamp']), 'last-realized-entry-datetime': self.dump_timestamp( self._stats['last-realized-entry-timestamp']), 'last-realized-exit-datetime': self.dump_timestamp( self._stats['last-realized-exit-timestamp']), 'profit-loss-currency': self._stats['profit-loss-currency'], 'profit-loss': self._stats['unrealized-profit-loss'], 'entry-fees': self._stats['entry-fees'], 'exit-fees': self._stats['exit-fees'], 'exit-reason': StrategyTrade.reason_to_str(self._stats['exit-reason']) } }
def do_optimizer(options): Terminal.inst().info("Starting SIIS optimizer...") Terminal.inst().flush() # database manager Database.create(options) Database.inst().setup(options) broker_id = options['broker'] market_id = options['market'] timeframe = None from_date = options.get('from') to_date = options.get('to') if not to_date: today = datetime.now().astimezone(UTC()) if timeframe == Instrument.TF_MONTH: to_date = today + timedelta(months=1) else: to_date = today + timedelta(seconds=timeframe) to_date = to_date.replace(microsecond=0) if not options.get('timeframe'): timeframe = None else: if options['timeframe'] in TIMEFRAME_FROM_STR_MAP: timeframe = TIMEFRAME_FROM_STR_MAP[options['timeframe']] else: try: timeframe = int(options['timeframe']) except: pass try: # checking data integrity, gap... if timeframe is None: for market in options['market'].split(','): if market.startswith('!') or market.startswith('*'): continue for tf in GENERATED_TF: Terminal.inst().info("Verifying %s OHLC %s..." % (market, timeframe_to_str(tf))) check_ohlcs(options['broker'], market, tf, from_date, to_date) elif timeframe == Instrument.TF_TICK: for market in options['market'].split(','): if market.startswith('!') or market.startswith('*'): continue Terminal.inst().info("Verifying %s ticks/trades..." % (market,)) check_ticks(options['broker'], market, from_date, to_date) elif timeframe > 0: # particular ohlc for market in options['market'].split(','): if market.startswith('!') or market.startswith('*'): continue Terminal.inst().info("Verifying %s OHLC %s..." % (market, timeframe_to_str(timeframe))) check_ohlcs(options['broker'], market, timeframe, from_date, to_date) except KeyboardInterrupt: pass finally: pass Terminal.inst().info("Flushing database...") Terminal.inst().flush() Database.terminate() Terminal.inst().info("Optimization done!") Terminal.inst().flush() Terminal.terminate() sys.exit(0)
def fetch_and_generate(self, market_id, timeframe, from_date=None, to_date=None, n_last=1000, fetch_option="", cascaded=None): if timeframe > 0 and timeframe not in self.GENERATED_TF: logger.error("Timeframe %i is not allowed !" % (timeframe, )) return generators = [] from_tf = timeframe self._last_ticks = [] self._last_ohlcs = {} if not from_date and n_last: # compute a from date today = datetime.now().astimezone(UTC()) if timeframe >= Instrument.TF_MONTH: from_date = ( today - timedelta(months=int(timeframe / Instrument.TF_MONTH) * n_last)).replace(day=1).replace(hour=0).replace( minute=0).replace(second=0) elif timeframe >= Instrument.TF_1D: from_date = (today - timedelta( days=int(timeframe / Instrument.TF_1D) * n_last)).replace( hour=0).replace(minute=0).replace(second=0) elif timeframe >= Instrument.TF_1H: from_date = (today - timedelta( hours=int(timeframe / Instrument.TF_1H) * n_last)).replace( minute=0).replace(second=0) elif timeframe >= Instrument.TF_1M: from_date = ( today - timedelta(minutes=int(timeframe / Instrument.TF_1M) * n_last)).replace(second=0) elif timeframe >= Instrument.TF_1S: from_date = (today - timedelta( seconds=int(timeframe / Instrument.TF_1S) * n_last)) from_date = from_date.replace(microsecond=0) if not to_date: today = datetime.now().astimezone(UTC()) if timeframe == Instrument.TF_MONTH: to_date = today + timedelta(months=1) else: to_date = today + timedelta(seconds=timeframe) to_date = to_date.replace(microsecond=0) # cascaded generation of candles if cascaded: for tf in Fetcher.GENERATED_TF: if tf > timeframe: # from timeframe greater than initial if tf <= cascaded: # until max cascaded timeframe generators.append(CandleGenerator(from_tf, tf)) from_tf = tf # store for generation self._last_ohlcs[tf] = [] else: from_tf = tf if timeframe > 0: self._last_ohlcs[timeframe] = [] n = 0 t = 0 if timeframe == 0: for data in self.fetch_trades(market_id, from_date, to_date, None): # store (int timestamp in ms, str bid, str ofr, str volume) Database.inst().store_market_trade( (self.name, market_id, data[0], data[1], data[2], data[3])) if generators: self._last_ticks.append( (float(data[0]) * 0.001, float(data[1]), float(data[2]), float(data[3]))) # generate higher candles for generator in generators: if generator.from_tf == 0: candles = generator.generate_from_ticks( self._last_ticks) if candles: for c in candles: self.store_candle(market_id, generator.to_tf, c) self._last_ohlcs[generator.to_tf] += candles # remove consumed ticks self._last_ticks = [] else: candles = generator.generate_from_candles( self._last_ohlcs[generator.from_tf]) if candles: for c in candles: self.store_candle(market_id, generator.to_tf, c) self._last_ohlcs[generator.to_tf] += candles # remove consumed candles self._last_ohlcs[generator.from_tf] = [] n += 1 t += 1 if n == 10000: n = 0 Terminal.inst().info("%i trades for %s..." % (t, market_id)) # calm down the storage of tick, if parsing is faster while Database.inst().num_pending_ticks_storage( ) > Fetcher.MAX_PENDING_TICK: time.sleep(Fetcher.TICK_STORAGE_DELAY ) # wait a little before continue logger.info("Fetched %i trades for %s" % (t, market_id)) elif timeframe > 0: for data in self.fetch_candles(market_id, timeframe, from_date, to_date, None): # store (int timestamp ms, str open bid, high bid, low bid, close bid, open ofr, high ofr, low ofr, close ofr, volume) Database.inst().store_market_ohlc( (self.name, market_id, data[0], int(timeframe), data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9])) if generators: candle = Candle(float(data[0]) * 0.001, timeframe) candle.set_bid_ohlc(float(data[1]), float(data[2]), float(data[3]), float(data[4])) candle.set_ofr_ohlc(float(data[5]), float(data[6]), float(data[7]), float(data[8])) candle.set_volume(float(data[9])) candle.set_consolidated(True) self._last_ohlcs[timeframe].append(candle) # generate higher candles for generator in generators: candles = generator.generate_from_candles( self._last_ohlcs[generator.from_tf]) if candles: for c in candles: self.store_candle(market_id, generator.to_tf, c) self._last_ohlcs[generator.to_tf].extend(candles) # remove consumed candles self._last_ohlcs[generator.from_tf] = [] n += 1 t += 1 if n == 1000: n = 0 Terminal.inst().info( "%i candles for %s in %s..." % (t, market_id, timeframe_to_str(timeframe))) logger.info("Fetched %i candles for %s in %s" % (t, market_id, timeframe_to_str(timeframe)))
def update_trades(self, timestamp): """ Update managed trades per instruments and delete terminated trades. """ if not self.trades: return trader = self.strategy.trader() # # for each trade check if the TP or SL is reached and trigger if necessary # self.lock() for trade in self.trades: # # managed operation # if trade.has_operations(): mutated = False for operation in trade.operations: mutated |= operation.test_and_operate(trade, self.instrument, trader) if mutated: trade.cleanup_operations() # # active trade # if trade.is_active(): # for statistics usage trade.update_stats(self.instrument.close_exec_price(trade.direction), timestamp) # # asset trade # if trade.trade_type == StrategyTrade.TRADE_BUY_SELL: if trade.is_closed(): continue # process only on active trades if not trade.is_active(): # @todo timeout if not filled before condition... continue if trade.is_closing(): continue if not self.instrument.tradeable: continue if trade.is_dirty: # entry quantity changed need to update the exits orders trade.update_dirty(trader, self.instrument) # potential order exec close price (always close a long) close_exec_price = self.instrument.close_exec_price(Order.LONG) if (trade.tp > 0) and (close_exec_price >= trade.tp) and not trade.has_limit_order(): # take profit trigger stop, close at market (taker fee) if trade.close(trader, self.instrument): # notify self.strategy.notify_order(trade.id, Order.SHORT, self.instrument.market_id, self.instrument.format_price(close_exec_price), timestamp, trade.timeframe, 'take-profit', trade.estimate_profit_loss(self.instrument)) # streaming (but must be done with notify) if self._global_streamer: self._global_streamer.member('buy-exit').update(close_exec_price, timestamp) elif (trade.sl > 0) and (close_exec_price <= trade.sl) and not trade.has_stop_order(): # stop loss trigger stop, close at market (taker fee) if trade.close(trader, self.instrument): # notify self.strategy.notify_order(trade.id, Order.SHORT, self.instrument.market_id, self.instrument.format_price(close_exec_price), timestamp, trade.timeframe, 'stop-loss', trade.estimate_profit_loss(self.instrument)) # streaming (but must be done with notify) if self._global_streamer: self._global_streamer.member('buy-exit').update(close_exec_price, timestamp) # # margin trade # elif trade.trade_type == StrategyTrade.TRADE_MARGIN or trade.trade_type == StrategyTrade.TRADE_IND_MARGIN: # process only on active trades if not trade.is_active(): # @todo timeout if not filled before condition... continue if trade.is_closed(): continue if trade.is_closing(): continue if not self.instrument.tradeable: continue # potential order exec close price close_exec_price = self.instrument.close_exec_price(trade.direction) if (trade.tp > 0) and ((trade.direction > 0 and close_exec_price >= trade.tp) or (trade.direction < 0 and close_exec_price <= trade.tp)) and not trade.has_limit_order(): # close in profit at market (taker fee) if trade.close(trader, self.instrument): # and notify self.strategy.notify_order(trade.id, trade.close_direction(), self.instrument.market_id, self.instrument.format_price(close_exec_price), timestamp, trade.timeframe, 'take-profit', trade.estimate_profit_loss(self.instrument)) # and for streaming if self._global_streamer: self._global_streamer.member('sell-exit' if trade.direction < 0 else 'buy-exit').update(close_exec_price, timestamp) elif (trade.sl > 0) and ((trade.direction > 0 and close_exec_price <= trade.sl) or (trade.direction < 0 and close_exec_price >= trade.sl)) and not trade.has_stop_order(): # close a long or a short position at stop-loss level at market (taker fee) if trade.close(trader, self.instrument): # and notify self.strategy.notify_order(trade.id, trade.close_direction(), self.instrument.market_id, self.instrument.format_price(close_exec_price), timestamp, trade.timeframe, 'stop-loss', trade.estimate_profit_loss(self.instrument)) # and for streaming if self._global_streamer: self._global_streamer.member('sell-exit' if trade.direction < 0 else 'buy-exit').update(close_exec_price, timestamp) self.unlock() # # remove terminated, rejected, canceled and empty trades # mutated = False self.lock() for trade in self.trades: if trade.can_delete(): mutated = True # cleanup if necessary before deleting the trade related refs trade.remove(trader) # record the trade for analysis and study if not trade.is_canceled(): # last update of stats before logging trade.update_stats(self.instrument.close_exec_price(trade.direction), timestamp) # realized profit/loss profit_loss = trade.profit_loss - trade.entry_fees_rate() - trade.exit_fees_rate() # perf sommed here it means that its not done during partial closing if profit_loss != 0.0: self._stats['perf'] += profit_loss self._stats['worst'] = min(self._stats['worst'], profit_loss) self._stats['best'] = max(self._stats['best'], profit_loss) if profit_loss <= 0.0: self._stats['cont-loss'] += 1 self._stats['cont-win'] = 1 elif profit_loss > 0.0: self._stats['cont-loss'] = 0 self._stats['cont-win'] += 1 record = { 'id': trade.id, 'eot': trade.entry_open_time, 'xot': trade.exit_open_time, 'd': trade.direction_to_str(), 'p': self.instrument.format_price(trade.entry_price), 'q': self.instrument.format_quantity(trade.order_quantity), 'e': self.instrument.format_quantity(trade.exec_entry_qty), 'x': self.instrument.format_quantity(trade.exec_exit_qty), 'tp': self.instrument.format_price(trade.take_profit), 'sl': self.instrument.format_price(trade.stop_loss), 'tf': timeframe_to_str(trade.timeframe), 'aep': self.instrument.format_price(trade.entry_price), 'axp': self.instrument.format_price(trade.exit_price), 's': trade.state_to_str(), 'b': self.instrument.format_price(trade.best_price()), 'w': self.instrument.format_price(trade.worst_price()), 'bt': trade.best_price_timestamp(), 'wt': trade.worst_price_timestamp(), 'pl': profit_loss, 'fees': trade.entry_fees_rate() + trade.exit_fees_rate(), 'c': trade.get_conditions(), 'com': trade.comment, } if profit_loss < 0: self._stats['failed'].append(record) elif profit_loss > 0: self._stats['success'].append(record) else: self._stats['roe'].append(record) if self._reporting == StrategyTrader.REPORTING_VERBOSE: self.report(trade, False) # recreate the list of trades if mutated: trades_list = [] for trade in self.trades: if not trade.can_delete(): # keep only active and pending trades trades_list.append(trade) self.trades = trades_list self.unlock()
def do_exporter(options): Terminal.inst().info("Starting SIIS exporter...") Terminal.inst().flush() # database manager Database.create(options) Database.inst().setup(options) broker_id = options['broker'] market_id = options['market'] timeframe = None # UTC option dates from_date = options.get('from') to_date = options.get('to') if not to_date: today = datetime.now().astimezone(UTC()) if timeframe == Instrument.TF_MONTH: to_date = today + timedelta(months=1) else: to_date = today + timedelta(seconds=timeframe or 1.0) to_date = to_date.replace(microsecond=0) if not options.get('timeframe'): timeframe = None else: if options['timeframe'] in TIMEFRAME_FROM_STR_MAP: timeframe = TIMEFRAME_FROM_STR_MAP[options['timeframe']] else: try: timeframe = int(options['timeframe']) except: pass filename = options.get('filename') cur_datetime = datetime.now().astimezone( UTC()).strftime("%Y-%m-%dT%H:%M:%SZ") from_date_str = from_date.strftime("%Y-%m-%dT%H:%M:%SZ") to_date_str = to_date.strftime("%Y-%m-%dT%H:%M:%SZ") try: # exporting data... if timeframe is None: for market in options['market'].split(','): if market.startswith('!') or market.startswith('*'): continue dst = open("%s-%s-%s-any.siis" % (filename, broker_id, market), "wt") # write file header dst.write( "format=SIIS\tversion=%s\tcreated=%s\tbroker=%s\tmarket=%s\tfrom=%s\tto=%s\ttimeframe=any\n" % (EXPORT_VERSION, cur_datetime, broker_id, market, from_date_str, to_date_str)) for tf in EXPORT_TF: Terminal.inst().info("Exporting %s OHLC %s..." % (market, timeframe_to_str(tf))) dst.write("timeframe=%s\n" % timeframe_to_str(tf)) export_ohlcs_siis_1_0_0(options['broker'], market, tf, from_date, to_date, dst) dst.close() dst = None elif timeframe == Instrument.TF_TICK: for market in options['market'].split(','): if market.startswith('!') or market.startswith('*'): continue dst = open("%s-%s-%s-t.siis" % (filename, broker_id, market), "wt") # write file header dst.write( "format=SIIS\tversion=%s\tcreated=%s\tbroker=%s\tmarket=%s\tfrom=%s\tto=%s\ttimeframe=t\n" % (EXPORT_VERSION, cur_datetime, broker_id, market, from_date_str, to_date_str)) Terminal.inst().info("Exporting %s ticks/trades..." % (market, )) dst.write("timeframe=t\n") export_ticks_siis_1_0_0(options['broker'], market, from_date, to_date, dst) dst.close() dst = None elif timeframe > 0: # particular ohlc for market in options['market'].split(','): if market.startswith('!') or market.startswith('*'): continue dst = open( "%s-%s-%s-%s.siis" % (filename, broker_id, market, timeframe_to_str(timeframe)), "wt") # write file header dst.write( "format=SIIS\tversion=%s\tcreated=%s\tbroker=%s\tmarket=%s\tfrom=%s\tto=%s\ttimeframe=%s\n" % (EXPORT_VERSION, cur_datetime, broker_id, market, from_date_str, to_date_str, timeframe_to_str(timeframe))) Terminal.inst().info("Exporting %s OHLC %s..." % (market, timeframe_to_str(timeframe))) dst.write("timeframe=%s\n" % timeframe_to_str(timeframe)) export_ohlcs_siis_1_0_0(options['broker'], market, timeframe, from_date, to_date, dst) dst.close() dst = None except KeyboardInterrupt: pass except Exception as e: error_logger.error(str(e)) dst.close() dst = None finally: pass Database.terminate() Terminal.inst().info("Exportation done!") Terminal.inst().flush() Terminal.terminate() sys.exit(0)