def open(self, trader, market_id, direction, order_type, order_price, quantity, take_profit, stop_loss, leverage=1.0, hedging=None): """ Open a position or buy an asset. @param hedging If defined use the defined value else use the default from the market. """ order = Order(trader, market_id) order.direction = direction order.order_price = order_price order.order_type = order_type order.quantity = quantity order.leverage = leverage if hedging: order.hedging = hedging # generated a reference order id trader.set_ref_order_id(order) self.create_ref_oid = order.ref_order_id self.dir = order.direction self.op = order.order_price # retains the order price self.oq = order.quantity # ordered quantity self.tp = take_profit self.sl = stop_loss self._stats['entry-maker'] = not order.is_market() if trader.create_order(order): # keep the related create position identifier if available self.position_id = order.position_id if not self.eot and order.created_time: # only at the first open self.eot = order.created_time return True else: self.create_ref_oid = None return False
def modify_take_profit(self, trader, market_id, price): if self.limit_oid: # cancel the limit order and create a new one if trader.cancel_order(self.limit_oid): self.limit_ref_oid = None self.limit_oid = None self.limit_order_qty = 0.0 else: return False if self.e == self.x: # all entry qty is filled return True if self.e < self.x: # something wrong but its ok return False if self.position_id: # if not accepted as modification do it as limit order if trader.modify_position(self.position_id, take_profit_price=price): self.tp = price return True elif self.e > 0: # only if filled entry partially or totally order = Order(self, market_id) order.direction = self.direction order.order_type = Order.ORDER_TAKE_PROFIT_LIMIT order.reduce_only = True order.quantity = self.e - self.x # remaining order.order_price = price trader.set_ref_order_id(order) self.limit_ref_oid = order.ref_order_id self._stats['exit-maker'] = not order.is_market() if trader.create_order(order): self.limit_oid = order.order_id self.limit_order_qty = order.quantity self.tp = price return True else: self.limit_ref_oid = None self.limit_order_qty = 0.0 return False
def open(self, trader, market_id, direction, order_type, order_price, quantity, take_profit, stop_loss, leverage=1.0, hedging=None): """ Open a position or buy an asset. """ order = Order(trader, market_id) order.direction = direction order.order_price = order_price order.order_type = order_type order.quantity = quantity order.leverage = leverage # if need to retry @todo or cancel # self._market_id = market_id # self._order_type = order_type # self._leverage = leverage # generated a reference order id trader.set_ref_order_id(order) self.buy_ref_oid = order.ref_order_id self.dir = order.direction self.op = order.order_price # retains the order price self.oq = order.quantity # ordered quantity self.tp = take_profit self.sl = stop_loss self._stats['entry-maker'] = not order.is_market() if trader.create_order(order): if not self.eot and order.created_time: # only at the first open self.eot = order.created_time return True else: self._entry_state = StrategyTrade.STATE_REJECTED return False
def open(self, trader, market_id, direction, order_type, order_price, quantity, take_profit, stop_loss, leverage=1.0, hedging=None): """ Open a position or buy an asset. """ order = Order(trader, market_id) order.direction = direction order.order_price = order_price order.order_type = order_type order.quantity = quantity order.leverage = leverage # generated a reference order id trader.set_ref_order_id(order) self.create_ref_oid = order.ref_order_id self.dir = order.direction self.op = order.order_price # retains the order price self.oq = order.quantity # ordered quantity self.tp = take_profit self.sl = stop_loss self._stats['entry-maker'] = not order.is_market() if trader.create_order(order): self.position_id = order.position_id # might be market-id if not self.eot and order.created_time: self.eot = order.created_time return True else: self.create_ref_oid = None return False
def modify_take_profit(self, trader, market_id, price): if self.limit_oid: # cancel the limit order and create a new one if trader.cancel_order(self.limit_oid): self.limit_ref_oid = None self.limit_oid = None self.limit_order_qty = 0.0 else: return False if self.e == self.x: # all entry qty is filled return True if self.e < self.x: # something wrong but its ok return False if self.e > 0: # only if filled entry partially or totally order = Order(self, market_id) order.direction = self.direction order.order_type = Order.ORDER_TAKE_PROFIT_LIMIT # order.reduce_only = True (not for now because it implies to have the filled qty, and so need to update each time trade qty is updated) order.quantity = self.e - self.x # remaining order.order_price = price trader.set_ref_order_id(order) self.limit_ref_oid = order.ref_order_id self._stats['exit-maker'] = not order.is_market() if trader.create_order(order): self.limit_oid = order.order_id self.limit_order_qty = order.quantity self.tp = price return True else: self.limit_ref_oid = None self.limit_order_qty = 0.0 return False
def modify_stop_loss(self, trader, market_id, price): if self.stop_oid: # cancel the stop order and create a new one if trader.cancel_order(self.stop_oid): self.stop_ref_oid = None self.stop_oid = None else: return False if self.e == self.x: # all entry qty is filled return True if self.e < self.x: # something wrong but its ok return False if self.e > 0: # only if filled entry partially or totally order = Order(self, market_id) order.direction = self.direction order.order_type = Order.ORDER_STOP order.reduce_only = True order.quantity = self.e - self.x # remaining order.order_price = price trader.set_ref_order_id(order) self.stop_ref_oid = order.ref_order_id self._stats['exit-maker'] = not order.is_market() if trader.create_order(order): self.stop_oid = order.order_id self.stop_order_qty = order.quantity self.sl = price return True else: self.stop_ref_oid = None self.stop_order_qty = 0.0 return False
def __fetch_orders(self, signals=False): """ This is the synchronous REST fetching, but prefer the WS asynchronous and live one. Mainly used for initial fetching. """ try: open_orders = self._watcher.connector.open_orders() except Exception as e: logger.error("__fetch_orders: %s" % repr(e)) raise orders = {} for data in open_orders: market = self.market(data['symbol']) if data['status'] == 'NEW': # might be... order = Order(self, data['symbol']) order.set_order_id(data['orderId']) order.quantity = data['origQty'] order.executed = data['executedQty'] order.direction = Order.LONG if data[ 'side'] == 'BUY' else Order.SHORT if data['type'] == 'LIMIT': order.order_type = Order.ORDER_LIMIT elif data['type'] == 'MARKET': order.order_type = Order.ORDER_MARKET elif data['type'] == 'STOP_LOSS_LIMIT': order.order_type = Order.ORDER_STOP_LIMIT order.close_only = True elif data['type'] == 'TAKE_PROFIT_LIMIT': order.order_type = Order.ORDER_LIMIT order.close_only = True order.order_price = data['price'] order.stop_loss = data['stopPrice'] order.created_time = data['time'] order.transact_time = data['updateTime'] if data['timeInForce'] == 'GTC': order.time_in_force = Order.TIME_IN_FORCE_GTC elif data['timeInForce'] == 'IOC': order.time_in_force = Order.TIME_IN_FORCE_IOC elif data['timeInForce'] == 'FOK': order.time_in_force = Order.TIME_IN_FORCE_FOK else: order.time_in_force = Order.TIME_IN_FORCE_GTC # "icebergQty": "0.0" # @todo a day when I'll be rich orders[order.order_id] = order if signals: # deleted (for signals if no WS) deleted_list = self._orders.keys() - orders.keys() # @todo # created (for signals if no WS) created_list = orders.keys() - self._orders.keys() # @todo self._orders = orders
def on_order_traded(self, market_id, data, ref_order_id): """ Order update, trade order in that case, is always successed by an asset update signal. Binance order modification is not possible, need cancel and recreate. @note Consume 1 API credit to get the asset quote price at the time of the trade. """ market = self._markets.get(data['symbol']) if market is None: # not interested by this market return base_asset = self.__get_or_add_asset(market.base) quote_asset = self.__get_or_add_asset(market.quote) quote_market = None order = self._orders.get(data['id']) if order is None: # not found (might not occurs) order = Order(self, data['symbol']) order.set_order_id(data['id']) # its might be the creation timestamp but it will be the trade execution order.created_time = data['timestamp'] order.direction = data['direction'] order.order_type = data['type'] order.time_in_force = data['time-in-force'] order.quantity = data['quantity'] order.order_price = data['order-price'] order.stop_loss = data['stop-loss'] self._orders[data['id']] = order order.executed += data['filled'] if data['trade-id']: # same asset used for commission buy_or_sell = data['direction'] == Order.LONG # base details in the trade order base_trade_qty = data['filled'] base_exec_price = data['exec-price'] # price of the quote asset expressed in prefered quote at time of the trade (need a REST call) quote_trade_qty = data[ 'quote-transacted'] # or base_trade_qty * base_exec_price quote_exec_price = 1.0 if quote_asset.quote and quote_asset.symbol != quote_asset.quote: # quote price to be fetched if self._watcher.has_instrument(quote_asset.symbol + quote_asset.quote): # direct, and get the related market quote_market = self._markets.get(quote_asset.symbol + quote_asset.quote) quote_exec_price = self.history_price( quote_asset.symbol + quote_asset.quote, data['timestamp']) elif self._watcher.has_instrument(quote_asset.quote + quote_asset.symbol): # indirect, but cannot have the market quote_exec_price = 1.0 / self.history_price( quote_asset.quote + quote_asset.symbol, data['timestamp']) # base asset self.__update_asset(order.order_type, base_asset, market, data['trade-id'], base_exec_price, base_trade_qty, buy_or_sell, data['timestamp']) # quote asset self.__update_asset(order.order_type, quote_asset, quote_market, None, quote_exec_price, quote_trade_qty, not buy_or_sell, data['timestamp']) # commission asset if data['commission-asset'] == base_asset.symbol: self.__update_asset(Order.ORDER_MARKET, base_asset, market, None, base_exec_price, data['commission-amount'], False, data['timestamp']) else: commission_asset = self.__get_or_add_asset( data['commission-asset']) commission_asset_market = None quote_exec_price = 1.0 if commission_asset.quote and commission_asset.symbol != commission_asset.quote: # commission asset price to be fetched if self._watcher.has_instrument(commission_asset.symbol + commission_asset.quote): # direct, and get the related market commission_asset_market = self.market( commission_asset.symbol + commission_asset.quote) quote_exec_price = commission_asset_market.price elif self._watcher.has_instrument(commission_asset.quote + commission_asset.symbol): # indirect, but cannot have the market quote_exec_price = 1.0 / self.history_price( commission_asset.quote + commission_asset.symbol, data['timestamp']) self.__update_asset(Order.ORDER_MARKET, commission_asset, commission_asset_market, None, quote_exec_price, data['commission-amount'], False, data['timestamp'])
def __update_orders(self): # filters only siis managed orders src_orders = self._watcher.connector.ws.open_orders("") # "siis_") # first delete older orders order_rm_list = [] for k, order in self._orders.items(): found = False for src_order in src_orders: src_order_id = src_order['clOrdID'] or src_order['orderID'] if order.order_id == src_order['clOrdID'] or order.order_id == src_order['orderID']: found = True break if not found: order_rm_list.append(order.order_id) for order_id in order_rm_list: del self._orders[order_id] # insert or update active orders for src_order in src_orders: found = False src_order_id = src_order['clOrdID'] or src_order['orderID'] order = self._orders.get(src_order_id) if order is None: # insert order = Order(self, src_order['symbol']) order.set_order_id(src_order_id) self._orders[order.order_id] = order else: order = self._orders.get(src_order_id) # logger.info(src_order) # probably modifier or when leavesQty is update the ordStatus must change # if src_order['ordStatus'] != "New": # continue # update order.direction = Position.LONG if src_order['side'] == 'Buy' else Position.SHORT # 'orderQty' (ordered qty), 'cumQty' (cumulative done), 'leavesQty' (remaning) order.quantity = src_order.get('leavesQty', src_order.get('orderQty', 0)) if src_order.get('transactTime'): order.transact_time = self._parse_datetime(src_order.get('transactTime')).timestamp() if src_order['ordType'] == "Market": order.order_type = Order.ORDER_MARKET elif src_order['ordType'] == "Limit": order.order_type = Order.ORDER_LIMIT order.order_price = src_order.get('price') elif src_order['ordType'] == "Stop": order.order_type = Order.ORDER_STOP order.order_price = src_order.get('stopPx') else: # stop limit, trigger marke, tripgger limit logger.info("bitmex trader l577 ", src_order['ordType']) if src_order['timeInForce'] == 'GoodTillCancel': order.time_in_force = Order.TIME_IN_FORCE_GTC elif src_order['timeInForce'] == 'ImmediateOrCancel': order.time_in_force = Order.TIME_IN_FORCE_IOC elif src_order['timeInForce'] == 'FillOrKill': order.time_in_force = Order.TIME_IN_FORCE_FOK else: order.time_in_force = Order.TIME_IN_FORCE_GTC # triggered, ordRejReason, currency # @todo # execution options exec_inst = src_order['execInst'].split(',') # taker or maker fee if 'ParticipateDoNotInitiate' in exec_inst: order.post_only = True else: order.post_only = False # close reduce only if 'Close' in exec_inst: # close only order (must be used with reduce only, only reduce a position, and close opposites orders) order.close_only = True else: order.close_only = False # close reduce only if 'ReduceOnly' in exec_inst: # reduce only order (only reduce a position) order.reduce_only = True else: order.redeuce_only = False # execution price if 'LastPrice' in exec_inst: order.price_type = Order.PRICE_LAST elif 'IndexPrice' in exec_inst: order.price_type = Order.PRICE_MARK elif 'MarkPrice' in exec_inst: order.price_type = Order.PRICE_INDEX
def close_position(self, position_id, market=True, limit_price=None): if not self._activity: return False position = self._positions.get(position_id) if position is None or not position.is_opened(): return False if not self.has_market(position.symbol): logger.error("%s does not support market %s on close position %s !" % ( self.name, position.symbol, position.position_id)) return False ref_order_id = "siis_" + base64.b64encode(uuid.uuid4().bytes).decode('utf8').rstrip('=\n') # keep for might be useless in this case order.set_ref_order_id(ref_order_id) order = Order(self, position.symbol) order.set_position_id(position.position_id) order.quantity = position.quantity order.direction = -position.direction # neg direction postdict = { 'symbol': order.symbol, 'clOrdID': ref_order_id, 'execInst': 'Close', # 'execInst': 'ReduceOnly,Close' # @todo why rejected with ReduceOnly ? } # short mean negative quantity if order.direction == Position.SHORT: qty = -qty # fully close (using Close and need 'side' when qty is not defined) # qty = None # order type if market: order.order_type = Order.ORDER_MARKET postdict['ordType'] = "Market" postdict['orderQty'] = qty else: order.order_type = Order.ORDER_LIMIT order.order_price = limit_price postdict['ordType'] = "Limit" postdict['price'] = order.order_price postdict['orderQty'] = qty if qty is None: postdict['side'] = "Buy" if order.direction > 0 else "Sell" try: result = self._watcher.connector.request(path="order", postdict=postdict, verb='POST', max_retries=15) except Exception as e: logger.error(str(e)) return False if result.get('ordRejReason'): logger.error("%s rejected closing order %s from %s %s - cause : %s !" % ( self.name, order.direction_to_str(), order.quantity, order.symbol, result['ordRejReason'])) return False # store the order with its order id order.set_order_id(result['orderID']) # and store the order self._orders[order.order_id] = order # set position closing until we get confirmation on a next update position.closing(limit_price) return True
def __update_reversal(self, instrument): # consts MIN_SCORE = 4*60 # min score to reach to validate an order BSD_SCORE_FACTOR = 2 # 2,3 CBO_SCORE_FACTOR = 2 # 2,3 RSI_SCORE_FACTOR = 0.5 # 0.5,1,2 RSI_TREND_SCORE_FACTOR = 1 # 0.5,1,2,4 EMA_VWMA_CROSS_SCORE_FACTOR = 8000 # 5000,8000 VWMA_PRICE_CROSS_SCORE_FACTOR = 2000 # 2000,4000 EMA_VWM_BONUS_SCORE = 2 # 1,2,3,5 TIME_SCORE_REGRESSION_FACTOR = 0.75 # 0.375,0.5,0.75 RSI_LOW = 30 # 25,30,35 RSI_HIGH = 70 # 65,70,75 # @todo a plot of the account balance and % gain/loss of each trade # process in 1 minute, retrieve analysis data instrument strategy_trader = self._strategy_traders.get(instrument) # compute with the max samples num_samples = instrument.num_samples(Instrument.TF_MIN) depth = min(self.depth, num_samples) last_prices = instrument.last_prices(Instrument.TF_MIN, Instrument.PRICE_CLOSE, depth) last_volumes = instrument.last_volumes(Instrument.TF_MIN, depth) # current timestamp timestamp = self.timestamp # instrument.last_candles(Instrument.TF_MIN, depth) # @todo Typical price is attained by taking adding the high, low and close, and dividing by three: (H+L+C)/3 if depth < self.min_depth: # not enought samples return rsi = strategy_trader.rsi.compute(last_prices) sma = strategy_trader.sma.compute(last_prices) ema = strategy_trader.ema.compute(last_prices) vwma = strategy_trader.vwma.compute(last_prices, last_volumes) # # scorify # bsd_score = 0 cbo_score = 0 rsi_score = 0 ema_vwma_score = 0 ema_vwma_bonus_score = 0 price_vwma_score = 0 if strategy_trader.blueskyday: if strategy_trader.blueskyday[-1].direction == Position.LONG: bsd_score = BSD_SCORE_FACTOR elif strategy_trader.blueskyday[-1].direction == Position.SHORT: bsd_score = -BSD_SCORE_FACTOR if strategy_trader.channelbreakout: if strategy_trader.channelbreakout[-1].direction == Position.LONG: cbo_score = CBO_SCORE_FACTOR elif strategy_trader.channelbreakout[-1].direction == Position.SHORT: cbo_score = -CBO_SCORE_FACTOR # rsi 30/70, gives strong signals # @todo be we could compute it on two tf (the last 14N and the more global at depth level to have two trends) rsi_argmin = np.argmin(rsi) rsi_argmax = np.argmax(rsi) # trend of the rsi or MM if rsi_argmin < rsi_argmax and rsi[rsi_argmin] < rsi[rsi_argmax]: # ++ rsi_trend = (rsi[rsi_argmax] + rsi[rsi_argmin]) / (rsi[rsi_argmax] - rsi[rsi_argmin]) elif rsi_argmax < rsi_argmin and rsi[rsi_argmax] > rsi[rsi_argmin]: ## -- rsi_trend = (rsi[rsi_argmin] + rsi[rsi_argmax]) / (rsi[rsi_argmin] - rsi[rsi_argmax]) else: rsi_trend = 0 if rsi[-1] < RSI_LOW: rsi_score = (RSI_LOW-rsi[-1]) * RSI_SCORE_FACTOR # ++ if rsi_trend > 0: rsi_score += rsi_trend * RSI_TREND_SCORE_FACTOR elif rsi[-1] > RSI_HIGH: rsi_score = (RSI_HIGH-rsi[-1]) * RSI_SCORE_FACTOR if rsi_trend < 0: rsi_score += rsi_trend * RSI_TREND_SCORE_FACTOR # prev = rsi[0] # for (i, v) in enumerate(rsi): # if v < prev and v < RSI_LOW: # longs.append((i, last_prices[i])) # prev = v # elif v > prev and v > RSI_HIGH: # shorts.append((i, last_prices[i])) # prev = v # ema/vwma crossing ema_vwma_score = (ema[-1]-vwma[-1]) / last_prices[-1] * EMA_VWMA_CROSS_SCORE_FACTOR # vwma/price crossing price_vwma_score = (last_prices[-1]-vwma[-1]) / last_prices[-1] * VWMA_PRICE_CROSS_SCORE_FACTOR # if last_prices[-1] > vwma[-1]: # strategy_trader.scores[-1] += 1 # elif last_prices[-1] < vwma[-1]: # strategy_trader.scores[-1] -= 1 # ema/vwma crossing and vwmap/price more score !! if ema[-1] > vwma[-1] and last_prices[-1] > vwma[-1]: ema_vwma_bonus_score = EMA_VWM_BONUS_SCORE elif ema[-1] < vwma[-1] and last_prices[-1] < vwma[-1]: ema_vwma_bonus_score = -EMA_VWM_BONUS_SCORE # support/resistance signal # @todo and then scores +-= 2 # price delta min including spread, have to determine if the price can vary of a minimal size # @todo # confirmation on N candles, and don't take care of pyramided orders total_score = rsi_score + ema_vwma_score + price_vwma_score + ema_vwma_bonus_score + bsd_score + cbo_score # # score tuning # # store the total score strategy_trader.scores[-1] = total_score final_score = total_score # average of the two last score and increase the last, score is exp if signals are in the trend if len(strategy_trader.scores) > 2: final_score = np.average(strategy_trader.scores[-2:]) final_score += strategy_trader.scores[-2] # and store it strategy_trader.scores[-1] = final_score # handle a score convergence to avoid multiple signals if (strategy_trader.cur_score > 0 and final_score > 0) or (strategy_trader.cur_score < 0 and final_score < 0): # cancel all # strategy_trader.scores = [0] # or ignore # strategy_trader.scores[-1] = 0 # or take 75% of the previous score to minimize its impact progressively # strategy_trader.scores[-1] = strategy_trader.scores[-2] * TIME_SCORE_REGRESSION_FACTOR # or keep only 37.5% of it strategy_trader.scores[-1] *= TIME_SCORE_REGRESSION_FACTOR * 0.5 # keep as final score or nullify final_score = strategy_trader.scores[-1] # handle a score divergence # if (rsi_score > 0 and ema_vwma_score < 0) or (rsi_score < 0 and ema_vwma_bonus_score > 0): # total_score *= 0.25 # limit strategy_trader.scores len to max depth if len(strategy_trader.scores) > self.depth: strategy_trader.scores = strategy_trader.scores[len(strategy_trader.scores)-self.depth:] # # pass an order if score is accepted # if abs(final_score) >= MIN_SCORE: # keep apart the current score strategy_trader.cur_score = final_score if final_score > 0: strategy_trader.longs.append((timestamp, last_prices[-1])) elif final_score < 0: strategy_trader.shorts.append((timestamp, last_prices[-1])) date_str = datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S') direction = Position.LONG if final_score > 0 else Position.SHORT # create an order an post it if final_score > 0: Terminal.inst().notice("> Strategy %s LONG %s%s at price %.4f on %s" % (self.name, instrument.trade_quantity, instrument.market_id, last_prices[-1], date_str)) else: Terminal.inst().notice("> Strategy %s SHORT %s%s at price %.4f on %s" % (self.name, instrument.trade_quantity, instrument.market_id, last_prices[-1], date_str)) trader = self.trader() if trader: order = Order(trader, instrument.market_id) order.direction = direction order.order_price = last_prices[-1] # depends of the instrument and the account, and not always necessary, but always in paper trader order.leverage = instrument.leverage positions = trader.positions(instrument.market_id) current_opposite_qty = 0.0 current_same_qty = 0.0 for position in positions: # strategy does a reversal (and quantity are always positives) if position.direction != direction: # opposit directions ? current_opposite_qty += position.quantity # need to close that else: # or same direction ? current_same_qty += position.quantity # trading quantity + what we have in opposite direction - what we already have in the same direction if self._pyramided >= 1: order.quantity = instrument.trade_quantity + current_opposite_qty - current_same_qty # @todo else: order.quantity = instrument.trade_quantity + current_opposite_qty - current_same_qty if order.quantity > 0: # @todo debug only Terminal.inst().info("Do order %s %s with %s" % (instrument.market_id, 'SHORT' if direction==Position.SHORT else 'LONG', order.quantity)) trader.create_order(order) # consumes buy sell signals # @todo could put previous scores into history # strategy_trader.scores = [0] strategy_trader.blueskyday = [] strategy_trader.channelbreakout = [] else: # append the next score entry at 0 strategy_trader.scores.append(0) # # charting # if strategy_trader.chart is None and Charting.inst(): # create the chart if necessary strategy_trader.chart = Charting.inst().chart("%s on %s" % (self.name, instrument.symbol)) rechart = strategy_trader.chart.can_redraw if rechart: longs = [] shorts = [] # take only in depth longs and shorts for long in strategy_trader.longs: if long[0] + depth*Instrument.TF_MIN >= timestamp: longs.append(((long[0] + depth*Instrument.TF_MIN - timestamp) / Instrument.TF_MIN, long[1])) for short in strategy_trader.shorts: if short[0] + depth*Instrument.TF_MIN >= timestamp: shorts.append(((short[0] + depth*Instrument.TF_MIN - timestamp) / Instrument.TF_MIN, short[1])) # @todo send a stream with the last values or/and updated ranges/objects strategy_trader.chart.set_range(0, depth) strategy_trader.chart.plot_price_serie(0, last_prices) strategy_trader.chart.plot_price_serie(1, sma) strategy_trader.chart.plot_price_serie(2, ema) strategy_trader.chart.plot_price_serie(3, vwma) strategy_trader.chart.annotate_price(0, longs, 'g^') strategy_trader.chart.annotate_price(1, shorts, 'r^') strategy_trader.chart.plot_serie(1, 0, rsi) strategy_trader.chart.plot_serie(1, 1, [30]*len(rsi)) strategy_trader.chart.plot_serie(1, 2, [70]*len(rsi)) # strategy_trader.chart.plot_serie(2, 0, mmt) strategy_trader.chart.draw()