Filter
Exclude
Time range
-
Near
import json from pathlib import Path import pyotp import logging import requests from firstrade import urls from firstrade.exceptions import ( AccountResponseError, LoginError, LoginRequestError, LoginResponseError, ) logger = logging.getLogger(__name__) class FTSession: """Class creating a session for Firstrade. This class handles the creation and management of a session for logging into the Firstrade platform. It supports multi-factor authentication (MFA) and can save session cookies for persistent logins. Attributes: username (str): Firstrade login username. password (str): Firstrade login password. pin (str, optional): Firstrade login pin. email (str, optional): Firstrade MFA email. phone (str, optional): Firstrade MFA phone number. mfa_secret (str, optional): Secret key for generating MFA codes. profile_path (str, optional): The path where the user wants to save the cookie pkl file. debug (bool, optional): Log HTTP requests/responses if true. DO NOT POST YOUR LOGS ONLINE. t_token (str, optional): Token used for MFA. otp_options (dict, optional): Options for OTP (One-Time Password) if MFA is enabled. login_json (dict, optional): JSON response from the login request. session (requests.Session): The requests session object used for making HTTP requests. Methods: __init__(username, password, pin=None, email=None, phone=None, mfa_secret=None, profile_path=None, debug=False): Initializes a new instance of the FTSession class. login(): Validates and logs into the Firstrade platform. login_two(code): Finishes the login process to the Firstrade platform. When using email or phone mfa. delete_cookies(): Deletes the session cookies. _load_cookies(): Checks if session cookies were saved and loads them. _save_cookies(): Saves session cookies to a file. _mask_email(email): Masks the email for use in the API. _handle_mfa(): Handles multi-factor authentication. _request(method, url, **kwargs): HTTP requests wrapper to the API. """ def __init__( self, username: str = "", password: str = "", pin: str = "", email: str = "", phone: str = "", mfa_secret: str = "", profile_path: str | None = None, *, save_session: bool = False, debug: bool = False ) -> None: """Initialize a new instance of the FTSession class. Args: username (str): Firstrade login username. password (str): Firstrade login password. pin (str, optional): Firstrade login pin. email (str, optional): Firstrade MFA email. phone (str, optional): Firstrade MFA phone number. mfa_secret (str, optional): Firstrade MFA secret key to generate TOTP. profile_path (str, optional): The path where the user wants to save the cookie json file. save_session (bool, optional): Save session cookies if true. debug (bool, optional): Log HTTP requests/responses if true. DO NOT POST YOUR LOGS ONLINE. """ self.username: str = username self.password: str = password self.pin: str = pin self.email: str = FTSession._mask_email(email) if email else "" self.phone: str = phone self.mfa_secret: str = mfa_secret self.profile_path: str | None = profile_path self.save_session: bool = save_session # Flag to save session cookies self.debug: bool = debug if self.debug: logging.basicConfig(level=logging.DEBUG) # Enable HTTP connection debug output import http.client as http_client http_client.HTTPConnection.debuglevel = 1 # requests logging too logging.getLogger("requests.packages.urllib3").setLevel(logging.DEBUG) logging.getLogger("requests.packages.urllib3").propagate = True self.t_token: str | None = None self.otp_options: str | list[dict[str, str]] | None = None self.login_json: dict[str, str] = {} self.session = requests.Session() def login(self) -> bool: """Validate and log into the Firstrade platform. This method sets up the session headers, loads cookies if available, and performs the login request. It handles multi-factor authentication (MFA) if required. Raises: LoginRequestError: If the login request fails with a non-200 status code. LoginResponseError: If the login response contains an error message. """ self.session.headers.update(urls.session_headers()) ftat: str = self._load_cookies() if ftat: self.session.headers["ftat"] = ftat response: requests.Response = self._request("get", url="api3x.firstrade.com/", timeout=10) # type: ignore[arg-type] self.session.headers["access-token"] = urls.access_token() data: dict[str, str] = { "username": r"" self.username, "password": r"" self.password, } response: requests.Response = self._request( method="post", url=urls.login(), data=data, ) try: self.login_json: dict[str, str] = response.json() except json.decoder.JSONDecodeError as exc: error_msg = "Invalid JSON is your account funded?" raise LoginResponseError(error_msg) from exc if "mfa" not in self.login_json and "ftat" in self.login_json and not self.login_json["error"]: self.session.headers["sid"] = self.login_json["sid"] return False self.t_token: str | None = self.login_json.get("t_token") if not self.login_json.get("mfa"): self.otp_options = self.login_json.get("otp") if response.status_code != 200: raise LoginRequestError(response.status_code) if self.login_json["error"]: raise LoginResponseError(self.login_json["error"]) need_code: bool | None = self._handle_mfa() if self.login_json["error"]: raise LoginResponseError(self.login_json["error"]) if need_code: return True self.session.headers["ftat"] = self.login_json["ftat"] self.session.headers["sid"] = self.login_json["sid"] if self.save_session: self._save_cookies() return False def login_two(self, code: str) -> None: """Finish login to the Firstrade platform.""" data: dict[str, str | None] = {} if self.login_json.get("mfa"): data.update({ "mfaCode": code, "remember_for": "30", "t_token": self.t_token, }) else: data: dict[str, str | None] = { "otpCode": code, "verificationSid": self.session.headers["sid"], "remember_for": "30", "t_token": self.t_token, } response: requests.Response = self._request(method="post", url=urls.verify_pin(), data=data) self.login_json: dict[str, str] = response.json() if self.login_json["error"]: raise LoginResponseError(self.login_json["error"]) self.session.headers["ftat"] = self.login_json["ftat"] self.session.headers["sid"] = self.login_json["sid"] if self.save_session: self._save_cookies() def delete_cookies(self) -> None: """Delete the session cookies.""" path: Path = Path(self.profile_path) / f"ft_cookies{self.username}.json" if self.profile_path is not None else Path(f"ft_cookies{self.username}.json") path.unlink() def get_tokens(self) -> dict[str, str | bytes | dict[str, str] | None]: """Return the current session tokens (access_token, ftat, sid and cookies).""" cookies: dict[str, str] = self.session.cookies.get_dict() return { "access-token": self.session.headers.get("access-token"), "ftat": self.session.headers.get("ftat"), "sid": self.session.headers.get("sid"), "cookies": cookies or "", } def build_session_from_tokens(self, tokens: dict[str, str | bytes | dict[str, str] | None]) -> None: """Build the session headers and cookies from provided tokens.""" self.session.headers.update(urls.session_headers()) if tokens: access_token = tokens.get("access-token") ftat_token = tokens.get("ftat") sid_token = tokens.get("sid") if isinstance(access_token, (str, bytes)): self.session.headers.update({"access-token": access_token}) if isinstance(ftat_token, (str, bytes)): self.session.headers.update({"ftat": ftat_token}) if isinstance(sid_token, (str, bytes)): self.session.headers.update({"sid": sid_token}) cookies = tokens.get("cookies") if isinstance(cookies, dict): self.session.cookies.update(cookies) # type: ignore[arg-type] def _load_cookies(self) -> str | None: """Check if session cookies were saved. Returns ------- str: The saved session token. """ ftat = "" directory: Path = Path(self.profile_path) if self.profile_path is not None else Path() if not directory.exists(): directory.mkdir(parents=True) for filepath in directory.iterdir(): if filepath.name.endswith(f"{self.username}.json"): with filepath.open(mode="r") as f: ftat: str = json.load(fp=f) return ftat def _save_cookies(self) -> str | None: """Save session cookies to a file.""" # Allow providing "ftat" token from an external source if self.save_session: if self.profile_path: directory = Path(self.profile_path) if not directory.exists(): directory.mkdir(parents=True) path: Path = directory / f"ft_cookies{self.username}.json" else: path = Path(f"ft_cookies{self.username}.json") with path.open("w") as f: ftat: str | None = self.session.headers.get("ftat") json.dump(obj=ftat, fp=f) @staticmethod def _mask_email(email: str) -> str: """Mask the email for use in the API. Args: email (str): The email address to be masked. Returns: str: The masked email address. """ local, domain = email.split(sep="@") masked_local: str = local[0] "*" * 4 domain_name, tld = domain.split(".") masked_domain: str = domain_name[0] "*" * 4 return f"{masked_local}@{masked_domain}.{tld}" def _handle_mfa(self) -> bool: """Handle multi-factor authentication. This method processes the MFA requirements based on the login response and user-provided details. """ response: requests.Response | None = None data: dict[str, str | None] = {} if self.pin: response: requests.Response = self._handle_pin_mfa(data) self.login_json = response.json() elif (self.email or self.phone) and not self.login_json.get("mfa"): response: requests.Response = self._handle_otp_mfa(data) self.login_json = response.json() elif self.mfa_secret: response: requests.Response = self._handle_secret_mfa(data) self.login_json = response.json() elif self.login_json.get("mfa"): pass # MFA handling without user provided secret in login_two else: error_msg = "MFA required but no valid MFA method was provided (pin, email/phone, or mfa_secret)." raise LoginError(error_msg) if self.login_json["error"]: raise LoginResponseError(self.login_json["error"]) if self.pin or self.mfa_secret: self.session.headers["sid"] = self.login_json["sid"] return False if self.login_json.get("mfa") and not self.mfa_secret: return True self.session.headers["sid"] = self.login_json["verificationSid"] return True def _handle_pin_mfa(self, data: dict[str, str | None]) -> requests.Response: """Handle PIN-based MFA.""" data.update({ "pin": self.pin, "remember_for": "30", "t_token": self.t_token, }) return self._request("post", urls.verify_pin(), data=data) def _handle_otp_mfa(self, data: dict[str, str | None]) -> requests.Response: """Handle email/phone OTP-based MFA.""" if not self.otp_options: error_msg = "No OTP options available." raise LoginResponseError(error_msg) for item in self.otp_options: if (item["channel"] == "sms" and self.phone and self.phone in item["recipientMask"]) or (item["channel"] == "email" and self.email and self.email == item["recipientMask"]): data.update({ "recipientId": item["recipientId"], "t_token": self.t_token, }) break return self._request("post", urls.request_code(), data=data) def _handle_secret_mfa(self, data: dict[str, str | None]) -> requests.Response: """Handle MFA secret-based authentication.""" mfa_otp = pyotp.TOTP(self.mfa_secret).now() data.update({ "mfaCode": mfa_otp, "remember_for": "30", "t_token": self.t_token, }) return self._request("post", urls.verify_pin(), data=data) def _request(self, method: str, url: str, **kwargs: object) -> requests.Response: """Send HTTP request and log the full response content if debug=True.""" resp = self.session.request(method, url, **kwargs) # type: ignore[no-untyped-call] if self.debug: # Suppress urllib3 / http.client debug so we only see this log logging.getLogger("urllib3").setLevel(logging.WARNING) # Basic request info logger.debug(f">>> {method.upper()} {url}") logger.debug(f"<<< Status: {resp.status_code}") logger.debug(f"<<< Headers: {resp.headers}") # Log raw bytes length try: logger.debug(f"<<< Raw bytes length: {len(resp.content)}") except Exception as e: logger.debug(f"<<< Could not read raw bytes: {e}") # Log pretty JSON (if any) try: import json as pyjson # This automatically uses requests decompression if gzip is set json_body = resp.json() pretty = pyjson.dumps(json_body, indent=2) logger.debug(f"<<< JSON body:\n{pretty}") except Exception as e: # If JSON decoding fails, fallback to raw text try: logger.debug(f"<<< Body (text):\n{resp.text}") except Exception as e2: logger.debug(f"<<< Could not read body text: {e2}") return resp def __getattr__(self, name: str) -> object: """Forward unknown attribute access to session object. Args: name (str): The name of the attribute to be accessed. Returns: The value of the requested attribute from the session object. """ return getattr(self.session, name) class FTAccountData: """Dataclass for storing account information.""" def __init__(self, session: requests.Session) -> None: """Initialize a new instance of the FTAccountData class. Args: session (requests.Session): The session object used for making HTTP requests. """ self.session: requests.Session = session self.all_accounts: list[dict[str, object]] = [] self.account_numbers: list[str] = [] self.account_balances: dict[str, object] = {} response: requests.Response = self.session._request("get", url=urls.user_info()) self.user_info: dict[str, object] = response.json() response: requests.Response = self.session._request("get", urls.account_list()) if response.status_code != 200 or response.json()["error"]: raise AccountResponseError(response.json()["error"]) self.all_accounts = response.json() for item in self.all_accounts["items"]: self.account_numbers.append(item["account"]) self.account_balances[item["account"]] = item["total_value"] def get_account_balances(self, account: str) -> dict[str, object]: """Get account balances for a given account. Args: account (str): Account number of the account you want to get balances for. Returns: dict: Dict of the response from the API. """ response: requests.Response = self.session._request("get", urls.account_balances(account)) return response.json() def get_positions(self, account: str) -> dict[str, object]: """Get currently held positions for a given account. Args: account (str): Account number of the account you want to get positions for. Returns: dict: Dict of the response from the API. """ response = self.session._request("get", urls.account_positions(account)) return response.json() def get_account_history( self, account: str, date_range: str = "ytd", custom_range: list[str] | None = None, ) -> dict[str, object]: """Get account history for a given account. Args: account (str): Account number of the account you want to get history for. date_range (str): The range of the history. Defaults to "ytd". Available options are ["today", "1w", "1m", "2m", "mtd", "ytd", "ly", "cust"]. custom_range (list[str] | None): The custom range of the history. Defaults to None. If range is "cust", this parameter is required. Format: ["YYYY-MM-DD", "YYYY-MM-DD"]. Returns: dict: Dict of the response from the API. """ if date_range == "cust" and custom_range is None: raise ValueError("Custom range required.") response: requests.Response = self.session._request( "get", urls.account_history(account, date_range, custom_range), ) return response.json() def get_orders(self, account: str, per_page: int = 0) -> list[dict[str, object]]: """Retrieve existing order data for a given account. Args: account (str): Account number of the account to retrieve orders for. per_page (int): Number of orders to retrieve per page. Defaults to 0 (all orders). Returns: list: A list of dictionaries, each containing details about an order. """ response = self.session._request("get", url=urls.order_list(account, per_page)) return response.json() def cancel_order(self, order_id: str) -> dict[str, object]: """Cancel an existing order. Args: order_id (str): The order ID to cancel. Returns: dict: A dictionary containing the response data. """ data = { "order_id": order_id, } response = self.session._request("post", url=urls.cancel_order(), data=data) return response.json() def get_balance_overview(self, account: str, keywords: list[str] | None = None) -> dict[str, object]: """Return a filtered, flattened view of useful balance fields. This is a convenience helper over `get_account_balances` to quickly surface likely relevant numbers such as cash, available cash, and buying power without needing to know the exact response structure. Args: account (str): Account number to query balances for. keywords (list[str], optional): Additional case-insensitive substrings to match in keys. Defaults to a sensible set for balances. Returns: dict: A dict mapping dot-notated keys to values from the balances response where the key path contains any of the keywords. """ if keywords is None: keywords = [ "cash", "avail", "withdraw", "buying", "bp", "equity", "value", "margin", ] payload: dict[str, object] = self.get_account_balances(account) filtered: dict[str, object] = {} def _walk(node: object, path: list[str]) -> None: if isinstance(node, dict): for k, v in node.items(): _walk(node=v, path=[*path, str(object=k)]) elif isinstance(node, list): for i, v in enumerate(iterable=node): _walk(node=v, path=[*path, str(object=i)]) else: key_path: str = ".".join(path) low: str = key_path.lower() if any(sub in low for sub in keywords): filtered[key_path] = node _walk(node=payload, path=[]) return filtered

