class OrderBook: def __init__(self, bids=[], asks=[]): self.bids = SortedList(bids, key = lambda order: -order.price) self.asks = SortedList(asks, key = lambda order: order.price) def __len__(self): return len(self.bids) + len(self.asks) def best_bid(self): if len(self.bids) > 0: return self.bids[0].price else: return 0 def best_ask(self): if len(self.asks) > 0: return self.asks[0].price else: return 0 def add(self, order): if order.side == 'buy': index = self.bids.bisect_right(order) self.bids.insert(index, order) elif order.side == 'sell': index = self.asks.bisect_right(order) self.asks.insert(index, order) def remove(self, order): if order.side == 'buy': self.bids.remove(order) elif order.side == 'sell': self.asks.remove(order)
class FastSplitter2d: def __init__(self, max_size=5000, chunk_count=5): self.max_size = max_size self.max_x = 0 self.points = SortedList(key=lambda p: -p[1]) self.chunk_count = chunk_count def add_to_pack(self, p): self.max_x = max(self.max_x, p[0]) new_pos = self.points.bisect_right(p) self.points.insert(new_pos, p) offset = 0 bs_vec = [] while offset < len(self.points): bs = self.max_size // (self.max_x + self.points[offset][1]) bs = min(len(self.points) - offset, bs) bs_vec.append(bs) offset += bs return new_pos, bs_vec def make_chunk_gen(self, points): prev_bs_vec = [0] for p in sorted(list(points), key=lambda p: p[0], reverse=True): new_pos, bs_vec = self.add_to_pack(p) if len(bs_vec) > len(prev_bs_vec): if len(prev_bs_vec) >= self.chunk_count: self.points.pop(new_pos) offset = 0 for sz in prev_bs_vec: yield self.points[offset:offset + sz] offset += sz self.points.clear() self.points.add(p) prev_bs_vec = [1] self.max_x = p[0] prev_bs_vec = bs_vec offset = 0 for sz in prev_bs_vec: yield self.points[offset:offset + sz] offset += sz
def test_insert_valueerror4(): slt = SortedList(range(10), load=4) slt.insert(5, 7)
def test_insert(): slt = SortedList(range(10), load=4) slt.insert(-1, 9) slt._check() slt.insert(-100, 0) slt._check() slt.insert(100, 10) slt._check() slt = SortedList(load=4) slt.insert(0, 5) slt._check() slt = SortedList(range(5, 15), load=4) for rpt in range(8): slt.insert(0, 4) slt._check() slt = SortedList(range(10), load=4) slt.insert(8, 8) slt._check()
class PortfolioHistory(SortedDict): """Represents the historical holdings of a portfolio. Usually this class should only be instantiated by GetPortfolio. """ def __init__(self, user_id): super(PortfolioHistory, self).__init__() self._user_id = user_id with sql.GetCursor() as cursor: cursor.execute( 'SELECT type, timestamp, in_symbol, in_amount, out_symbol, out_amount ' 'FROM transactions where user_id = %s' % user_id) self._transactions = SortedList([ Transaction(type=t[0], timestamp=t[1], in_symbol=t[2], in_amount=t[3], out_symbol=t[4], out_amount=t[5]) for t in cursor.fetchall() ]) self.InitFromTransactions() def InitFromTransactions(self): # TODO(brandonsalmon): If it becomes necessary, we can greatly improve # the performance of !buy, !sell, !trade, by adding a transaction cursor # and not reinitializing all transactions every time. self.clear() for t in self._transactions: if t.type == "INIT": self[t.timestamp] = {} continue if t.timestamp not in self: bisect_point = self.bisect(t.timestamp) if (bisect_point) is 0: copy = {} else: copy = self[self._list[bisect_point - 1]].copy() self[t.timestamp] = copy if t.in_symbol: if t.in_symbol not in self[t.timestamp]: self[t.timestamp][t.in_symbol] = 0 self[t.timestamp][t.in_symbol] += t.in_amount if t.out_symbol: if t.out_symbol not in self[t.timestamp]: raise Exception( '%s tried to remove coin %s they didn\'t own' % (self._user_id, t.out_symbol)) self[t.timestamp][t.out_symbol] -= t.out_amount if self[t.timestamp][t.out_symbol] < 1e-10: del self[t.timestamp][t.out_symbol] def CreationDate(self): return self._transactions[0].timestamp def GetValueList(self, t_list): return [self.Value(t) for t in t_list] def GetChange(self, timestamp=None, timedelta='24h'): dt = datetime.fromtimestamp(timestamp) if timestamp else datetime.now() old_timestamp = (dt - util.GetTimeDelta(timedelta)).timestamp() old_value = self.Value(old_timestamp) new_value = self.Value(timestamp) if old_value != 0: return '%.2f%s' % (100 * (new_value - old_value) / old_value, '%') elif new_value == 0: return "No change" elif new_value > 0: return "+Inf%" else: return "-Inf%" def ClearRemote(self): with sql.GetCursor() as cursor: cursor.execute('DELETE FROM transactions where user_id = %s' % self._user_id) self.clear() def Init(self, tuples, timestamp=None): """Takes a list of tuples of (symbol, amount).""" timestamp = int(timestamp if timestamp else time.time()) with sql.GetCursor() as cursor: cursor.execute( 'INSERT INTO transactions (user_id, type, timestamp) ' 'values (%s, "%s", %s)' % (self._user_id, "INIT", timestamp)) transaction = Transaction(type="INIT", timestamp=timestamp) self._transactions.insert(self._transactions.bisect(transaction), transaction) for t in tuples: self.Buy(t[0], t[1], timestamp, init=False) self.InitFromTransactions() def Buy(self, symbol, amount, timestamp=None, init=True): timestamp = int(timestamp if timestamp else time.time()) with sql.GetCursor() as cursor: cursor.execute( 'INSERT INTO transactions (user_id, type, timestamp, in_symbol, in_amount) ' 'values (%s, "%s", %s, "%s", %s)' % (self._user_id, "BUY", timestamp, symbol.upper(), amount)) transaction = Transaction(type="BUY", timestamp=timestamp, in_symbol=symbol.upper(), in_amount=amount) self._transactions.insert(self._transactions.bisect(transaction), transaction) if init: self.InitFromTransactions() def Sell(self, symbol, amount, timestamp=None): timestamp = int(timestamp if timestamp else time.time()) with sql.GetCursor() as cursor: cursor.execute( 'INSERT INTO transactions (user_id, type, timestamp, out_symbol, out_amount) ' 'values (%s, "%s", %s, "%s", %s)' % (self._user_id, "SELL", timestamp, symbol.upper(), amount)) transaction = Transaction(type="SELL", timestamp=timestamp, out_symbol=symbol.upper(), out_amount=amount) self._transactions.insert(self._transactions.bisect(transaction), transaction) self.InitFromTransactions() def Trade(self, in_symbol, in_amount, out_symbol, out_amount, timestamp=None): timestamp = int(timestamp if timestamp else time.time()) with sql.GetCursor() as cursor: cursor.execute( 'INSERT INTO transactions (user_id, type, timestamp, in_symbol, in_amount, ' 'out_symbol, out_amount) values (%s, "%s", %s, "%s", %s, "%s", %s)' % (self._user_id, "SELL", timestamp, in_symbol.upper(), in_amount, out_symbol.upper(), out_amount)) transaction = Transaction(type="TRADE", timestamp=timestamp, out_symbol=out_symbol.upper(), out_amount=out_amount, in_symbol=in_symbol.upper(), in_amount=in_amount) self._transactions.insert(self._transactions.bisect(transaction), transaction) self.InitFromTransactions() def Value(self, timestamp=None): try: if timestamp: bisect_point = self.bisect(timestamp) if (bisect_point) is 0: return 0.0 data = self[self._list[bisect_point - 1]] else: data = self[self._list[-1]] except (IndexError, KeyError): return 0.0 value = 0.0 for symbol, amount in data.items(): price = coin_data.GetHistory(symbol).GetValue(timestamp) value += amount * price return value def GetOwnedCurrency(self, timestamp=None): try: if timestamp: bisect_point = self.bisect(timestamp) if (bisect_point) is 0: return {} return self[self._list[bisect_point - 1]] else: return self[self._list[-1]] except (IndexError, KeyError): return {} def AsTable(self, timestamp=None): tuples = [] for symbol, amount in self.GetOwnedCurrency(timestamp).items(): history = coin_data.GetHistory(symbol) price = history.GetValue(timestamp) curr_value = amount * price change_day = history.GetDayChange(timestamp) tuples.append([ symbol, amount, '$%.2f (%.2f%s)' % (curr_value, change_day, "%"), curr_value ]) tuples = sorted(tuples, key=lambda x: x[3], reverse=True) for t in tuples: t.pop() return tabulate(tuples, tablefmt='fancy_grid', floatfmt='.4f') def BreakTable(self, timestamp=None): tuples = [] for symbol, amount in self.GetOwnedCurrency(timestamp).items(): price = coin_data.GetHistory(symbol).GetValue(timestamp) value_at_t = amount * price tuples.append([ symbol, amount, '%.2f%s' % ((value_at_t / self.Value(timestamp)) * 100, "%"), (value_at_t / self.Value(timestamp)) * 100 ]) tuples = sorted(tuples, key=lambda x: x[3], reverse=True) for t in tuples: t.pop() return tabulate(tuples, tablefmt='fancy_grid', floatfmt='.4f')
def test_insert_valueerror4(): slt = SortedList(range(10)) slt._reset(4) slt.insert(5, 7)
class InclusionTreeBuilder: """ this class builds a tree of polygons included in one another. it works through a sweeping line algorithm. also identifies each as a hole or a polygon. """ def __init__(self, polygons): # the algorithm works in O(n) (times sorted container's costs) in this way: # we have a SortedList of all currently crossed paths self.crossed_paths = SortedList() # for each polygon, a SortedList of all of its currently crossed paths self.polygons = defaultdict(SortedList) # when meeting a new polygon for the first time # we will insert it in the crossed_paths list ; we get it's top neighbour (smaller) # and get the corresponding polygon # now if we are contained inside it, we are its child # if we are not contained inside it, we are its brother # to figure out whether we are inside or not, we look at #paths smaller than us # in the neighbour polygon's SortedList set_comparer(self) # we store all keys used for comparing paths # this speeds up keys computations and more importantly removes # rounding errors self.sweeping_keys = dict() polygons_number = self._create_events(polygons) self.current_point = None self.tree = InclusionTree() self.nodes = dict() # store for each poly its node and father node for event in self.events: self.execute_event(event) if len(self.nodes) == polygons_number: return # no need to finish the sweep once everyone is identified def _create_events(self, polygons): """ create all start/end events for each path. each event is : a comparison key ; the path. """ self.events = [] polygons_number = 0 for height, polygons in polygons.items(): for polygon in polygons: polygons_number += 1 for segment in polygon_segments(height, polygon): angle = segment.key_angle() print("angle for", segment, "is", angle) for point, event_type in zip(sorted(segment.endpoints), (START_EVENT, END_EVENT)): key = (point, event_type, -height) raise Exception("we lack an angle here") self.events.append((key, segment)) self.sweeping_keys[(id(segment), point)] =\ (point.coordinates[1], angle, -height) self.events.sort(key=lambda e: e[0]) return polygons_number def key(self, path): """ returns key at current point for given path. """ key_id = (id(path), self.current_point) if key_id in self.sweeping_keys: return self.sweeping_keys[key_id] else: current_x = self.current_point.coordinates[0] return (path.vertical_intersection_at(current_x), path.key_angle(), -path.height) def execute_event(self, event): """ execute start path or end path event """ event_key, event_path = event event_point, event_type = event_key[0:2] if event_type == START_EVENT: self.current_point = event_point self.start_path(event_path) else: self.end_path(event_path) self.current_point = event_point if __debug__: # very slow paths = iter(self.crossed_paths) previous_path = next(paths, None) for path in paths: if self.key(previous_path) >= self.key(path): paths = list(self.crossed_paths) print(paths) print("previous", previous_path, self.key(previous_path)) print("current", path, self.key(path)) tycat(self.current_point, paths, previous_path, path) raise Exception("pb ordre") previous_path = path def start_path(self, path): """ handles incoming path """ index = self.crossed_paths.bisect(path) self.crossed_paths.insert(index, path) polygon = path.polygon_id() self.polygons[polygon].add(path) if polygon not in self.nodes: father_node = self.identify_father_node(path, index) new_node = father_node.add_child(path) self.nodes[polygon] = (new_node, father_node) print("adding", polygon, "as child of", id(father_node.content)) def identify_father_node(self, path, index): """ identify where polygon is in tree. we need the path and its position in crossed paths """ if index == 0: # no one above us, we are below root return self.tree else: neighbour_polygon = self.crossed_paths[index - 1].polygon_id() above_paths = self.polygons[neighbour_polygon].bisect(path) if above_paths % 2: # odd neighbour's paths above us # we are inside him return self.nodes[neighbour_polygon][0] else: # event neighbour's paths above us # we are beside him return self.nodes[neighbour_polygon][1] def end_path(self, path): """ handles ending path """ print("removing", path, "from", self.crossed_paths) self.crossed_paths.remove(path) self.polygons[path.polygon_id()].remove(path)