"MARKET", "session": "NORMAL", "duration": "DAY", "orderStrategyType": "SINGLE", "orderLegCollection": [{ "instruction": "BUY", "quantity": int(qty), "instrument": { "symbol": ticker, "assetType": "EQUITY" } }] } return data stocks = pd.read_csv(CSV_FILE) df = pd.DataFrame(stocks) orders = [] for i in range(len(df)): order = build_order(df.loc[i, "Ticker"], df.loc[i, "Qty"]) orders.append(order) TDSession.login() for order in orders: order_response = TDSession.place_order(account=ACCOUNT_NUMBER, order=order)
class AmeritradeRebalanceUtils: def __init__(self): self.session = None self.account = None self.account_id = None def auth(self, credentials_path='./td_state.json', client_path='./td_client_auth.json'): with open(client_path) as f: data = json.load(f) self.session = TDClient( client_id=data['client_id'], redirect_uri=data['callback_url'], credentials_path=credentials_path ) self.session.login() # assuming only 1 account under management self.account = self.session.get_accounts(fields=['positions'])[0] self.account_id = self.account['securitiesAccount']['accountId'] return self.session def get_portfolio(self): positions = self.account['securitiesAccount']['positions'] portfolio = {} for position in positions: portfolio[position['instrument']['symbol']] = position['longQuantity'] return portfolio def place_orders_dry_run(self, portfolio_diff: dict): result = portfolio_diff.copy() prices = self._get_last_prices(result) for ticker, qty in portfolio_diff.items(): round_qty = round(qty) abs_rounded_qty = abs(round_qty) result[ticker] = { 'instruction': ('BUY' if qty > 0 else 'SELL'), 'qty': abs_rounded_qty, 'money_movement': round_qty*prices[ticker]*-1 } return result def place_orders(self, place_orders_dry_run: dict): result = [] for ticker, order in place_orders_dry_run.items(): res = self.session.place_order(account=self.account_id, order=self._get_market_order_payload(ticker, order['qty'], order['instruction'])) result.append(res) return result def _get_market_order_payload(self, ticker, quantity, instruction='BUY'): return { "orderType": "MARKET", "session": "NORMAL", "duration": "DAY", "orderStrategyType": "SINGLE", "orderLegCollection": [ { "instruction": instruction, "quantity": quantity, "instrument": { "symbol": ticker, "assetType": "EQUITY" } } ] } def _get_last_prices(self, portfolio: dict): quotes = self.session.get_quotes(instruments=portfolio.keys()) portfolio_prices = portfolio.copy() for ticker, _ in portfolio_prices.items(): portfolio_prices[ticker] = quotes[ticker]['lastPrice'] return portfolio_prices
# Login to the session td_session.login() # Define the Order. order_template = buy_limit_enter = { "orderType": "LIMIT", "session": "NORMAL", "duration": "DAY", "price": 10.0, "orderStrategyType": "SINGLE", "orderLegCollection": [ { "instruction": "BUY", "quantity": 1, "instrument": { "symbol": "AAL", "assetType": "EQUITY" } } ] } # Place the Order. order_response = td_session.place_order( account=ACCOUNT_NUMBER, order=order_template ) # Print the Response. pprint.pprint(order_response)
# Define the DURATION of the Order - ENUM EXAMPLE. new_order.order_duration(duration=DURATION.GOOD_TILL_CANCEL) # Define a new OrderLeg Object. new_order_leg = OrderLeg() # Define the ORDER INSTRUCTION - ENUM EXAMPLE. new_order_leg.order_leg_instruction(instruction=ORDER_INSTRUCTIONS.SELL) # Define the PRICE - CAN ONLY BE A FLOAT. new_order_leg.order_leg_price(price=112.50) # Define the QUANTITY - CAN ONLY BE A INTEGER. new_order_leg.order_leg_quantity(quantity=10) # Define the ASSET to be traded - ENUM EXAMPLE -- SYMBOL MUST ALWAYS BE A STRING. new_order_leg.order_leg_asset(asset_type=ORDER_ASSET_TYPE.EQUITY, symbol='MSFT') # Once we have built our order leg, we can add it to our OrderObject. new_order.add_order_leg(order_leg=new_order_leg) # Create a new session td_session = TDClient(account_number=ACCOUNT_NUMBER, account_password=ACCOUNT_PASSWORD, consumer_id=CONSUMER_ID, redirect_uri=REDIRECT_URI) td_session.place_order(account='11111', order=new_order)
class TDAccount(AbstractAccount): def valid_scope(self, scope: Scope): for code in scope.codes: cc = code.split("_") symbol = cc[0] symbol_type = cc[1] if symbol_type != 'STK': raise NotImplementedError res = self.client.search_instruments(symbol, "symbol-search") if not res or len(res) <= 0 or symbol not in res: raise RuntimeError("没有查询到资产,code:" + code) if res[symbol]['assetType'] != 'EQUITY': raise RuntimeError("资产不是股票类型,暂不支持") def __init__(self, name: str, initial_cash: float): super().__init__(name, initial_cash) self.account_id = None self.client: TDClient = None self.td_orders: Mapping[str, TDOrder] = {} self.start_sync_order_thread() def with_client(self, client_id, redirect_uri, credentials_path, account_id): self.account_id = account_id self.client = TDClient(client_id=client_id, redirect_uri=redirect_uri, credentials_path=credentials_path) self.client.login() @do_log(target_name='下单', escape_params=[EscapeParam(index=0, key='self')]) @alarm(target='下单', escape_params=[EscapeParam(index=0, key='self')]) @retry(limit=3) def place_order(self, order: Order): td_order = TDOrder(order, self.order_callback, self) try: resp = self.client.place_order(self.account_id, td_order.to_dict()) except Exception as e: order.status = OrderStatus.FAILED raise e td_order_id = resp.get('order_id') order.td_order_id = td_order_id self.sync_order(td_order) if order.status == OrderStatus.CREATED or order.status == OrderStatus.FAILED: if order.status == OrderStatus.CREATED: self.cancel_open_order(order) raise RuntimeError("place order error") self.td_orders[td_order_id] = td_order def start_sync_order_thread(self): def do_sync(): while True: for td_order_id in self.td_orders.keys(): td_order = self.td_orders.get(td_order_id) if td_order.framework_order.status in [ OrderStatus.FAILED, OrderStatus.FILLED, OrderStatus.CANCELED ]: # 如果订单到终态,就不需要同步 continue try: self.sync_order(td_order) except: import traceback logging.error("{}".format(traceback.format_exc())) import time time.sleep(0.5) threading.Thread(target=do_sync, name="sync td orders").start() @alarm(level=AlarmLevel.ERROR, target="同步订单", escape_params=[EscapeParam(index=0, key='self')]) def sync_order(self, td_order: TDOrder): """ 同步订单状态以及订单执行情况 :return: """ if not td_order.td_order_id: raise RuntimeError("非法的td订单") if td_order.framework_order.status in [ OrderStatus.FAILED, OrderStatus.CANCELED, OrderStatus.FILLED ]: logging.info("订单已经到终态,不需要同步") return o: dict = self.client.get_orders(self.account_id, td_order.td_order_id) # 更新框架订单的状态 td_order_status: str = o.get('status') if td_order_status in ['ACCEPTED', 'WORKING', 'QUEUED'] and \ td_order.framework_order.status == OrderStatus.CREATED: td_order.framework_order.status = OrderStatus.SUBMITTED elif 'PENDING' in td_order_status: # pending状态下,不改变状态 pass elif td_order_status == "CANCELED" and td_order.framework_order.status != OrderStatus.CANCELED: td_order.framework_order.status = OrderStatus.CANCELED self.order_callback.order_status_change(td_order.framework_order, None) elif td_order_status == 'FILLED': # filled的状态在account.order_filled中变更 pass else: raise NotImplementedError("无法处理的订单状态:" + td_order_status) # 同步执行详情,由于td的执行详情没有惟一标识,所以每次同步到执行详情时,会将旧的执行详情回滚掉,再应用新的执行详情 executions: List[Dict] = o.get("orderActivityCollection") if executions and len(executions) > 0: filled_quantity = 0 total_cost = 0 # 汇总所有执行详情 for execution in executions: if not execution.get("executionType") == 'FILL': raise NotImplementedError execution_legs: List[Dict] = execution.get("executionLegs") if len(execution_legs) > 1: raise NotImplementedError if execution_legs and len(execution_legs) == 1: execution_leg = execution_legs[0] single_filled_quantity = execution_leg.get("quantity") single_filled_price = execution_leg.get('price') total_cost += single_filled_price * single_filled_quantity filled_quantity += single_filled_quantity filled_avg_price = total_cost / filled_quantity if filled_quantity > td_order.framework_order.filled_quantity: if len(td_order.framework_order.execution_map) > 0: old_version = td_order.framework_order.execution_map.get( "default").version oe = OrderExecution("default", old_version + 1, 0, filled_quantity, filled_avg_price, None, None, td_order.framework_order.direction, None) else: oe = OrderExecution("default", 1, 0, filled_quantity, filled_avg_price, None, None, td_order.framework_order.direction, None) self.order_filled(td_order.framework_order, oe) def match(self, data): raise NotImplementedError @do_log(target_name='取消订单', escape_params=[EscapeParam(index=0, key='self')]) @alarm(target='取消订单', escape_params=[EscapeParam(index=0, key='self')]) @retry(limit=3) def cancel_open_order(self, open_order: Order): if not open_order.td_order_id or open_order.td_order_id not in self.td_orders: raise RuntimeError("没有td订单号") if open_order.status == OrderStatus.CANCELED: return td_order = self.td_orders[open_order.td_order_id] self.client.cancel_order(self.account_id, open_order.td_order_id) self.sync_order(td_order) open_order.status = OrderStatus.CANCELED self.order_callback.order_status_change(open_order, self) def update_order(self, order: Order, reason): if not order.td_order_id or (order.td_order_id not in self.td_orders): raise RuntimeError("没有td订单号") if not isinstance(order, LimitOrder): raise NotImplementedError self.cancel_open_order(order) new_order = LimitOrder(order.code, order.direction, order.quantity - order.filled_quantity, Timestamp.now(tz='Asia/Shanghai'), order.limit_price, None) self.place_order(new_order) def start_save_thread(self): # 启动账户保存线程,每隔半小时会保存当前账户的操作数据 def save(): while True: try: logging.info("开始保存账户数据") self.save() except: import traceback err_msg = "保存账户失败:{}".format(traceback.format_exc()) logging.error(err_msg) import time time.sleep(30 * 60) threading.Thread(name="account_save", target=save).start()