4
1
3,910
import enum from firstrade import urls from firstrade.account import FTSession class PriceType(enum.StrEnum): """Enum for valid price types in an order. Attributes: MARKET (str): Market order, executed at the current market price. LIMIT (str): Limit order, executed at a specified price or better. STOP (str): Stop order, becomes a market order once a specified price is reached. STOP_LIMIT (str): Stop-limit order, becomes a limit order once a specified price is reached. TRAILING_STOP_DOLLAR (str): Trailing stop order with a specified dollar amount. TRAILING_STOP_PERCENT (str): Trailing stop order with a specified percentage. """ LIMIT = "2" MARKET = "1" STOP = "3" STOP_LIMIT = "4" TRAILING_STOP_DOLLAR = "5" TRAILING_STOP_PERCENT = "6" class Duration(enum.StrEnum): """Enum for valid order durations. Attributes: DAY (str): Day order (9:30 AM - 4 PM ET) DAY_EXT (str): Day extended order (8 AM - 8 PM ET). OVERNIGHT (str): Overnight order (8 PM - 4 AM ET). GT90 (str): Good till 90 days order (9:30 AM - 4 PM ET). """ DAY = "0" DAY_EXT = "D" OVERNIGHT = "N" GT90 = "1" class OrderType(enum.StrEnum): """Enum for valid order types. Attributes: BUY (str): Buy order. SELL (str): Sell order. SELL_SHORT (str): Sell short order. BUY_TO_COVER (str): Buy to cover order. BUY_OPTION (str): Buy option order. SELL_OPTION (str): Sell option order. """ BUY = "B" SELL = "S" SELL_SHORT = "SS" BUY_TO_COVER = "BC" BUY_OPTION = "BO" SELL_OPTION = "SO" class OrderInstructions(enum.StrEnum): """Enum for valid order instructions. Attributes: NONE (str): No special instruction. AON (str): All or none. OPG (str): At the Open. CLO (str): At the Close. """ NONE = "0" AON = "1" OPG = "4" CLO = "5" class OptionType(enum.StrEnum): """Enum for valid option types. Attributes: CALL (str): Call option. PUT (str): Put option. """ CALL = "C" PUT = "P" class Order: """Represents an order with methods to place it. Attributes: ft_session (FTSession): The session object for placing orders. """ def __init__(self, ft_session: FTSession) -> None: """Initialize the Order with a FirstTrade session.""" self.ft_session: FTSession = ft_session def place_order( self, account: str, symbol: str, price_type: PriceType, order_type: OrderType, duration: Duration, quantity: int = 0, price: float = 0.00, stop_price: float | None = None, *, dry_run: bool = True, notional: bool = False, order_instruction: OrderInstructions = OrderInstructions.NONE, ): """Build and place an order. Args: account (str): The account number to place the order in. symbol (str): The ticker symbol for the order. price_type (PriceType): The price type for the order (e.g., LIMIT, MARKET, STOP). order_type (OrderType): The type of order (e.g., BUY, SELL). duration (Duration): The duration of the order (e.g., DAY, GT90). quantity (int, optional): The number of shares to buy or sell. Defaults to 0. price (float, optional): The price at which to buy or sell the shares. Defaults to 0.00. stop_price (float, optional): The stop price for stop orders. Defaults to None. dry_run (bool, optional): If True, the order will not be placed but will be built and validated. Defaults to True. notional (bool, optional): If True, the order will be placed based on a notional dollar amount rather than share quantity. Defaults to False. order_instruction (OrderInstructions, optional): Additional order instructions (e.g., AON, OPG). Defaults to "0". Returns: dict: A dictionary containing the order confirmation data. """ if price_type == PriceType.MARKET and not notional: price = "" if order_instruction == OrderInstructions.AON and price_type != PriceType.LIMIT: raise ValueError("AON orders must be a limit order.") if order_instruction == OrderInstructions.AON and quantity <= 100: raise ValueError("AON orders must be greater than 100 shares.") data = { "symbol": symbol, "transaction": order_type, "shares": quantity, "duration": duration, "preview": "true", "instructions": order_instruction, "account": account, "price_type": price_type, "limit_price": "0", } if notional: data["dollar_amount"] = price del data["shares"] if price_type in {PriceType.LIMIT, PriceType.STOP_LIMIT}: data["limit_price"] = price if price_type in {PriceType.STOP, PriceType.STOP_LIMIT}: data["stop_price"] = stop_price response: requests.Response = self.ft_session._request("post", url=urls.order(), data=data) if response.status_code != 200 or response.json()["error"] != "": return response.json() preview_data = response.json() if dry_run: return preview_data data["preview"] = "false" data["stage"] = "P" response = self.ft_session._request("post", url=urls.order(), data=data) return response.json() def place_option_order( self, account: str, option_symbol: str, price_type: PriceType, order_type: OrderType, contracts: int, duration: Duration, stop_price: float | None = None, price: float = 0.00, *, dry_run: bool = True, order_instruction: OrderInstructions = OrderInstructions.NONE, ): """Build and place an option order. Args: account (str): The account number to place the order in. option_symbol (str): The option ticker symbol for the order. price_type (PriceType): The price type for the order (e.g., LIMIT, MARKET, STOP). order_type (OrderType): The type of order (e.g., BUY, SELL). contracts (int): The number of option contracts to buy or sell. duration (Duration): The duration of the order (e.g., DAY, GT90). stop_price (float, optional): The stop price for stop orders. Defaults to None. price (float, optional): The price at which to buy or sell the option contracts. Defaults to 0.00. dry_run (bool, optional): If True, the order will not be placed but will be built and validated. Defaults to True. order_instruction (OrderInstructions, optional): Additional order instructions (e.g., AON, OPG). Defaults to "0". Raises: ValueError: If AON orders are not limit orders or if AON orders have a quantity of 100 contracts or less. Returns: dict: A dictionary containing the order confirmation data. """ if order_instruction == OrderInstructions.AON and price_type != PriceType.LIMIT: raise ValueError("AON orders must be a limit order.") if order_instruction == OrderInstructions.AON and contracts <= 100: raise ValueError("AON orders must be greater than 100 shares.") data = { "duration": duration, "instructions": order_instruction, "transaction": order_type, "contracts": contracts, "symbol": option_symbol, "preview": "true", "account": account, "price_type": price_type, } if price_type in {PriceType.LIMIT, PriceType.STOP_LIMIT}: data["limit_price"] = price if price_type in {PriceType.STOP, PriceType.STOP_LIMIT}: data["stop_price"] = stop_price response = self.ft_session._request("post", url=urls.option_order(), data=data) if response.status_code != 200 or response.json()["error"] != "": return response.json() if dry_run: return response.json() data["preview"] = "false" response = self.ft_session._request("post", url=urls.option_order(), data=data) return response.json()

