File size: 19,102 Bytes
7c86ba8
 
 
b3b49c3
7c86ba8
 
 
 
 
 
 
 
 
 
 
 
b3b49c3
 
7c86ba8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23a7434
 
 
 
 
 
 
 
 
7c86ba8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3b49c3
23a7434
 
b3b49c3
 
 
 
 
 
 
 
 
 
 
 
7c86ba8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23a7434
 
 
 
 
 
7c86ba8
23a7434
 
 
 
 
 
 
7c86ba8
 
 
 
 
 
 
 
 
23a7434
7c86ba8
b3b49c3
7c86ba8
 
 
b3b49c3
 
23a7434
 
 
 
 
 
b3b49c3
23a7434
7c86ba8
23a7434
 
 
7c86ba8
 
 
 
 
 
 
 
 
 
b3b49c3
7c86ba8
 
 
 
b3b49c3
7c86ba8
 
 
23a7434
 
b3b49c3
23a7434
b3b49c3
7c86ba8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3b49c3
7c86ba8
 
23a7434
7c86ba8
 
 
 
 
 
 
 
 
 
 
b3b49c3
7c86ba8
23a7434
7c86ba8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3b49c3
7c86ba8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23a7434
7c86ba8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
import functools
from pathlib import Path

from api import Goods, Inventory, test_tokens
import streamlit as st
from st_aggrid import AgGrid
from st_aggrid import DataReturnMode, GridUpdateMode
from st_aggrid.shared import JsCode
from st_aggrid.grid_options_builder import GridOptionsBuilder
import pandas as pd
from pyecharts.charts import Bar, Pie
from pyecharts import options as opts
from pyecharts.globals import ThemeType
import streamlit_echarts
import json



def delete_goods(inventory, index):
    for i in index:
        inventory.delete(i['库存编号'])


def sell_goods(inventory, index):
    for i in index:
        try:
            inventory()[i['库存编号']].sell(eval(i['卖出价格']))
        except:
            st.error("卖出价格输入错误,请检查输入")

def lease_goods(inventory, index):
    for i in index:
        inventory()[i['库存编号']].lease()


def back_goods(inventory, index):
    for i in index:
        inventory()[i['库存编号']].back()



def open_inventory(path):
    if type(path) == str:
        st.session_state.inventory=Inventory(path)
        st.session_state.inventory.save()
    else:
        with st.spinner("加载库存中..."):
            st.session_state.inventory = Inventory(path.name)
            st.success("库存已打开 ✅")
        with open(st.session_state.inventory.path,'wb+') as file:
            file.write(path.getvalue())
    with st.spinner("更新饰品信息..."):
        progress_bar = st.progress(0)
        if len(st.session_state.inventory())<=0:
            rate=1
        else:
            rate = 1 / len(st.session_state.inventory())
        for p, i in enumerate(st.session_state.inventory):
            progress_bar.progress(rate * p)
            st.session_state.inventory()[i].refresh()
        progress_bar.empty()


def save_inventory(path):
    with st.spinner("保存库存中..."):
        st.session_state.inventory.save()
        st.success("库存保存成功 ✅")
    with st.sidebar:
        with open(st.session_state.inventory.path, 'rb') as f:
            st.download_button('下载到本地', f, file_name=st.session_state.inventory.path)
def update_token(token):
    if not test_tokens(token):
        st.error("Token无效,请重新登陆悠悠,按F12查找token,格式为 Bearer xxx")
        return
    USER_TOKEN=token
    if 'inventory' in st.session_state:
        for good in st.session_state.inventory():
                st.session_state.inventory()[good].token=USER_TOKEN
    st.success("Token更新成功,请及时保存!!✅")
    
    
    
cellsytle_jscode = JsCode(
    """
function (params) {
        if (params.value < 0) {
            return {
                'color': 'white',
                'backgroundColor': 'forestgreen'
            }
        } else {
            return {
                'color': 'white',
                'backgroundColor': 'crimson'
            }
        } 
    };
    """
)

def import_from_file(path,token):
    if not "inventory" in st.session_state:
        st.error("请新建仓库在导入数据")
        return
    if not test_tokens(token):
        st.error("悠悠token不正确")
        return
    try:
        if path.name.endswith('.csv'):
            data=pd.read_csv(path.getvalue())
        elif path.name.endswith('.xlsx'):
            data=pd.read_excel(path.getvalue())
        else:
             st.error('%s 文件类型不支持'%path.name)
             return
        for i in range(data.shape[0]):
            tmp = Goods(str(data.iloc[i]['Buff id']), int(data.iloc[i]['购入花费(元)']),token=token)
            tmp.refresh()
            st.session_state.inventory.add(tmp)
            st.success(tmp.name + "已添加 ✅")
    except:
        st.error('检查%s中是否包含 <Buff id> <购入花费(元)>字段'%path)

