owned this note
owned this note
Published
Linked with GitHub
# fin-pyコードリーディング会#2 :bookmark_tabs:
## 概要
### fin-pyについて
https://github.com/fin-py/guideline
### Slack
https://docs.google.com/forms/d/e/1FAIpQLSd9oVlrCMHEuD3PN0x3QcMgeQGy6Sj90d6uP1CQXQnArX9YqQ/viewform
### 参加方法
終了
### タイムテーブル :clock1:
1. 発表者枠が話す: 各自15min
1. クロージング(コードを読んだ感想など): 10min
## コードリーディング
### 読むコード
[pybotters](https://github.com/MtkN1/pybotters)
発表者のかたは、コードを読んだ際のメモ書きなどを下記に記載してください
ほかの形式でまとめたかたは要点(もし可能であれば資料のリンク)を記載してください
## 自己紹介・コメント・メモ
- にぎやかし枠、発表者枠のかたは下記に自己紹介を記入してください
- pybottersを使っているかなどの情報があると嬉しいです
- イベントのフィードバックや感想などを追記していただけると助かります
### どりらん
- Twitter: [patraqushe](https://twitter.com/patraqushe)
- オプションをトレードしてます
- 感想
- aiohttpの知識が必要だと思った
---
### しんせいたろう
- Twitter: [shinseitaro](https://twitter.com/shinseitaro)
- 米国株とか売ったり買ったりしてるひと。pythonは長く書いてるわりに分かってないひと。クラスとかいまだ苦手
- 感想:
- おもしろかったです。
- まちゅけんさんの、「こういう理由でこう書きました」的なところをシェアして頂ける方法があるといいですね。
---
### まちゅけん
- Twitter[MtkN1XBt](https://twitter.com/MtkN1XBt)
- Discord https://discord.com/invite/CxuWSX9U69
- お世話になってます、開発者のまちゅけんです。ご質問はお気軽にどうぞ!
- 感想
- 細かい箇所の実装意図は何かしらの形で残しておきたい
---
### やろころ
- 最近、育児ばかりしてました
- オプショントレードはじめました
- 感想
- ログの出し方、テストの仕方など、ノウハウ?的な内容を入れてもらえたのは良かったです
---
### saru999
- FXのシストレから始めて現在国内株をトレードしています。pythonシストレに興味があるもののまだ動かせていません。。
- 感想
- 製作者はもちろん、質問者もレベルが高くて、すごく楽しかったです。開催していただきありがとうございました。
---
### やまもと
- 趣味でたまにpython書いてます。最近、暗号資産シストレに興味あります。
- コードもっと読んでおけば、もっと楽しめたと思いました。今度は発表してみたいです。
---
### ちょう
- データ分析・機械学習をやっています
- https://twitter.com/sea85419
---
### 椎名🐍
- pythonでbot作成途中で、まだ1号機も動かせていません。
- 現在はLGB使って分析したりしてます。
- https://twitter.com/Lazy_wavelet
---
### takei
- 仮想通貨、株などpythonでトレードしてます。
- pybottersはまだ使っていませんが、まちゅけんさんのbybitのライブラリはお世話になってます。ありがとうございます。
- 感想:
- いつも課題がいっぱい見つかって勉強になります。
- 複数の取引所に対応したひとつのプログラムを作るのは大変そうだと思いました。まちゅけんさんほんとにありがとうございます。m(_ _)m
---
## 発表者
### しんせいたろう
```python=
# request.py
:
from .auth import Auth, Hosts
:
```
- `Hosts` は、各取引所の url と Item(取引所名, 認証用メソッド)
- `Auth` は 各取引所の認証
```python
# request.py
import aiohttp
:
:
class ClientRequest(aiohttp.ClientRequest):
def __init__(self, *args, **kwargs) -> None:
method: str = args[0]
url: URL = args[1]
:
:
```
- `aiohttp.ClientRequest` のドキュメントを探してもなかったので、コードを見に行った
- たぶんこれですよね?
- https://github.com/aio-libs/aiohttp/blob/09ac1cbb4c07b4b3e1872374808512c9bc52f2ab/aiohttp/client_reqrep.py#L157
- `ClientRequest` は、`client.py`の `Client`クラスの `request_class` に渡されてる
```python
:
class Client:
_session: aiohttp.ClientSession
_base_url: str
def __init__(
self,
apis: Union[Dict[str, List[str]], str] = {},
base_url: str = '',
**kwargs: Any,
) -> None:
self._session = aiohttp.ClientSession(
request_class=ClientRequest, # ここで呼ばれていて
:
:
```
- この Client クラスは、 pybotters async client として、ユーザが実行ファイルでこんな感じで使うから
```python
# 自分の python file
async with pybotters.Client(
apis={"ftx": ["apikey", "apisecret"]},
base_url="https://ftx.com/api"
) as pybclient:
```
- なので、ここに api と base_url がはいるんですね...
```python
# client.py
def __init__(
self,
apis: Union[Dict[str, List[str]], str] = {}, # ここ
base_url: str = '',# ここ
```
---
### 発表者2
- とりあえずpylintでパッケージ図とクラス図を可視化
- [pylintでクラス図を自動生成する](https://qiita.com/kenichi-hamaguchi/items/c0b947ed15725bfdfb5a)
```
pyreverse -o png -p pybotters $HOME/xxx/.pybotvenv/lib/python3.8/site-packages/pybotters/
client.py,request.pyあたりから読み始めるのがよい?
```
![](https://i.imgur.com/VkIf3nD.png)
![](https://i.imgur.com/R2OgFn5.png)
- [型ヒント](https://qiita.com/icoxfog417/items/c17eb042f4735b7924a3)
- コメントの延長
```
help(greeting)
```
- [特殊メソッド](https://qiita.com/y518gaku/items/07961c61f5efef13cccc)
- クラスの継承とオーバーライドと[super](https://qiita.com/taku_hito/items/c9c80ff453d0c4fad239)
- [__init__.pyについて](https://qiita.com/msi/items/d91ea3900373ff8b09d7)
- from pybotters.client import Clientせずにpybotters.ClientでClientが呼べるのは、__init__.pyにfrom .client import Clientがあるから
- from pybotter import * と__all__ https://qiita.com/suzuki-kei/items/8fea67655abf216a5013
- [デコレータ](https://qiita.com/_rdtr/items/d3bc1a8d4b7eb375c368):すでにある関数に処理の追加や変更を行う為の機能
- パッケージ開発について知りたい
- パッケージ構成はどのように考えていけばいいのか
- テストの仕方
---
### ちょう
- ざっくり`async def`
- https://docs.python.org/3/library/asyncio-task.html
- ざっくりいうとこれで定義すると関数が非同期になる
- pythonは基本順番に処理をするので、前の処理が終わらないと次の処理は実行されない
- イメージ:通常の書き方はご飯が炊き上がるまでにおかずを作れないが、async defを作るとご飯炊いてから同時におかずを作れる
- `await`をつけるとその部分の実行を待ってくれる
- asyncioでイロイロ便利な関数がある
- https://docs.python.org/3/library/asyncio.html
- 感想:昔は業務でmultiprocessingを使ったことがあって、嫌な思いをしました。もっと早く知りたかった
- https://docs.python.org/3/library/multiprocessing.html
- とりあえず処理をフォローしてみた(`client.py`)
- 使い方:https://github.com/MtkN1/pybotters#single-exchange
- リクエストは`aiohttp.ClientSession`を通して行っている(`Client._session`)
- リクエストメソッドのwrappingは`Client._request`
- request, get, post, put, deleteなどはそれを呼び出している
- ちょっと頑張った部分:認証あたり
このあたりで認証情報をセッションの属性に入れている
```
self._session.__dict__['_apis'] = self._encode_apis(apis)
```
(`api`は認証キー等を入れている
https://github.com/MtkN1/pybotters#single-exchange)
Q.リクエストをする時にこれを使うはずだが、どこで呼び出している?
A.`request.py`で使っている。詳細の仕組みはaiohttpのソースコードを読みました
```
self._session = aiohttp.ClientSession(
request_class=ClientRequest,
ws_response_class=ClientWebSocketResponse,
**kwargs,
)
```
request_classを渡すと、aiohttp.ClientSession._request_classに格納して
https://github.com/aio-libs/aiohttp/blob/6ec33c5d841c8e845c27ebdd9384bbf72651cbb8/aiohttp/client.py#L270
リクエストをするときにaiohttp.ClientSession._request_classを使っている。そのさい、呼び出しもとのインスタンスをsessionに渡している
https://github.com/aio-libs/aiohttp/blob/6ec33c5d841c8e845c27ebdd9384bbf72651cbb8/aiohttp/client.py#L469
pybottersのrequest.pyの使い方とつながった!
https://github.com/MtkN1/pybotters/blob/9533e359c63ccc25cf18c69b4aa988b57accd5cd/pybotters/request.py#L28
疑問:aiohttpのドキュメントではrequest_class周りの説明は見つからなかった。どういう経緯でこの実装になったのか。
---
### 発表者4
---
### どりらん
`ClientRequest` クラスの目的
- `aiohttp.ClientRequest.update_auth` の置き換え
- `pybotters.auth.Auth` で各取引所に対応した引数に変更
- `aiohttp.ClientRequest.send` メソッド実行後に属性を追加
- `_auth`
- どこで使われているか見つからなかった
- `_raw_session`
- `models.binance.BinanceDataStor.initialize` で使われる?
- 属性名が `_session` のままだと不都合がある?
- ↑2つがよくわかっていない
:::info
```python=
class ClientRequest(aiohttp.ClientRequest):
def __init__(self, *args, **kwargs) -> None:
method: str = args[0]
url: URL = args[1]
```
必要な引数は仮引数に入れたほうがわかりやすいと思いました
また、`url=xxx, method=xxx` のような渡され方をした場合にも対応できそうです
```python=
class ClientRequest(aiohttp.ClientRequest):
def __init__(self, method: str, url: URL, *args, **kwargs) -> None:
```
:::
:::info
`__dict__` にする理由がよくわかりませんでした
```python=
self.__dict__['_auth'] = kwargs['auth']
```
```python=
self._auth = kwargs['auth']
```
:::
#### yarl
- 公式ドキュメント: https://yarl.readthedocs.io/en/latest/
- URLをパースするライブラリ
URLの構造
```
[scheme:]//[user[:password]@]host[:port][/path][?query][#fragment]
```
```
http://user:pass@example.com:8042/over/there?name=ferret#nose
\__/ \__/ \__/ \_________/ \__/\_________/ \_________/ \__/
| | | | | | | |
scheme user password host port path query fragment
```
#### multidict
- 公式ドキュメント: https://multidict.readthedocs.io/en/stable/
- URLヘッダやクエリ引数を操作するためのライブラリ
- コードはaiohttpから抽出
##### MultiDictProxyクラス
- MultiDictの動的なビューを提供
##### MultiDictクラス
- ミュータブルなmultidictインスタンスを生成
- dictと同じパラメータを渡せる
URLクラスの `query` メソッドでは上記のquery部分を `multidict.MultiDictProxy` 型で返す
> https://yarl.readthedocs.io/en/latest/api.html#yarl.URL.query
```python=
url = URL("https://ftx.com/api/markets/BTC-0628/candles?resolution=300")
q = MultiDict(url.query)
print(q)
```
```
<MultiDict('resolution': '300')>
```
`with_query` メソッドは渡されたパラメータからURLを置き換える
> https://yarl.readthedocs.io/en/latest/api.html#yarl.URL.with_query
```python=
url2 = url.with_query({"start_time": 1559881511, "end_time": 1559881711})
print(url2)
```
```
https://ftx.com/api/markets/BTC-0628/candles?start_time=1559881511&end_time=1559881711
```
`extend` メソッドではキーと値を追加(更新)する
> https://multidict.readthedocs.io/en/stable/multidict.html#multidict.MultiDict.extend
```python=
q.extend(url2.query)
print(q)
```
```
<MultiDict('resolution': '300', 'start_time': '1559881511', 'end_time': '1559881711', 'start_time': '1559881511', 'end_time': '1559881711', 'start_time': '1559881511', 'end_time': '1559881711', 'start_time': '1559881511', 'end_time': '1559881711')>
```
`with_query` メソッドではすべてのクエリパラメータを置き換える
> https://yarl.readthedocs.io/en/latest/api.html#yarl.URL.with_query
```python=
print(url.with_query(q))
```
```
https://ftx.com/api/markets/BTC-0628/candles?resolution=300&start_time=1559881511&end_time=1559881711&start_time=1559881511&end_time=1559881711
```
:::info
`if kwargs['params']:` ブロックの処理は継承元の `aiohttp.ClientRequest` と同じ処理をしているようなので、引数に `auth` がないときは `params` をそのまま `super().__init__` に渡してもよさそうに見えました
https://github.com/aio-libs/aiohttp/blob/09ac1cbb4c07b4b3e1872374808512c9bc52f2ab/aiohttp/client_reqrep.py#L157
:::
---
### やろころ
#### 発表内容
##### 「どのように読んだか」
使い方
https://github.com/MtkN1/pybotters#-usage
```
async def main():
async with pybotters.Client(apis=apis, base_url='https://api.bybit.com') as client:
# REST API
resp = await client.get('/v2/private/position/list', params={'symbol': 'BTCUSD'})
data = await resp.json()
print(data)
# WebSocket API (with defautl print handler)
ws = await client.ws_connect(
url='wss://stream.bybit.com/realtime',
send_json={'op': 'subscribe', 'args': ['trade.BTCUSD', 'order', 'position']},
)
await ws # Ctrl+C to break
```
読む対象
https://github.com/MtkN1/pybotters/blob/main/pybotters/request.py
使い方2
https://github.com/MtkN1/pybotters/wiki#rest-api
```
async def main():
async with pybotters.Client(apis=apis) as client:
r = await client.request('GET', 'https://...')
r = await client.get('https://...', params={'foo': 'bar'})
r = await client.post('https://...', data={'foo': 'bar'})
r = await client.put('https://...', data={'foo': 'bar'})
r = await client.delete('https://...', data={'foo': 'bar'})
```
利用箇所
https://github.com/MtkN1/pybotters/search?q=ClientRequest
##### 対象を読んでいく
###### 可変長引数
https://note.nkmk.me/python-args-kwargs-usage/
- *args: 複数の引数をタプルとして受け取る
- **kwargs: 複数のキーワード引数を辞書として受け取る
###### URL
https://yarl.readthedocs.io/en/latest/api.html#yarl-api
URL.with_query(query)
URL.with_query(**kwargs)
Return a new URL with query part replaced.
```
>>> URL('http://example.com/path?a=b').with_query('c=d')
URL('http://example.com/path?c=d')
>>> URL('http://example.com/path?a=b').with_query({'c': [1, 2]})
URL('http://example.com/path?c=1&c=2')
```
URL.query
A multidict.MultiDictProxy representing parsed query parameters in decoded representation. Empty value if URL has no query part.
```
URL('http://example.com/path?a1=a&a2=b').query
<MultiDictProxy('a1': 'a', 'a2': 'b')>
URL('http://example.com/path').query
<MultiDictProxy()>
```
###### is
https://qiita.com/Takuya_Fujitani/items/e93b051e44e770264840
###### 「コードでわからないところ、きいてみたいところ」
- import
- send()の呼び出し # aiohttpから呼び出される?
###### 「新たな知見を得たところ」
- 可変長引数のところ
- Python is と == の違い
###### リンク
- https://docs.aiohttp.org/en/stable/
- https://multidict.readthedocs.io/en/stable/
- https://yarl.readthedocs.io/en/latest/index.html
にぎやかし枠
### Toshi
- Pythonに出会ってから3年以上経ちます。まだまだ初学者です。finPyのもくもく会、作業会、勉強会では皆さんに教えてもらえて、楽しく参加させて頂いています。本日も宜しくお願いします。
- クラスの継承と合成が難しかったので、自分で勉強してみたいと思いました。
- poetryも勉強します。