# Technical Documentation: shidenggui/easytrader

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/shidenggui/easytrader)

### Overview (README.md)

# easytrader

[](https://pypi.python.org/pypi/easytrader)
[](https://github.com/shidenggui/easytrader/blob/master/LICENSE)

* 进行股票量化交易
* 通用的同花顺客户端模拟操作
* 支持券商的 [miniqmt](https://easytrader.readthedocs.io/zh-cn/master/miniqmt/) 官方量化接口
* 支持雪球组合调仓和跟踪
* 支持远程操作客户端
* 支持跟踪 `joinquant`, `ricequant` 的模拟交易


### 微信群以及公众号

欢迎大家扫码关注公众号「食灯鬼」，一起交流。进群可通过菜单加我好友，备注量化。



若二维码因 Github 网络无法打开，请点击[公众号二维码](https://camo.githubusercontent.com/6fad032c27b30b68a9d942ae77f8cc73933b95cea58e684657d31b94a300afd5/68747470733a2f2f67697465652e636f6d2f73686964656e676775692f6173736574732f7261772f6d61737465722f755069632f6d702d71722e706e67)直接打开图片。

### Author

> Blog [@shidenggui](https://shidenggui.com) · Weibo [@食灯鬼](https://www.weibo.com/u/1651274491) · Twitter [@shidenggui](https://twitter.com/shidenggui)

### 相关

* [easyquotation 实时获取全市场股票行情](https://github.com/shidenggui/easyquotation)
* [easyquant 简单的量化框架](https://github.com/shidenggui/easyquant)


### 模拟交易

* 雪球组合 by @[haogefeifei](https://github.com/haogefeifei)（[说明](docs/xueqiu.md)）

### 使用文档

[中文文档](https://easytrader.readthedocs.io/)

---

### Index (`docs/index.md`)

# 简介

* 通用的同花顺客户端模拟操作
* 支持券商的 [miniqmt](miniqmt.md) 官方量化接口
* 支持雪球组合调仓和跟踪
* 支持远程操作客户端
* 支持跟踪 `joinquant`, `ricequant` 的模拟交易

### 加微信群以及公众号

欢迎大家扫码关注公众号"食灯鬼"，通过菜单加我好友，备注量化进群




### 支持券商


* 海通客户端(海通网上交易系统独立委托)
* 华泰客户端(网上交易系统（专业版Ⅱ）)
* 国金客户端(全能行证券交易终端PC版)
* 通用同花顺客户端(同花顺免费版)
* 其他券商专用同花顺客户端(需要手动登陆)


### 模拟交易

* 雪球组合 by @[haogefeifei](https://github.com/haogefeifei)（[说明](xueqiu.md)）



### 作者

> Blog [@shidenggui](https://shidenggui.com) · Weibo [@食灯鬼](https://www.weibo.com/u/1651274491) · Twitter [@shidenggui](https://twitter.com/shidenggui)
>

**其他作品**

* [easyquotation 实时获取全市场股票行情](https://github.com/shidenggui/easyquotation)
* [easyquant 简单的量化框架](https://github.com/shidenggui/easyqutant)

---

### Follow (`docs/follow.md`)

# 策略跟踪 

## 跟踪 `joinquant` / `ricequant`  的模拟交易

##### 1) 初始化跟踪的 trader

这里以雪球为例, 也可以使用银河之类 `easytrader` 支持的券商

```
xq_user = easytrader.use('xq')
xq_user.prepare('xq.json')
```

##### 2) 初始化跟踪 `joinquant` / `ricequant` 的 follower

```
target = 'jq'  # joinquant
target = 'rq'  # ricequant
follower = easytrader.follower(target)
follower.login(user='rq/jq用户名', password='rq/jq密码')
```

##### 3) 连接 follower 和 trader

##### joinquant
```
follower.follow(xq_user, 'jq的模拟交易url')
```

注: jq的模拟交易url指的是对应模拟交易对应的可以查看持仓, 交易记录的页面, 类似 `https://www.joinquant.com/algorithm/live/index?backtestId=xxx`

正常会输出



注: 启动后发现跟踪策略无输出，那是因为今天模拟交易没有调仓或者接收到的调仓信号过期了，默认只处理120s内的信号，想要测试的可以用下面的命令：

```python
jq_follower.follow(user, '模拟交易url',
          trade_cmd_expire_seconds=100000000000, cmd_cache=False)
```

- trade_cmd_expire_seconds 默认处理多少秒内的信号

- cmd_cache 是否读取已经执行过的命令缓存，以防止重复执行

目录下产生的 cmd_cache.pk，是用来存储历史执行过的交易指令，防止在重启程序时重复执行交易过的指令，可以通过 `follower.follow(xxx, cmd_cache=False)` 来关闭。

##### ricequant

```
follower.follow(xq_user, run_id)
```
注：ricequant的run_id即PT列表中的ID。


## 跟踪雪球的组合

##### 1) 初始化跟踪的 trader

同上

##### 2) 初始化跟踪 雪球组合 的 follower

```
xq_follower = easytrader.follower('xq')
xq_follower.login(cookies='雪球 cookies，登陆后获取，获取方式见 https://smalltool.github.io/2016/08/02/cookie/')
```

##### 3) 连接 follower 和 trader

```
xq_follower.follow(xq_user, 'xq组合ID，类似ZH123456', total_assets=100000)
```


注: 雪球组合是以百分比调仓的， 所以需要额外设置组合对应的资金额度

* 这里可以设置 total_assets, 为当前组合的净值对应的总资金额度, 具体可以参考参数说明
* 或者设置 initial_assets, 这时候总资金额度为 initial_assets * 组合净值

* 雪球额外支持 adjust_sell 参数，决定是否根据用户的实际持仓数调整卖出股票数量，解决雪球根据百分比调仓时计算出的股数有偏差的问题。当卖出股票数大于实际持仓数时，调整为实际持仓数。目前仅在银河客户端测试通过。 当 users 为多个时，根据第一个 user 的持仓数决定


#### 3. 多用户跟踪多策略

```
follower.follow(users=[xq_user, yh_user], strategies=['组合1', '组合2'], total_assets=[10000, 10000])
```

#### 4. 其它与跟踪有关的问题

使用市价单跟踪模式，目前仅支持银河

```
follower.follow(***, entrust_prop='market')
```

调整下单间隔, 默认为0s。调大可防止卖出买入时卖出单没有及时成交导致的买入金额不足

```
follower.follow(***, send_interval=30) # 设置下单间隔为 30 s
```
设置买卖时的滑点

```
follower.follow(***, slippage=0.05) # 设置滑点为 5%
```

---

### Install (`docs/install.md`)

# 安装

### 同花顺客户端设置

需要对客户端按以下设置，不然会导致下单时价格出错以及客户端超时锁定

* 系统设置 > 界面设置: 界面不操作超时时间设为 0
* 系统设置 > 交易设置: 默认买入价格/买入数量/卖出价格/卖出数量 都设置为 空

同时客户端不能最小化也不能处于精简模式

### 云端部署建议

在云服务上部署时，使用自带的远程桌面会有问题，推荐使用 TightVNC

### 登陆时的验证码识别

券商如果登陆需要识别验证码的话需要安装 tesseract：

* `tesseract` : 非 `pytesseract`, 需要单独安装, [地址](https://github.com/tesseract-ocr/tesseract/wiki),保证在命令行下 `tesseract` 可用

或者你也可以手动登陆后在通过 `easytrader` 调用，此时 `easytrader` 在登陆过程中会直接识别到已登陆的窗口。

### 安装

```shell
pip install easytrader
```

### 升级

```shell
pip install easytrader -U
```

---

### Miniqmt (`docs/miniqmt.md`)

# miniqmt

miniqmt 是券商官方的低门槛 Python 量化交易接口，基于券商的讯投 QMT 服务。详情可以[进群](https://easytrader.readthedocs.io/zh-cn/master/#_2)交流。

## 安装 miniqmt 组件

miniqmt 功能依赖 `xtquant` 库，因为这个库比较大(100 MB+)，所以需要单独安装

```python
pip install easytrader[miniqmt]
``` 

## 引入

```python
import easytrader
```

## 初始化客户端

```python
user = easytrader.use('miniqmt')
```

## 连接 QMT 客户端

需要通过 `connect` 方法连接到 QMT 客户端。

**注意：登录 QMT 客户端时必须勾选极简模式/独立交易模式，否则无法连接**

```python
user.connect(
    miniqmt_path=r"D:\国金证券QMT交易端\userdata_mini",  # QMT 客户端下的 miniqmt 安装路径
    stock_account="你的资金账号",  # 资金账号
    trader_callback=None, # 默认使用 `easytrader.miniqmt.DefaultXtQuantTraderCallback`
)
```

**参数说明：**

- `miniqmt_path`: QMT 客户端下的 miniqmt 安装路径，例如 `r"D:\国金证券QMT交易端\userdata_mini"`
    - 注意：不建议安装在 C 盘。在 C 盘每次都需要用管理员权限运行客户端，才能正常连接
- `stock_account`: 资金账号
- `trader_callback`: 交易回调对象，默认使用 `easytrader.miniqmt.DefaultXtQuantTraderCallback`

## 交易相关

### 获取资金状况

```python
user.balance

# return
# qmt 官网文档 https://dict.thinktrader.net/nativeApi/xttrader.html?id=7zqjlm#%E8%B5%84%E4%BA%A7xtasset
[{
  'total_asset': 1000000.0,  # 总资产
  'market_value': 400000.0,  # 持仓市值
  'cash': 600000.0,  # 可用资金
  'frozen_cash': 0.0,  # 冻结资金
  'account_type': 2,  # 账户类型
  'account_id': '你的资金账号'  # 账户ID
}]
```

### 获取持仓

```python
user.position

# return
# qmt 官网文档 https://dict.thinktrader.net/nativeApi/xttrader.html?id=7zqjlm#%E6%8C%81%E4%BB%93xtposition
[{'security': '162411',
  'stock_code': '162411.SZ',
  'volume': 100,
  'can_use_volume': 100,
  'open_price': 0.618,
  'market_value': 63.8,
  'frozen_volume': 0,
  'on_road_volume': 0,
  'yesterday_volume': 100,
  'avg_price': 0.618,
  'direction': 48,
  'account_type': 2,
  'account_id': '1111111111'}]

```

### 限价买入

```python
user.buy('600036', price=35.5, amount=100)

# return
{'entrust_no': 123456}
```

**注意事项**

- 成功发送委托后的订单编号为大于 0 的正整数，如果为 -1 表示委托失败，失败具体原因请查看 `DefaultXtQuantTraderCallback.on_order_error` 回调
- 注：非交易时间下单可以拿到订单编号，但 `on_order_error` 回调会报错：
  ```
  下单失败回调: order_id=10231, error_id=-61, error_msg=限价买入 [SZ162411] [COUNTER] [12313][当前时间不允许此类证券交易]
  ```

### 限价卖出

```python
user.sell('600036', price=36.0, amount=100)

# return
{'entrust_no': 123456}
```

### 市价买入

```python
user.market_buy('600036', amount=100, ttype='对手方最优价格委托')

# return
{'entrust_no': 123456}
```

**市价委托类型（ttype）可选值**:

深市可选:

- 对手方最优价格委托（默认）
- 本方最优价格委托
- 即时成交剩余撤销委托
- 最优五档即时成交剩余撤销
- 全额成交或撤销委托

沪市可选:

- 对手方最优价格委托（默认）
- 最优五档即时成交剩余撤销
- 最优五档即时成交剩转限价
- 本方最优价格委托

### 市价卖出

```python
user.market_sell('600036', amount=100, ttype='对手方最优价格委托')

# return
{'entrust_no': 123456}
```

### 撤单

```python
user.cancel_entrust(123456)  # 传入之前买入或卖出时返回的订单编号

# return
{'success': True, 'message': 'success'} # 成功
{'success': False, 'message': 'failed'} # 失败
```

### 查询当日委托

```python
user.today_entrusts

# return
# qmt 官网文档 https://dict.thinktrader.net/nativeApi/xttrader.html?id=7zqjlm#%E5%A7%94%E6%89%98xtorder
[{'security': '162411',
  'stock_code': '162411.SZ',
  'order_id': 3456,
  'order_sysid': '1111',
  'order_time': 1634278451,
  'order_type': 23,
  'order_type_name': '买入', # ['买入', '卖出']
  'order_volume': 100,
  'price_type': 50,
  'price_type_name': '限价',
  'price': 0.62,
  'traded_volume': 100,
  'traded_price': 0.613,
  'order_status': 56,
  'order_status_name': '已成', # ['已报', '已成', '部成', '已撤', '部撤']
  'status_msg': '',
  'offset_flag': 48,
  'offset_flag_name': '买入', # ['买入', '卖出']
  'strategy_name': '',
  'order_remark': '',
  'direction': 48,
  'direction_name': '多', # ['多', '空']
  'account_type': 2,
  'account_id': '1111111111'}]
```

### 查询当日成交

```python
user.today_trades

# return
# qmt 官网文档 https://dict.thinktrader.net/nativeApi/xttrader.html?id=7zqjlm#%E6%88%90%E4%BA%A4xttrade
[{'security': '162411',
  'stock_code': '162411.SZ',
  'traded_id': '0303222200422222',
  'traded_time': 1634278451,
  'traded_price': 0.613,
  'traded_volume': 100,
  'traded_amount': 61.3,
  'order_id': 1111,
  'order_type': 23,
  'order_type_name': '买入',
  'offset_flag': 48,
  'offset_flag_name': '买入',
  'account_id': '1111111111',
  'account_type': 2,
  'order_sysid': '1111',
  'strategy_name': '',
  'order_remark': ''}]
```


## 进阶功能

### 获取原始交易对象

通过获取原始对象，可以直接调用 miniqmt 的接口进行更多高级操作，具体请参考 [miniqmt 官方文档](https://dict.thinktrader.net/nativeApi/xttrader.html)

```python
# 获取 XtQuantTrader 对象
trader = user.trader

# 获取 StockAccount 对象
account = user.account
```


### 2. 交易回调处理

MiniqmtTrader 默认使用 `DefaultXtQuantTraderCallback` 类处理交易回调，但您可以通过继承 `XtQuantTraderCallback` 类来创建自定义回调处理：

```python
from xtquant.xttrader import XtQuantTraderCallback

class MyTraderCallback(XtQuantTraderCallback):
    def on_disconnected(self):
        print("连接断开")

    def on_account_status(self, status):
        print(f"账户状态: {status.account_id}, 状态: {status.status}")

    def on_stock_order(self, order):
        print(f"委托回调: {order.stock_code}, 状态: {order.order_status}")

    def on_stock_trade(self, trade):
        print(f"成交回调: {trade.stock_code}, 价格: {trade.traded_price}")

    def on_order_error(self, order_error):
        print(f"下单失败: {order_error.order_id}, 错误: {order_error.error_msg}")

    def on_cancel_error(self, cancel_error):
        print(f"撤单失败: {cancel_error.order_id}, 错误: {cancel_error.error_msg}")

# 连接时使用自定义回调
user.connect(
    miniqmt_path=r"D:\国金证券QMT交易端\userdata_mini",
    stock_account="你的资金账号",
    trader_callback=MyTraderCallback()
)
```

---

### Remote (`docs/remote.md`)

# 远端服务模式

远端服务模式是交易服务端和量化策略端分离的模式。

**交易服务端**通常是有固定`IP`地址的云服务器，该服务器上运行着`easytrader`交易服务。而**量化策略端**可能是`JoinQuant、RiceQuant、Vn.Py`，物理上与交易服务端不在同一台电脑上。交易服务端被动或主动获取交易信号，并驱动**交易软件**（交易软件包括运行在同一服务器上的下单软件，比如同花顺`xiadan.exe`，或者运行在另一台服务器上的雪球`xq`）。


## 交易服务端——启动服务

```python
from easytrader import server

server.run(port=1430) # 默认端口为 1430
```

## 量化策略端——调用服务

```python
from easytrader import remoteclient

user = remoteclient.use('使用客户端类型，可选 yh_client, ht_client, ths, xq等', host='服务器ip', port='服务器端口，默认为1430')

user.buy(......)

user.sell(......)
```

---

### Usage (`docs/usage.md`)

# 使用

## 引入

```python
import easytrader
```

## 设置同花顺客户端类型

**通用同花顺客户端**

```python
user = easytrader.use('universal_client') 
```

注: 通用同花顺客户端是指同花顺官网提供的客户端软件内的下单程序，内含对多个券商的交易支持，适用于券商不直接提供同花顺客户端时的后备方案。

**其他券商专用同花顺客户端**

```python
user = easytrader.use('ths')
```

注: 其他券商专用同花顺客户端是指对应券商官网提供的基于同花顺修改的软件版本，类似银河的双子星(同花顺版本)，国金证券网上交易独立下单程序（核新PC版）等。


**雪球组合**

```python
user = easytrader.use('xq')
```

**国金客户端**

```python
user = easytrader.use('gj_client') 
```

**海通客户端**

```python
user = easytrader.use('htzq_client')
```

**华泰客户端**

```python
user = easytrader.use('ht_client')
```


## 启动并连接客户端

### （一）其他券商专用同花顺客户端

其他券商专用同花顺客户端不支持自动登录，需要先手动登录。

请手动打开并登录客户端后，运用connect函数连接客户端。

```python
user.connect(r'客户端xiadan.exe路径') # 类似 r'C:\htzqzyb2\xiadan.exe'
```

### （二）通用同花顺客户端

需要先手动登录一次：添加券商，填入账户号、密码、验证码，勾选“保存密码”

第一次登录后，上述信息被缓存，可以调用prepare函数自动登录（仅需账户号、客户端路径，密码随意输入）。

### （三）其它

非同花顺的客户端，可以调用prepare函数自动登录。

调用prepare时所需的参数，可以通过`函数参数` 或 `配置文件` 赋予。

**1. 函数参数(推荐)**

```
user.prepare(user='用户名', password='雪球、银河客户端为明文密码', comm_password='华泰通讯密码，其他券商不用')
```

注: 雪球比较特殊，见下列配置文件格式

**2. 配置文件**

```python
user.prepare('/path/to/your/yh_client.json')  # 配置文件路径
```

注: 配置文件需自己用编辑器编辑生成, **请勿使用记事本**, 推荐使用 [notepad++](https://notepad-plus-plus.org/zh/) 或者 [sublime text](http://www.sublimetext.com/) 。

**配置文件格式如下：**

银河/国金客户端

```
{
  "user": "用户名",
  "password": "明文密码"
}

```

华泰客户端

```
{
   "user": "华泰用户名",
   "password": "华泰明文密码",
   "comm_password": "华泰通讯密码"
}

```

雪球

```
{
  "cookies": "雪球 cookies，登陆后获取，获取方式见 https://smalltool.github.io/2016/08/02/cookie/",
  "portfolio_code": "组合代码(例:ZH818559)",
  "portfolio_market": "交易市场(例:us 或者 cn 或者 hk)"
}
```

## 交易相关

有些客户端无法通过默认方法输入文本，可以通过开启 type_keys 的方法绕过，开启方式

```python
user.enable_type_keys_for_editor()
```

###  获取资金状况

```python
user.balance

# return
[{'参考市值': 21642.0,
  '可用资金': 28494.21,
  '币种': '0',
  '总资产': 50136.21,
  '股份参考盈亏': -90.21,
  '资金余额': 28494.21,
  '资金帐号': 'xxx'}]
```

### 获取持仓

```python
user.position

# return
[{'买入冻结': 0,
  '交易市场': '沪A',
  '卖出冻结': '0',
  '参考市价': 4.71,
  '参考市值': 10362.0,
  '参考成本价': 4.672,
  '参考盈亏': 82.79,
  '当前持仓': 2200,
  '盈亏比例(%)': '0.81%',
  '股东代码': 'xxx',
  '股份余额': 2200,
  '股份可用': 2200,
  '证券代码': '601398',
  '证券名称': '工商银行'}]
```

### 买入

```python
user.buy('162411', price=0.55, amount=100)

# return
{'entrust_no': 'xxxxxxxx'}
```

注: 系统可以配置是否返回成交回报。如果没配的话默认返回 `{"message": "success"}`

### 卖出

```python
user.sell('162411', price=0.55, amount=100)

# return
{'entrust_no': 'xxxxxxxx'}
```


### 撤单

```python
user.cancel_entrust('buy/sell 获取的 entrust_no')

# return
{'message': 'success'}
```

### 查询当日成交

```python
user.today_trades

# return
[{'买卖标志': '买入',
  '交易市场': '深A',
  '委托序号': '12345',
  '成交价格': 0.626,
  '成交数量': 100,
  '成交日期': '20170313',
  '成交时间': '09:50:30',
  '成交金额': 62.60,
  '股东代码': 'xxx',
  '证券代码': '162411',
  '证券名称': '华宝油气'}]
```

### 查询当日委托

```python
user.today_entrusts

# return
[{'买卖标志': '买入',
  '交易市场': '深A',
  '委托价格': 0.627,
  '委托序号': '111111',
  '委托数量': 100,
  '委托日期': '20170313',
  '委托时间': '09:50:30',
  '成交数量': 100,
  '撤单数量': 0,
  '状态说明': '已成',
  '股东代码': 'xxxxx',
  '证券代码': '162411',
  '证券名称': '华宝油气'},
 {'买卖标志': '买入',
  '交易市场': '深A',
  '委托价格': 0.6,
  '委托序号': '1111',
  '委托数量': 100,
  '委托日期': '20170313',
  '委托时间': '09:40:30',
  '成交数量': 0,
  '撤单数量': 100,
  '状态说明': '已撤',
  '股东代码': 'xxx',
  '证券代码': '162411',
  '证券名称': '华宝油气'}]
```


### 查询今日可申购新股

```python
from easytrader.utils.stock import get_today_ipo_data
get_today_ipo_data()

# return
[{'stock_code': '股票代码',
  'stock_name': '股票名称',
  'price': 发行价,
  'apply_code': '申购代码'}]
```

### 一键打新

```python
user.auto_ipo()
```

### 刷新数据

```python
user.refresh()
```

### 雪球组合比例调仓 

```python
user.adjust_weight('股票代码', 目标比例)
```

例如 `user.adjust_weight('000001', 10)`是将平安银行在组合中的持仓比例调整到10%。

## 退出客户端软件

```python
user.exit()
```

## 常见问题

### 某些同花顺客户端不允许拷贝 `Grid` 数据

现在默认获取 `Grid` 数据的策略是通过剪切板拷贝，有些券商不允许这种方式，导致无法获取持仓等数据。为解决此问题，额外实现了一种通过将 `Grid` 数据存为文件再读取的策略，
使用方式如下:

```python
from easytrader import grid_strategies

user.grid_strategy = grid_strategies.Xls
```

### 通过工具栏刷新按钮刷新数据

当前的刷新数据方式是通过切换菜单栏实现，通用但是比较缓慢，可以选择通过点击工具栏的刷新按钮来刷新

```python
from easytrader import refresh_strategies

# refresh_btn_index 指的是刷新按钮在工具栏的排序，默认为第四个，根据客户端实际情况调整
user.refresh_strategy = refresh_strategies.Toolbar(refresh_btn_index=4)
```

### 无法保存对应的 xls 文件

有些系统默认的临时文件目录过长，使用 xls 策略时无法正常保存，可通过如下方式修改为自定义目录

```
user.grid_strategy_instance.tmp_folder = 'C:\\custom_folder'
```

### 如何关闭 debug 日志的输出

```python
user = easytrader.use('yh', debug=False)

```


# 编辑配置文件，运行后出现 `json` 解码报错


出现如下错误

```python
raise JSONDecodeError("Expecting value", s, err.value) from None

JSONDecodeError: Expecting value
```

请勿使用 `记事本` 编辑账户的 `json` 配置文件，推荐使用 [notepad++](https://notepad-plus-plus.org/zh/) 或者 [sublime text](http://www.sublimetext.com/)

---

### Xueqiu (`docs/xueqiu.md`)

# 雪球组合模拟交易

因为雪球组合是按比例调仓的,所以模拟成券商实盘接口会有一些要注意的问题

* 接口基本与其他券商接口调用参数返回一致
* 委托单不支持挂高挂低(开盘时间都是直接市价成交的)
* 初始资金是按组合净值 1:1000000 换算来的, 可以通过 `easytrader.use('xq', initial_assets=初始资金值)` 来调整
* 委托单的委托价格和委托数量目前换算回来都是按1手拆的(雪球是按比例调仓的)
* 持仓价格和持仓数量问题同上, 但持股市值是对的.
* 一些不合理的操作会直接抛TradeError,注意看错误信息
          
----------------
20160909 新增函数adjust_weight，用于雪球组合比例调仓
             
adjust_weight函数包含两个参数，stock_code 指定调仓股票代码，weight 指定调仓比例

---

--- METRICS ---
- Files Extracted: 8
- Estimated Token Budget: ~3950 tokens
- Canonical Reference: https://codewiki.google/github.com/shidenggui/easytrader