3
3,510
$SPCX 湊熱鬧 😎 原來 Firstrade 還會寄 S-1 來 訂閱免費電子報 SpaceX 要 IPO 了 你買的是現金牛還是無底洞? open.substack.com/pub/basm/p…
14
1,761
Yz W retweeted
【26年最新】第一证券Firstrade开户入金教程:美股0手续费、无CRS、有Visa卡、支持Wise、众安银行入金Firstrade全流程 美股零佣金券商,第一证券可能是被低估最狠的一家。 不只是免佣,是所有费用全免:股票、ETF、期权、基金,统统零手续费。期权免费这一条,IBKR都做不到。 实测最新开户入金全流程,连补贴怎么薅都讲清楚。 为什么是它? 1985年成立的美国本土券商,受监管 投资者保障。老板是华人,产品和客服对中文用户友好。美国券商不属于CRS,税务上更省心。 新功能也跟上了:夜盘、碎股、闲置股票自动借出生息。资产到2.5万刀还能申请Visa卡,免费寄送,全球券商里独一份。 开户:10分钟搞定 官网选"非美国居民",邮箱注册。支持中国内地、香港、台湾、新加坡身份。 注意三点: 只能用护照,身份证不行 税务识别号填身份证号 地址直接写中文,系统自动翻译 开户时把融资、期权、延长时段、证券借出权限全勾上,反正不要钱。审核一两个工作日。 入金:实测损耗只有$20 收款方是APEX Clearing,美国前三清算公司。钱不经券商的手,分开托管,安全。 我用众安银行汇美元走Swift,全部费用就一笔$20代理行费。25号上午汇出,26号凌晨到账,比官方说的1-4天快多了。 汇款备注一定要填:Firstrade 账号 姓名,漏了到账会麻烦。 补贴别忘了领 新客30天内入金满$2500,可报销$25汇款费,刚好覆盖成本。入口在网页版(APP里没有):客户服务 → 特惠活动。我28号提交,当天到账。 老客也有:单笔汇款满1万刀,每月可申请3次,每次$25。 日常出入金绑Wise走ACH,基本无损,这个之后单独写一篇。 转给想上车美股的朋友。
3
4
18
2,616
منصة أبيان للتداول ؛ رسميا تقدر تتداول بSpace X يوم الإثنين بالأوفرنايت و بالبري / الأفترماركت ؛ و أجزاء السهم مسموح التداول على هالسهم بعد الإفتتاح 4:35PM و كذلك بيسمحون فيها حتى ل Firstrade ، Gate ، Binance منصة عوائد ؛ إستخدم أوامر محددة و لربما تتنفذ معاك و في ناس ماضبطت معاهم منصة Invest Sky المفترض تسمح بها كذلك ؛
692
zgw21cn retweeted
请教一下,最近听说了一个 Firstrade,又叫第一证券,身份证可以直接开。公司老板是美籍华人,对中文的支持非常好,甚至客服都有说中文的。 据说用这个证券账户绕开CRS投资美股很好用,有没有熟悉的朋友来说说看,这个Firstrade 怎么样啊?
32
7
110
58,058
Replying to @Timcast
firstrade wasnt working two days ago. Have also had issues with Sofi. going to try Schwab next.
46
Chunji Zhao retweeted
大家对开户的需求非常高,我整理了下开户完整需要的材料,大家自己去对应的官网申请开户即可,没什么难度。 如果你只是想要一个能买美股的账户,从 Firstrade 或 BBAE 入手最省事,材料最少, 86的手机号就能走完全程。把护照、身份证、手机、邮箱这四样备齐,十几分钟就能走完申请。 刚需材料:护照、身份证、能收短信的手机卡、一个常用邮箱。 需要注意的点: 1.嘉信会多要一张国内地址证明,水电账单或银行账单都行,我是用的招商银行信用卡账单。 2.IBKR 要的是境外居住或工作证明,这是唯一一个普通大陆居民很难绕过去的,不建议大家P图。 3.盈立需要挂梯子申请,审批很轻松,不到一小时就过了,不过盈立也会参与CRS
昨天忙了一天的券商和汇款账户的开户申请,汇报一下战绩 - 新加坡盈立 失败,不允许中国大陆居民开户 - BIT 成功 - Wise 国内 成功 - 嘉信 处理中 - BBAE 处理中 - IBKR 处理中 - Firstrade 处理中
77
63
355
109,657
Jun 12
帰国しても維持できるらしいという理由で作ったFirstradeだけど、まだ数年はこちらにいるつもりなのでRobinhoodに口座移管した。2%のボーナスはありがたい🙏
7
591
Replying to @YaelC_03
用firstrade吧 只炒美股的话界面费用开户流程都比ib阳间多了还支持国内地址不用瞎折腾
1
405
Even retweeted
出海副业实验室 · 第三课:2026 Firstrade 第一证券详细开户全流程(实测) Firstrade 国际账户开户实测:手机上大概 5-10 分钟 就能提交申请。 之前实测了一遍,整体比想象中顺畅不少。 我把实际操作步骤和容易踩坑的地方整理了一下,适合想低成本配置美股、ETF 的朋友参考。 下面直接上流程👇
3
2
5
682
1/ Firstrade 开户入口 直接在手机打开 Firstrade App,切换成简体中文后点击「立即开户」,选择「非美国居民开户」。
1
3
208
Keep up with the market: Today's top earnings are out! #Earnings #Firstrade #Earning
226
Replying to @SuLin2046
bro u have ibkr, schwab, firstrade, ewb, hsbc one, bochk, wise. and ur "anxious." thats not anxiety, thats first-world offshore problems. the real unlock is rite where ur headed: itin us credit cards. 5-figure welcome bonuses, 2-5% cashback, credit that compounds for decades. once ur in u never leave. stop stressing. start executing. ur closer than u think
1
515
Jun 11
我「捡」回了一个盈透证券账户,分享一下 其实也挺简单,2017 年为了支持雪球,从 Firstrade 第一理财转户到了雪盈证券 雪盈证券一直是盈透证券 IBKR 的全批露经纪商 (fully disclosed broker),当时开雪盈,实际上就开了一个 IBKR 账户 全批露经纪商是啥意思呢,雪盈负责获客开户、app 前端服务等 (所有信息完整披露给底层券商),IBKR 负责结算/清算、资产托管、账户系统等底层,合规和资产安全性有保障 前段时间看到 𝕏 上有网友提这个架构,才想起来,去 1Password 里找到了用户名和密码。登录 IBKR 网站时,左上角会有雪盈证券的 logo;登录 IBKR app 时,无 logo。所有功能 (包括入金) 正常使用 当然雪盈应该在 2022 年之后,就不让中国大陆用户注册了 老虎证券在 2019 年获得清算牌照前,也是通过 IBKR 完成交易结算。所以老虎早期的一部分账户 (2019 年前注册的),环球账户是「U 数字」结构的,应该实际上也有一个 IBKR 的账户。可以试试联系 IBKR 客服,提供资料以找回密码。 其他中国人熟悉的券商里,微牛、富途、华盛、长桥、TradeUP 等早期都曾有过 IBKR 通道或类似说法,但不能一概而论,不确定的就看是否有「U 数字」结构的账户
Jun 10
来看个数据 盈透证券 IBKR 前几天,公布了 5 月电子经纪业务的数据,多项核心指标都显著增长 其中,净新增账户数 (Net New Accounts) 达到 13.59 万户,此前二、三、四月份,都稳定在 10 万户左右,同比与环比增长分别高达 81.7% 和 29.6%
4
1
30
11,302