def main() -> None:
    global USER_TOKEN
    st.header("CSGO 饰品投资追踪 :moneybag: :dollar: :bar_chart:")
    st.caption("Made by Shevon & Lishuai, maintained by whatcoldwind")
    st.text("请在左侧打开库存文件")
    with st.sidebar:
        st.subheader("选择库存")
        path = st.file_uploader("上传本地库存文件")
        if path:
            launch = st.button('打开库存', on_click=open_inventory, args=(path,))
        new_name=st.text_input("输入新建库存的名称",value="xxxxx")
        if new_name:
            new_one = st.button('新建库存', on_click=open_inventory, args=(new_name+'.pkl',))
        save = st.button('保存库存更改', on_click=save_inventory, args=(path,))
        token_value = st.text_input("token值", value=USER_TOKEN)
        token = st.button('更新悠悠有品token', on_click=update_token, args=(token_value,))
        
        if 'inventory' in st.session_state:
            import_file_path = st.file_uploader("上传导出表格文件并导入到当前仓库 *.csv *.xlsx")
            if import_file_path:
                import_bt = st.button('导入', on_click=import_from_file, args=(import_file_path,USER_TOKEN))
            st.caption('目前已启动库存 ' + st.session_state.inventory.path)
            st.subheader("添加饰品")
            form_track = st.form(key="track")
            with form_track:
                code = st.text_input("请输入饰品buff代码")
                cost = eval(st.text_input("请输入购买价格,0表示仅观望", "0"))
                submitted = st.form_submit_button(label="添加")
            if submitted:
                with st.spinner("加载饰品信息..."):
                    try:
                        tmp = Goods(code, cost,token=USER_TOKEN)
                        tmp.refresh()
                        st.session_state.inventory.add(tmp)
                        st.success(tmp.name + "已添加 ✅")
                    except:
                        st.error("饰品信息加载失败,请检查代码和token是否正确")

    if 'inventory' in st.session_state:
        for good in st.session_state.inventory():
            validation=test_tokens(st.session_state.inventory()[good].token)
            if validation :
                USER_TOKEN=st.session_state.inventory()[good].token
                st.success("✅ 你当前token有效:%s"%USER_TOKEN)
                break
        st.subheader("投资信息")
        if len(st.session_state.inventory()) > 0:
            col = st.columns(4)
            col2 = st.columns(4)
            col3 = st.columns(4)
            col[0].metric(
                "总投资额", value=f"{st.session_state.inventory.total_cost():.2f} 元",
                help="购买饰品总花费"
            )
            col[1].metric("追踪总量", value=f"{len(st.session_state.inventory())} 件",help="加入库存文件的饰品数量")
            col[2].metric(
                "库存价值(Buff计,含租出)",
                value=f"{st.session_state.inventory.calc_price():.2f} 元",
                help="库存饰品和已租出饰品总价值"
            )
            col[3].metric(
                "总套现", value=f"{st.session_state.inventory.sell_price():.2f} 元",
                help="卖出饰品总收入"
            )
            earn = (
                st.session_state.inventory.calc_price()
                + st.session_state.inventory.sell_price()
                - st.session_state.inventory.total_cost()
            )
            col2[0].metric("盈利(Buff计)", value=f"{earn:.2f} 元",
                           help="总套现 + 库存价值 - 总投资额")
            col2[1].metric(
                "总收益率",
                value=f"{earn/st.session_state.inventory.total_cost()*100:.2f} %",
                help="盈利 / 总投资额 * 100"            
            )
            yyyp_earn = (
                st.session_state.inventory.calc_yyyp_price()
                + st.session_state.inventory.sell_price()
                - st.session_state.inventory.total_cost()
            )
            col2[2].metric("盈利(悠悠有品计)", value=f"{yyyp_earn:.2f} 元")
            col2[3].metric(
                "总收益率",
                value=f"{yyyp_earn/st.session_state.inventory.total_cost()*100:.2f} %",
                help="盈利 / 总投资额 * 100"
            )
            col3[0].metric(
                "持有饰品收益(Buff计)",
                value=f"{st.session_state.inventory.calc_price() - st.session_state.inventory.total_cost_in_inventory():.2f} 元",
                help="库存价值 - 库存内和已租出饰品总花费"
            )
            col3[1].metric(
                "持有饰品收益率(Buff计)",
                value=f"{100 * (st.session_state.inventory.calc_price() - st.session_state.inventory.total_cost_in_inventory())/st.session_state.inventory.total_cost_in_inventory():.2f} %",
                help="( 持有饰品收益 - 库存内和已租出饰品总花费 ) * 100"
            )
            col3[2].metric(
                "持有饰品收益(悠悠有品计)",
                value=f"{st.session_state.inventory.calc_yyyp_price() - st.session_state.inventory.total_cost_in_inventory():.2f} 元",
                help="库存价值 - 库存内和已租出饰品总花费"
            )
            col3[3].metric(
                "持有饰品收益率(悠悠有品计)",
                value=f"{100 * (st.session_state.inventory.calc_yyyp_price() - st.session_state.inventory.total_cost_in_inventory())/st.session_state.inventory.total_cost_in_inventory():.2f} %",
                help="( 持有饰品收益 - 库存内和已租出饰品总花费 ) * 100"
            )
            st.subheader("目前资金组成")
            col4 = st.columns(2)
            with col4[0]:
                fig1 = Pie(init_opts=opts.InitOpts(theme=ThemeType.MACARONS)).add(
                    "库存资金组成",
                    [('出租',sum(
                            [
                                st.session_state.inventory()[good].price
                                for good in st.session_state.inventory()
                                if st.session_state.inventory()[good].status == 1
                            ]
                        )), 
                     ('在库',sum(
                            [
                                st.session_state.inventory()[good].price
                                for good in st.session_state.inventory()
                                if (
                                    st.session_state.inventory()[good].status == 0
                                    and st.session_state.inventory()[good].cost != 0
                                )
                            ]
                        ))],
                    radius=["30%", "75%"],
                )
                streamlit_echarts.st_pyecharts(fig1, height="400px", key="fig1")
            with col4[1]:
                fig2 = Pie(init_opts=opts.InitOpts(theme=ThemeType.MACARONS)).add(
                    "盈利资金组成",
                    [('库存增值',st.session_state.inventory.calc_price()
                        - st.session_state.inventory.total_cost_in_inventory(),), ('卖出收益',st.session_state.inventory.sell_earn(),)],
                    radius=["30%", "75%"],
                )
                streamlit_echarts.st_pyecharts(fig2, height="400px", key="fig2")
        else:
            st.caption("当前库存为空")
        # 追踪列表
        st.subheader("追踪列表")
        st.text("右键表格可以导出表格")
        if len(st.session_state.inventory()) > 0:
            data = pd.DataFrame(
                columns=['库存编号', 'Buff id', '名称', '状态', '购入花费(元)', '卖出价格']
            )
            for xx in st.session_state.inventory:
                xx = st.session_state.inventory()[xx]
                data.loc[len(data)] = [
                    xx.index,
                    xx.id,
                    xx.name,
                    xx.get_status(),
                    xx.cost,
                    xx.sell_price,
                ]

            gb = GridOptionsBuilder.from_dataframe(data)

            gb.configure_selection(
                selection_mode='multiple',
                use_checkbox=True,
            )
            gb.configure_side_bar()
            gb.configure_grid_options(domLayout='normal')
            gridOptions = gb.build()
            grid = AgGrid(
                data,
                gridOptions=gridOptions,
                allow_unsafe_jscode=True,
                return_mode_value=DataReturnMode.FILTERED,
                update_mode=GridUpdateMode.MODEL_CHANGED,
                enable_enterprise_modules=True,
            )
            selected = grid["selected_rows"]
            if selected != []:
                print(selected)
                st.button(
                    '删除选中饰品',
                    on_click=delete_goods,
                    args=(st.session_state.inventory, selected),
                )
                st.button(
                    '出售选中饰品',
                    on_click=sell_goods,
                    args=(st.session_state.inventory, selected),
                )
                st.button(
                    '租出选中饰品',
                    on_click=lease_goods,
                    args=(st.session_state.inventory, selected),
                )
                st.button(
                    '回仓选中饰品',
                    on_click=back_goods,
                    args=(st.session_state.inventory, selected),
                )
        else:
            st.caption("暂无饰品记录")

        goods = [st.session_state.inventory()[xx] for xx in st.session_state.inventory]
        # 已购列表
        st.subheader("已购列表")
        st.text("右键表格可以导出表格")
        track = [xx for xx in goods if xx.cost != 0]
        if len(track) > 0:
            data_track = pd.DataFrame([xx() for xx in track])
            data_track['Status'] = data_track['Status'].map(
                {0: '在库中', 1: '已租出', 2: '已卖出'}
            )

            data_track.columns = [
                'Buff id',
                '有品 id',
                '名称',
                '购入花费(元)',
                'Buff 价格',
                '有品价格',
                'Steam 价格(元)',
                '状态',
                '有品在售',
                '有品在租',
                '短租价格(元)',
                '长租价格(元)',
                '押金(元)',
                '租售比',
                '理论目前收益(元)',
                '理论目前收益率(%)',
                '租金比例(%)',
                '押金比例(%)',
                '年化短租比例(%)',
                '年化长租比例(%)',
                '套现比例(%)',
                'buff和有品价格比例',
            ]
            data_track = data_track.round(4)
            #del data_track['Buff id']
            #del data_track['有品 id']
            gb0 = GridOptionsBuilder.from_dataframe(data_track)
            gb0.configure_columns(["Buff id", "有品 id", "名称"], pinned=True)
            gb0.configure_columns(
                ['理论目前收益(元)', '理论目前收益率(%)'],
                cellStyle=cellsytle_jscode,
            )
            gb0.configure_side_bar()
            gb0.configure_grid_options(domLayout='normal')

            gridOptions = gb0.build()
            grid_track = AgGrid(
                data_track,
                gridOptions=gridOptions,
                allow_unsafe_jscode=True,
                enable_enterprise_modules=True,
            )
            st.subheader("理论收益分析")
            # Plot
            x = data_track.sort_values(
                by='理论目前收益率(%)',
            )['名称'].tolist()
            y1 = data_track.sort_values(
                by='理论目前收益率(%)',
            )['理论目前收益率(%)'].tolist()
            y2 = data_track.sort_values(
                by='理论目前收益率(%)',
            )['理论目前收益(元)'].tolist()
            fig0 = (
                Bar(init_opts=opts.InitOpts(theme=ThemeType.MACARONS))
                .add_xaxis(x)
                .add_yaxis("理论目前收益率(%)", y1)
                .add_yaxis("理论目前收益(元)", y2)
                .reversal_axis()
                .set_global_opts(
                    # 设置操作图表缩放功能,orient="vertical" 为Y轴 滑动
                    datazoom_opts=[
                        opts.DataZoomOpts(),
                        opts.DataZoomOpts(type_="inside", range_start=0, range_end=100),
                        opts.DataZoomOpts(
                            orient="vertical",
                            range_start=0,
                            range_end=100,
                        ),
                    ],
                )
                # .render("bar_datazoom_both.html")
            )

            streamlit_echarts.st_pyecharts(fig0, height="900px", key="fig0")

        else:
            st.caption("暂无已购饰品")

        # 观望列表
        st.subheader("观望列表")
        observe = [xx for xx in goods if xx.cost == 0]
        if len(observe) > 0:
            data_observe = pd.DataFrame([xx() for xx in observe])
            del data_observe['Cost']
            data_observe['Status'] = data_observe['Status'].map({0: '观望中'})

            data_observe.columns = [
                'Buff id',
                '有品 id',
                '名称',
                'Buff 价格(元)',
                '有品价格(元)',
                'Steam 价格(元)',
                '状态',
                '有品在售',
                '有品在租',
                '短租价格(元)',
                '长租价格(元)',
                '押金',
                '租售比',
                '租金比例(%)',
                '押金比例(%)',
                '年化短租比例(%)',
                '年化长租比例(%)',
                '套现比例(%)',
                'buff和有品价格比例',
            ]
            data_observe = data_observe.round(4)
            del data_observe['Buff id']
            del data_observe['有品 id']
            gb1 = GridOptionsBuilder.from_dataframe(data_observe)
            gb1.configure_columns(["Buff id", "有品 id", "名称"], pinned=True)
            gb1.configure_side_bar()
            gb1.configure_grid_options(domLayout='normal')

            gridOptions = gb1.build()
            grid_observe = AgGrid(
                data_observe,
                gridOptions=gridOptions,
                allow_unsafe_jscode=True,
                enable_enterprise_modules=True,
            )

        else:
            st.caption("暂无观望饰品")


if __name__ == "__main__":
    st.set_page_config(
        "CSGO 饰品投资追踪",
        "💰",
        layout="wide",
    )
    USER_TOKEN="Bearer xxx"
    main()